Nginx и HAProxy имеют опции для балансировки нагрузки и умеют организовывать отказоустойчивость. Т.е при выходе одного из трех бакендов он просто перестанет посылать запросы к нему и распределит нагрузку между двумя оставшимися. Примеры в этой статье будут приведены для nginx, но теория является общей для всех балансировщиков HTTP.
upstream backends_poll { server 10.31.1.17 weight=100 max_fails=3 fail_timeout=10 slow_start=75 ; server 10.31.1.18 weight=100; server 10.31.1.19 weight=100; least_conn; } server { ... location / { proxy_connect_timeout 3s; proxy_next_upstream error timeout invalid_header http_502; proxy_next_upstream_timeout 3s; proxy_next_upstream_tries 1; ... } }
Это отлично работает, если на том конце сервер включен и доступен по сети, но сервис "упал". Nginx быстро получит через ICMP ответ "connection refused" и перекинет запрос на другой бакенд без видимой для пользователя ошибки.
Но что произойдет при аппаратном выключении сервера 10.32.1.19, если адрес сервера с nginx - 10.32.1.4/24, т.е. находится в том же сегменте сети, что и бакенд?
Сперва nginx будет пытаться соединиться до наступления proxy_connect_timeout, а потом перекинет запрос на другой бакенд. Запрос, конечно, не выдаст пользователю никакой ошибки, но работать он будет медленно: proxy_connect_timeout + время обработки, например 3+0.009 = 3.009s. Три секунды для отдачи html-станички - это величина неприемлемая и мешающая пользователям. Это проблема номер раз.
Она актуальна как при авариях до "выключения железки", так и при сетевых проблемах, и в ряде случаев при деплое с перезапуском виртуальных машин (есть и такие сервисы, обычно это делается в реалиях суровой ИБ).
Проблема реальна, т.к. при трех бакендах страдает треть соединений (грубо), а, например, при одновременной обработке хотя бы трех тысяч запросов - это уже тысяча запросов пользователей/поисковиков/api-клиентов тормозит на одну секунду. Мобильные клиенты вовсе могут не дождаться долгого ответа из-за непостоянства в 3G/4G передаче данных.
Nginx умеет запоминать бакенд, выдавший ошибку и потом пробно отправляет на него запрос, как в нашем примере на одну минуту. За минуту - из ARP кеша выпала запись о MAC-адресе недоступного сервера и вся история повторяется, но еще и с созданием дополнительного трафика во внутреней сети. Оно будет повторяться периодически, пока сервер не вернется в сеть. Это проблема номер два.
Что меняет маршрутизатор?
Маршрутизатор имеет на себе ARP-таблицу и берет на себя функцию возвращать ICMP-ответы до тех пор, пока сервер-получатель не вернется в сеть, а грамотно настроеннный маршрутизатор сразу даст ICMP-ответ в сторону nginx о том, что host unreachable. Для nginx - ICMP ответ host unreachable имеет такой же эфект, как и connection refused. Выдать ответ маршрутизатор может очень быстро - гораздо быстрее трех секунд. Это снижает время переброса запроса на другой бакенд с величины proxy_connect_timeout до времени обработки ICMP-ответа.
Маршрутизатор может отдавать этот ответ до тех пор, пока сервер не вернеться в сеть и не начнет отзыватся на ARP. Если все строить на уровне l2/l3, как в этом описании, то придеться снизить таймаут ARP-таблицы маршрутизатора и искуственно поднять объем трафика, хоть и совсем не намного - 1 пакет на 1 сервер за время таймаута. Т.е. если у вас стоит таймаут ARP-таблицы в 1 секунду, а в сегменте 8 серверов, то это будет 8 запросов и 8 ответов за секунду. Всего arp-16 пакетов не создадут проблем заполнения канала. Очень хорошо, что маршрутизатор запрашивает arpу 1 раз, а не на каждый пакет.
Такая схема, конечно же, имеет минусы, потому что нагружает CPU-маршрутизатора постоянными действиями с ARP-таблицей.
В некоторых сервисах мы применяем /32 маршрутизацию и каналы точка-точка до бакенд-серверов. Это позволяет на уровне L3 (используя OSPF), искуственно выводить бакенды из работы и включать их назад без reload-а nginx-ов. Последнее важно в проектах с большим числом keep-alive соединений или websocket подключений - в них reload nginx-а приведет к тому, что новые соединения будут обрабатываться новыми процессами, а старые процессы не завершатся до тех пор, пока не завершатся WS:// или keep-alive соединения.
В итоге
Обе проблемы (и начальный тормоз части запросов и периодические просадки при попытках подключится к упавшему backend-у) решаемы и легче всего они решаются на уровне построения сети Вашего Production. Особенно важно то, что таким нехитрым разделением на подсети - балансировщиков и бакендов - поднимается качество измеримое в секундах ответа в моменты аварий. Падения одного бакенда становятся не так заметны пользователю, а при /32 маршрутизации к бакендам и вовсе не привлекают внимания пользователей к разовой задержке в ответе.
Дополнительные плюсы
1) Очевидно, что подсеть с frontend-серверами (балансировщиками) пропускает через себя гораздо больше, чем сеть с backend-серверами. Это объясняется тем, что frontend отдают еще и статику, а backend работают только с динамикой (которой по объемам трафика в большей части проектов меньше). Разделив одну сеть на две подсети, можно четко контролировать узкие места и управлять каналами и пропускной способностью, исходя из задачи - объем трафика vs горячая замена backend.
2) Так же есть отдельная задача в сети backend-серверов - работа с серверами баз данных (СУБД). Сеть до СУБД, в отличие от сети, по которой проходит http/fastcgi трафик, должна соответствовать горазо более высоким требованиям к Latency и потерям пакетов (хорошая показательная величина - "количество потерянных пакетов за год"). В ответственом проекте связь между СУБД и коммутатором должна резервироваться на двух уровнях: L2 и L7. На уровне L2 это может быть самый обычный Etherchannel, главное, чтобы он был корректно настроен, а на уровне L7 это может быть балансировщик нагрузки между базами данных (pgpool для postgresql, mongos для mongodb, и т.д.).
Разделение сетей frontend (nginx) и backend позволяет решить задачу качества сети между СУБД и Backend-серверами гораздо легче и при этом дешевле, что немаловажно при закупке оборудования.
Протокол WebSocket имеет свои преимущества и свои недостатки: детальный разбор
Не секрет, что хорошо настроенный сервер "падает" гораздо реже, чем доступ из него в Интернет
Позволяет решать интеллектуальные задачи помимо задач самого мониторинга
Потому что отсутствует самое важное, что должно быть в любой надежной инженерной системе
Особенности серверных приложений, работающих с сетью IoT-устройств на практике и в теории