# теория · шаг 1 из 5
Группа серверов вместо адреса
Коротко
upstream - именованная группа серверов. Пишется в контексте http, дальше имя
группы ставят в proxy_pass вместо адреса.
- Сервер выбирается на каждый запрос, а не на клиента: приложение с сессией в памяти процесса за балансировщиком ломается.
Hostбэкенду по умолчанию станет именем группы (Host: app), а не именем сайта.- Счётчики группы по умолчанию у каждого воркера свои. Строка
zoneкладёт их в общую память, и это меняет поведение отказов. - Проверять раскладку надо
$upstream_addrв логе, а не догадками.
Если знаешь, что решение принимается на каждый запрос, и понимаешь смысл строки zone - листай до «Теперь сам».
Сначала ответь сам
upstream app {
server a1:8000;
server a2:8000;
server a3:8000;
}
location / { proxy_pass http://app; }
a1, a2, a3 - имена серверов: в Docker это имена контейнеров, на обычной
машине - записи из DNS или /etc/hosts. Слово server внутри upstream - не
тот блок server { }, что описывает сайт: здесь это просто «адрес в группе»,
listen и location внутрь не пишут. Имя группы nginx отличает от адреса по
тому, что оно объявлено блоком upstream. Какой Host увидит приложение?
Стенд отвечает app - имя группы (подставной бэкенд печатает $http_host). Не
имя сайта, не адрес конкретного сервера.
Четыре строки заголовков из прошлого раздела нужны здесь ровно так же, и
забывают их именно при переезде с одиночного адреса на группу: раньше в Host
был хотя бы app-1:8000, теперь там слово, которого не существует нигде.
На что это похоже
Отдел из трёх сотрудников и общий телефон на входе. Звонящий не выбирает человека - он звонит в отдел, и трубку берёт тот, кто свободен.
Пока работа безличная, схема работает. Стоит одному сотруднику записать что-то в свой блокнот и попросить перезвонить позже - и следующий звонок попадёт к другому, у которого этого блокнота нет. Ровно так ломается приложение, хранящее сессию в памяти своего процесса.
Механизм: решение принимается на каждый запрос
Выходов из этого два, и они не равнозначны. Первый - сделать приложение stateless: сессии в Redis, файлы в общем хранилище. Тогда любой сервер можно убрать в любой момент. Второй - привязать клиента к серверу, чему посвящён следующий урок; это лечение симптома, потому что сервер всё равно нельзя выключить, не уронив чьи-то сессии.
Первый путь правильный, второй быстрый. Часто делают второй, чтобы прожить до первого.
Правило
upstream app {
zone app 64k; # общая память на всех воркеров
server a1:8000 weight=3; # втрое больше запросов; параметры через = без пробелов
server a2:8000; # weight=1 по умолчанию
}
Веса нужны, когда серверы разные по мощности. Единственный честный способ
подобрать числа - измерить: если $upstream_response_time у «тяжёлого» сервера
растёт быстрее, чем у остальных, вес завышен.
Строка zone выглядит служебной, а меняет она главное - где живут счётчики
группы. 64k - размер этой общей памяти, имя обычно берут как у группы. Без
неё каждый рабочий процесс считает отказы и соединения сам по себе.
Разбор: круг раздаётся не там, где кажется
Метод по умолчанию - круговой перебор. На стенде из трёх серверов при одном рабочем процессе он идеален:
Бэкенды печатают своё имя, worker_processes 1, 30 запросов подряд:
a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a3
А при четырёх процессах в ту же последовательность вклиниваются повторы:
a2a3a1a2a3a1a2a3a1a2a3a1a2a2a3a1a2a3a1a2a3a1a2a3a1a2a3a1a2a2
auto - это столько процессов, сколько ядер (задаётся worker_processes);
у тебя число своё. Ничего не сломалось: у каждого воркера свой счётчик круга, и запрос, попавший к
другому воркеру, начинает круг с его позиции. На больших числах раскладка
выравнивается, а вот на десятке запросов «проверил - неровно» ничего не значит.
Это первое следствие раздельных счётчиков. Второе, куда более неприятное, - в
главе про отказы: без zone каждый воркер узнаёт о падении сервера
самостоятельно, и платит за это своим неудачным запросом.
Разбор: как посмотреть, кто на самом деле отвечает
Догадки тут не работают - в лог добавляют адрес сервера, который обработал запрос:
log_format up '$time_iso8601 $request_uri -> $upstream_addr rt=$request_time';
Строка access_log /var/log/nginx/up.log up; пишет этот формат; awk '{print
$4}' берёт четвёртое поле (адрес после ->), uniq -c считает одинаковые,
sort перед ним обязателен - uniq схлопывает только соседние строки:
67 10.0.0.11:8000
66 10.0.0.12:8000
67 10.0.0.13:8000
Слева количество, справа адрес. Имена a1-a3 в логе превратились в адреса:
nginx резолвит их при старте, в замере они легли на 10.0.0.11-13. Ровно -
группа работает. Перекос на один сервер обычно значит, что остальные
помечены недоступными. А если вместо адреса в логе стоит имя группы -
-> app - значит живых серверов не осталось вовсе; разбор этой строки в главе
про отказы.
Что ломается без этого
Забытый proxy_set_header Host $host в группе даёт не «немного не тот
заголовок», а имя, которого нет в DNS: письма со ссылками на http://app/,
непройденные проверки источника (приложение сверяет Host с настроенным
доменом и отклоняет чужой), мультидоменное приложение, выбравшее сайт наугад.
Второе - тихая потеря половины мощности. Сервер выпал из ротации, клиенты
ничего не заметили (nginx сам повторил запрос на живом сервере - когда именно,
разбирает урок про отказыРазбирается в разделе 8, глава «Отказы, backup и keepalive»: Как nginx понимает, что сервер лёг), нагрузка на оставшиеся
выросла вдвое, и узнаешь ты об этом в час пик. Поэтому раскладку по
$upstream_addr стоит держать на графике, а не смотреть руками раз в квартал.
Зачем это в работе
Второй экземпляр приложения появляется по одной из двух причин: одного не хватает по нагрузке или одного мало для выкладки без простоя (обновление версии так, чтобы ни один запрос не упал). Вторая причина приходит раньше первой почти всегда - и именно она заставляет заводить группу там, где нагрузки нет вовсе.
С этого момента приложение обязано пережить, что запросы одного человека приходят в разные процессы. Проверяется это не чтением кода, а простым опытом: подними два экземпляра, залогинься, походи по сайту. Если разлогинивает через раз - состояние живёт в памяти процесса.
Теперь сам
В группе три сервера, все живые. Разработчик жалуется: «загрузка файла падает через раз». Файл загружается по частям, каждая часть - отдельный запрос. Что происходит и что чинить?
Части уходят на разные серверы, и тот, кто получил вторую часть, не знает про
первую. Залипание (привязка клиента к одному серверу, следующий урокРазбирается в разделе 8, глава «Методы балансировки»: Шесть методов и когда какой)
тут закроет симптом, но правильный ответ - собирать части в
общем хранилище, а не в памяти или локальном каталоге процесса. Быстрая
проверка: посмотреть $upstream_addr у запросов одной загрузки - адреса будут
разные.
Главное
upstream - именованная группа серверов в контексте http, её имя ставят в
proxy_pass вместо адреса. Сервер выбирается на каждый запрос заново, поэтому
приложение с состоянием в памяти процесса за группой ломается, и правильное
лечение - вынести состояние наружу. Host бэкенду по умолчанию станет именем
группы, так что четыре строки заголовков нужны и здесь. Счётчики группы без
строки zone у каждого воркера свои - на круговом переборе это видно как
повторы в короткой выборке, а на отказах обходится дороже. Раскладку проверяют
$upstream_addr в логе.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий