# теория · шаг 2 из 5
Шесть методов и когда какой
Коротко
Основных методов шесть, и выбирают между ними по одному вопросу: что должно
быть равномерным - число запросов, загрузка серверов или привязка клиента.
Седьмой, random, нужен в особом случае - о нём в конце.
round-robinравняет число запросов,least_conn- занятость. Замер: пять долгих запросов заняли серверы неравномерно, и все лёгкие ушли на свободный.ip_hashсчитает по первым трём октетам: смена последнего привязку не рвёт, смена третьего рвёт.hash ... consistentпри добавлении сервера переносит 26% ключей вместо 76%, но раскладывает их менее ровно.sticky cookieс 1.29.6 есть в открытой версии и работает по-настоящему: метка в куке, а не совпадение хеша.
Если различаешь least_conn и round-robin на практике и знаешь про sticky в открытой версии - листай до «Теперь сам».
Сначала ответь сам
Три бэкенда, round-robin. Половина запросов лёгкие (карточка товара, 20 мс), половина тяжёлые (отчёт, четыре секунды). Что произойдёт через несколько минут?
Запросов каждый сервер получит поровну, а тяжёлых - как повезёт. Вот замер: пять долгих запросов легли на серверы неровно (a1 достался один, соседям по два), и следом пошли шесть лёгких.
/light1 a3 /light2 a1 /light3 a2
/light4 a3 /light5 a1 /light6 a2
Круг продолжает раздавать по очереди, подкидывая работу тем, кто и так занят.
Тот же опыт с least_conn:
/light1 a1 /light2 a1 /light3 a1
/light4 a1 /light5 a1 /light6 a1
Все шесть ушли на свободный сервер. Равное число запросов не означает равную нагрузку.
На что это похоже
Очередь к трём кассам. Первый способ - пускать людей строго по очереди: этому в первую кассу, следующему во вторую. Работает, пока у всех одинаковые корзины; человек с двумя тележками парализует свою кассу, а соседняя скучает.
Второй способ - отправлять к кассе с самой короткой очередью. Ровно то же самое
делает least_conn, и по той же причине так устроены очереди в аэропорту.
Механизм: шесть способов выбрать сервер
Правило
upstream app {
least_conn; # метод - первой строкой группы
server a1:8000;
server a2:8000;
}
| Ситуация | Метод |
|---|---|
| Запросы примерно одинаковые, серверы равные | round-robin (ничего не пишем) |
| Стоимость запросов сильно разная | least_conn |
| Серверы с разным железом, хочется мерить | least_time header (открыт с 1.31; header - равнять по времени до первого байта, есть ещё last_byte) |
| Приложение хранит сессию в памяти процесса | sticky cookie |
| Кеширующий слой, важна попадаемость по ключу | hash $request_uri consistent |
| Балансировщиков несколько, друг о друге не знают | random two least_conn (седьмой метод: берёт два сервера наугад и из них — менее занятый) |
Разбор: ip_hash считает не весь адрес
Стенд, группа из трёх серверов, адрес клиента подменяется через realip:
| адрес клиента | сервер |
|---|---|
10.0.0.1 |
a1 |
10.0.0.2 |
a1 |
10.0.0.77 |
a1 |
10.0.0.254 |
a1 |
10.0.1.1 |
a2 |
10.0.2.1 |
a3 |
10.0.3.1 |
a1 |
Для IPv4 ключом служат первые три октета: смена последнего привязку не рвёт - это сделано ради клиентов, у которых адрес плавает внутри подсети провайдера. Для IPv6 берётся весь адрес.
Отсюда же главная беда метода: за NAT весь офис или вся сотовая сеть выглядят
одним адресом и оседают на одном сервере. А если перед nginx стоит CDN, без
realip из прошлого раздела ip_hash вообще бессмыслен - он увидит два-три
адреса узлов CDN.
Разбор: чем платит consistent
Параметр consistent включает ketama-хеширование. Обещание - при добавлении
сервера переезжает примерно 1/N ключей, а не почти все. Замер на 300 разных
ключах, группа растёт с трёх серверов до четырёх:
| переехало ключей | раскладка после | |
|---|---|---|
hash $request_uri |
228 из 300 (76%) | 77 / 73 / 73 / 77 |
hash $request_uri consistent |
79 из 300 (26%) | 94 / 65 / 62 / 79 |
Обещание выполняется: 26% против 76%. Но видна и цена - раскладка стала заметно менее ровной. Для кеширующего слоя это правильный размен: узел, перегруженный на четверть (94 против среднего 75), дешевле, чем три четверти промахов мимо кеша. Для раскидывания нагрузки - неправильный.
sticky: настоящая привязка, а не совпадение хеша
ip_hash и hash привязывают «по совпадению»: одинаковый ключ даёт одинаковый
сервер, пока состав группы не менялся. С 1.29.6 в открытой версии есть
директива, которая закрепляет клиента честно. Метод балансировки (least_conn,
ip_hash) пишут первой строкой группы, а sticky - директива привязки, и её
ставят среди server, не обязательно первой:
upstream app {
server a1:8000;
server a2:8000;
sticky cookie srv_id expires=1h path=/ httponly;
}
Проверено на стенде: первый ответ приносит куку, дальше клиент не сходит с сервера.
curl -si http://shop.local/ | grep -i set-cookie
Set-Cookie: srv_id=c69a4fd523c3ca0ca67cda2f009f1ea6; expires=Fri, 11-Sep-26 12:26:19 GMT; max-age=3600; httponly; path=/
с кукой: a1 a1 a1 a1 a1
без куки: a2 a1 a2 a1 a2
Отличий от хеша два, и оба важные: привязка не разъезжается при добавлении сервера в группу и не ломается за NAT - метка у каждого браузера своя.
Рядом два параметра, ради которых всё и затевалось:
server a1:8000 route=a;
server a2:8000 route=b drain;
route задаёт метку явно - удобно, когда её выставляет балансировщик выше по
цепочке. drain переводит сервер в режим «новых не принимать, старых
дообслужить»: закреплённые клиенты продолжают ходить на него, новые уходят на
соседей. Это штатный способ вывести узел из работы, не оборвав сессии.
Половина статей в интернете всё ещё пишет «sticky - это только NGINX Plus». С
1.29.6 это неправда; в 1.31.5 на стенде работают все три формы - cookie,
route и learn.
Что ломается без этого
backup не работает с hash, ip_hash и random. Конфиг не соберётся
вовсе:
nginx: [emerg] balancing method does not support parameter "backup" in /etc/nginx/nginx.conf:11
nginx: configuration file /etc/nginx/nginx.conf test failed
С least_conn и sticky резервный сервер разрешён. Если нужны и привязка, и
резерв - выбирать придётся между ними либо делать приложение stateless.
Убирать сервер из группы нельзя - только помечать down. Список участвует в
вычислении хеша, поэтому удаление строки перетасует привязки всех клиентов, а
не только тех, кто сидел на убранном сервере.
slow_start в открытой версии нет. Строка server a1:8000 slow_start=30s;
роняет конфиг с invalid parameter. Вернувшийся сервер получает полную долю
трафика сразу, и если он ещё прогревается, первые пользователи это почувствуют.
Зачем это в работе
Выбор метода - это ответ на вопрос про приложение, а не про nginx. Стоимость
запросов ровная - оставь умолчание, оно предсказуемее всех. Разброс большой -
least_conn. Железо разное - least_time header, он мерит время ответа вместо
того, чтобы верить весам в конфиге.
Привязку включают, только когда состояние действительно живёт в процессе, и
включают sticky cookie, а не хеш по адресу. И помнят, что даже идеальная
привязка не отменяет разговора про общее хранилище: сервер всё равно придётся
перезапустить - на выкладке, при обновлении ядра, при отказе, - и все сессии в
его памяти исчезнут.
Теперь сам
Кеширующий слой из четырёх серверов, ключ hash $request_uri consistent.
Добавили пятый сервер, и в первый час нагрузка на базу выросла втрое, хотя
переехать должно было около пятой части ключей. Что не сходится?
Сходится. Переехало и правда примерно 20% ключей, но каждый переехавший ключ -
это промах мимо кеша и поход в базу, пока новый узел не прогрелся. Замер выше
объясняет и порядок величины: без consistent переехало бы три четверти, и
рост был бы не втрое, а на порядок. Лечится не выбором метода, а прогревом
нового узла до включения в группу.
Главное
Метод пишется первой строкой группы. round-robin равняет число запросов,
least_conn - занятость (на замере он отдал все лёгкие запросы свободному
серверу, пока круг продолжал грузить занятых), least_time мерит время ответа.
ip_hash считает по первым трём октетам и ломается за NAT и CDN; hash ...
consistent переносит при росте группы 26% ключей вместо 76%, но раскладывает
менее ровно. Настоящую привязку даёт sticky cookie - с 1.29.6 он есть в
открытой версии, а drain рядом выводит сервер из работы, дообслуживая
закреплённых. backup несовместим с hash, ip_hash и random, а slow_start
в открытой версии нет вовсе.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий