# теория · шаг 1 из 5
Как nginx понимает, что сервер лёг
Коротко
Открытый nginx не ходит на бэкенды проверять их состояние. Выводы он делает из обычных запросов пользователей - и цена этих выводов зависит от одной строки в группе.
max_failsнеудач заfail_timeoutвыводят сервер из ротации на тот же срок. Умолчание - одна неудача и десять секунд.- Без
zoneсчётчик у каждого воркера свой: замер даёт четыре открытия одного и того же падения четырьмя процессами. Соzone- одно. - Ответ 500 или 502 от приложения неудачей не считается. Добавишь
http_502вproxy_next_upstream- и бэкенд, штатно отвечающий 502, вылетит из группы. - Имя группы вместо адреса в
$upstream_addrозначаетno live upstreamsи 502 клиенту.
Если знаешь, что 500 не считается отказом, и понимаешь роль zone в счётчиках - листай до «Теперь сам».
Сначала ответь сам
Четыре рабочих процесса, группа из трёх серверов, max_fails=1. Один сервер
падает. Сколько неудачных запросов уйдёт в мёртвый сервер, прежде чем nginx
перестанет туда ходить?
Ответ зависит от строки, которой в конфиге обычно нет. Замер на стенде без
zone:
[warn] 72#72: *31 upstream server temporarily disabled while connecting to upstream
[warn] 73#73: *44 upstream server temporarily disabled while connecting to upstream
[warn] 74#74: *57 upstream server temporarily disabled while connecting to upstream
[warn] 75#75: *68 upstream server temporarily disabled while connecting to upstream
72#72 - номер процесса и потока (у каждого воркера свой), *31 - номер
соединения. Четыре строки от четырёх разных процессов: каждый узнал о падении сам и
заплатил за это своим неудачным соединением. Со строкой zone app 64k; в группе
строка ровно одна.
На что это похоже
Четыре продавца в одном зале и общий список поставщиков. Если список висит на стене, первый же продавец, которому не отгрузили товар, вычеркнет поставщика для всех. Если у каждого свой блокнот, каждому придётся обжечься лично.
Работа в итоге сделается одинаково, разница только в цене обучения - и в том, сколько покупателей подождёт, пока все четверо разберутся.
Механизм: пассивная проверка
Правило
upstream app {
zone app 64k;
server a1:8000 max_fails=3 fail_timeout=15s;
server a2:8000;
}
Читается так: «если за 15 секунд с сервером случилось 3 неудачи, считать его
недоступным на те же 15 секунд». Умолчание - max_fails=1 fail_timeout=10s.
Список того, что считается неудачей, задаёт proxy_next_upstream:
proxy_next_upstream error timeout;
proxy_next_upstream_tries 2; # всего попыток, считая первую
proxy_next_upstream_timeout 10s; # общий бюджет времени на все попытки
Умолчание - error timeout: обрыв соединения, отказ в соединении, таймаут. Код
ответа приложения в этот список не входит.
Разбор: 502 от приложения - не отказ, и это к лучшему
Стенд, бэкенд отвечает 502 на каждый запрос. С умолчаниями:
/fail -> a1 status=502
/fail -> a2 status=502
/fail -> a1 status=502
/fail -> a2 status=502
nginx передал ответ клиенту и ничего не заподозрил. Теперь добавим http_502 в
список, как советуют «чтобы клиент не видел ошибок»:
/fail -> a1, a2 status=502 ustatus=502, 502
/fail -> a2, app status=502 ustatus=502, 502
/fail -> app status=502 ustatus=502
/fail -> app status=502 ustatus=502
Первый запрос обошёл оба сервера. Второй нашёл только один живой и добил
группу. Дальше в $upstream_addr стоит имя группы app - значит no live
upstreams: живых серверов не осталось, и nginx отвечает 502 сам, даже не
пробуя соединиться:
[error] no live upstreams while connecting to upstream, upstream: "http://app/fail"
Клиент как получал 502, так и получает. Разница в том, что теперь вся группа
выключена, и штатные запросы, на которые бэкенд ответил бы 200, тоже получат
502. Отсюда правило: коды в proxy_next_upstream добавляют, только когда 502
означает «сервер сломан», а не «приложение так отвечает».
Разбор: как выглядит успешный повтор
Когда сервер и правда умер, клиент обычно не видит ничего:
/api/cart -> 10.0.0.11:8000, 10.0.0.12:8000 status=200 ustatus=502, 200
Два адреса через запятую - первая попытка и вторая, успешная. В error_log в
этот момент две строки подряд:
[error] connect() failed (111: Connection refused) while connecting to upstream
[warn] upstream server temporarily disabled while connecting to upstream
Первая говорит, что случилось, вторая - что nginx решил. Такие пары в логе стоит считать: если их десятки в час, у тебя постоянно падающий бэкенд, а пользователи об этом молчат, потому что каждый раз выручает повтор.
Что ломается без этого
non_idempotent добавляют, не подумав. Этот флаг в списке
proxy_next_upstream разрешает повторять на другом сервере POST, PUT и DELETE -
запросы, меняющие данные (идемпотентный запрос можно повторить без вреда,
меняющий - нет). Если первый бэкенд успел провести платёж и умер до
ответа, повтор проведёт его второй раз. Для меняющих данные запросов повтор
безопасен, только когда приложение умеет идемпотентность по ключу операции.
Сервер, вернувшийся из паузы, встречает пользователя, а не проверку. Между
«процесс запустился» и «готов обслуживать» проходит время: прогрев, миграции,
подключение к базе. slow_start в открытой версии нет, поэтому вернувшийся
сервер сразу получает полную долю; чинится это порядком выкладки из следующего
урока, а не настройками группы.
Единственный сервер группы не выключается никогда. Когда сервер в группе
один, max_fails и fail_timeout игнорируются: выключать некого, отвечать
стало бы нечем. Поэтому на одиночном бэкенде строки no live upstreams не
бывает в принципе, и весь разговор про max_fails начинается со второго
сервера. А вот когда серверов несколько, недоступными могут стать все -
это и показал разбор http_502 выше: «последний живой» защищён, только пока он
буквально единственный в группе.
Зачем это в работе
down и backup - две соседние строки с очень разным смыслом:
upstream app {
zone app 64k;
server a1:8000 down; # выведен руками, на обслуживание
server a2:8000;
server maintenance:8080 backup; # включится, только если все основные легли
}
down - «не трогать вовсе»; так выводят машину на работы, не удаляя строку
(удаление перетасует привязки при hash и ip_hash). backup - резерв,
который молчит, пока жив хоть один основной сервер. Типичное применение - не
второй экземпляр приложения, а маленький сервис со страницей «идут работы»:
вежливая заглушка лучше, чем 502.
Теперь сам
В группе два сервера. Мониторинг показывает, что раз в несколько минут
появляется пара строк connect() failed и temporarily disabled, но доля 5xx у
пользователей нулевая. Стоит ли реагировать?
Стоит. Пользователей выручает повтор на живом сервере, но каждый такой эпизод -
это половина мощности, выключенная на fail_timeout, и лишняя задержка у
попавших под него запросов. Смотреть надо в бэкенд: обычно это перезапуск по
нехватке памяти или исчерпание пула соединений к базе. И заодно добавить zone,
чтобы падение открывал один воркер, а не каждый по очереди.
Главное
Открытая версия проверяет бэкенды пассивно: max_fails неудач за
fail_timeout (по умолчанию одна за десять секунд) выключают сервер на тот же
срок, а первым после паузы идёт настоящий пользовательский запрос. Без zone
счётчик у каждого воркера свой, и падение открывают все по очереди. Неудачей
считается error timeout, но не код ответа: добавив http_502, легко выключить
всю группу из-за приложения, которое так отвечает штатно. Имя группы вместо
адреса в $upstream_addr - это no live upstreams. Сервер на обслуживание
помечают down, а backup держат под заглушку.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий