3. Load balancing
Балансировка нагрузки в Nginx: от Round Robin до Power of Two Choices и gRPC
Когда один сервер с Go-бэкендом исчерпывает физические лимиты масштабирования (CPU, память, пропускную способность сетевой карты), естественным шагом становится горизонтальное масштабирование — запуск нескольких независимых реплик приложения на разных узлах кластера или виртуальных машинах.
Но кто будет распределять лавину входящих запросов между этими репликами? Кто вовремя заметит, что один из инстансов Go рухнул из-за паники или нехватки памяти (OOM), и мгновенно перенаправит трафик на живые узлы? Эту фундаментальную задачу берет на себя балансировщик нагрузки (Load Balancer).
В классической связке Nginx выступает в роли балансировщика седьмого уровня модели OSI (L7 Application Load Balancer). В отличие от L4-балансировщиков (таких как чистый HAProxy в режиме TCP, Linux IPVS или AWS Network Load Balancer), оперирующих только голыми TCP-пакетами и портами, Nginx глубоко анализирует протокол HTTP: заголовки, куки, URI путей, методы и тело запроса. Это позволяет принимать гибкие и интеллектуальные решения о маршрутизации трафика к пулам Go-микросервисов.
1. Блок upstream: Декларативный пул бэкендов
В конфигурации Nginx целевая группа серверов объединяется в директиву upstream. Это виртуальный кластер бэкендов, к которому затем обращаются директивы proxy_pass или grpc_pass:
upstream go_backends {
# Основные рабочие узлы с параметрами пассивных проверок
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
# Резервный сервер: вступает в работу только если упадут все основные
server 10.0.1.12:8080 backup;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://go_backends;
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
}
2. Алгоритмы балансировки трафика
Выбор алгоритма балансировки — это ключевой фактор, определяющий хвостовую задержку (tail latency, p99) и равномерность утилизации ресурсов вашего Go-кластера.
1. Round Robin (Круговое распределение по умолчанию)
Запросы поочередно распределяются между серверами по кругу: 1-й → Сервер А, 2-й → Сервер B, 3-й → Сервер C, 4-й → Сервер А.
Round Robin справедлив только в идеальном сферическом вакууме: когда все инстансы развернуты на абсолютно идентичном железе, а каждый входящий HTTP-запрос требует одинакового времени CPU и памяти. В реальном бэкенде такого равенства не существует в принципе.
2. Least Connections (least_conn)
Директива least_conn указывает Nginx направлять очередной входящий запрос на тот сервер, у которого в текущий момент зафиксировано наименьшее количество активных TCP-соединений.
3. IP Hash (ip_hash)
Алгоритм вычисляет хэш от первых трех октетов IPv4-адреса клиента (или полного IPv6) и жестко привязывает пользователя к конкретному серверу бэкенда (Sticky Sessions).
4. Random и концепция «Сила двух выборов» (Power of Two Choices)
Современная директива random two least_conn; реализует фундаментальный алгоритм распределенных систем — Power of Two Random Choices.
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
A["Входящий запрос"]:::entry --> B{"Nginx random two"}
B -->|"Случайный выбор 2"| C["Go Server 1 - 10 активных коннектов"]
B -->|"Случайный выбор 2"| D["Go Server 2 - 45 активных коннектов"]
C -->|"Сравнение: 10 меньше 45"| E["Запрос отправлен на Server 1"]
3. Health Checks: Пассивные проверки и защита от 502 Bad Gateway
Когда один из инстансов Go внезапно падает (например, аварийно завершается из-за OOM Killer ядра Linux или неперехваченной паники), балансировщик обязан незамедлительно исключить его из маршрутизации.
В базовой Open-Source версии Nginx поддерживаются Пассивные проверки здоровья (Passive Health Checks). Они настраиваются директивами внутри блока upstream:
max_fails=3: Число последовательных неудачных попыток соединения или чтения ответа от бэкенда, после которого сервер признается временно недоступным.fail_timeout=30s: Временной интервал, в течение которого Nginx выдерживает сервер в карантине (не шлет на него запросы), прежде чем предпримет новую попытку отправить проверочный запрос.
Механика работы: Nginx не генерирует фоновый проверочный трафик самостоятельно. Он узнает о недоступности бэкенда исключительно в момент обслуживания реального клиентского запроса. Если системный вызов connect() завершается по таймауту или сервер обрывает соединение, Nginx инкрементирует счетчик сбоев (fails).
4. Специфика балансировки gRPC и HTTP/2
С бурным развитием микросервисов в Go стандартом межсервисного общения стал фреймворк gRPC, работающий поверх транспортного протокола HTTP/2.
И здесь традиционная балансировка через обычный proxy_pass терпит полный крах!
Причина в архитектуре HTTP/2: протокол выполняет мультиплексирование потоков (Streams) внутри одного постоянного TCP-соединения. Тысячи RPC-запросов передаются параллельно в рамках единой TCP-сессии.
Если настроить обычный proxy_pass http://grpc_backends;:
- Nginx установит ровно одно TCP-соединение с каждым бэкендом.
- Все миллионы gRPC-запросов от сотен клиентов полетят через эти зафиксированные соединения.
- Алгоритм Round Robin распределит TCP-соединения, а не сами RPC-вызовы! В результате один сервер может оказаться забит тысячами одновременных вызовов, а второй будет полностью простаивать.
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph Bad ["Обычный proxy_pass (Дисбаланс по TCP-соединениям)"]
C1["Клиент 1 (100 RPC)"]:::entry --> TCP1["Единый TCP/HTTP2 канал"]
C2["Клиент 2 (500 RPC)"]:::entry --> TCP2["Единый TCP/HTTP2 канал"]
TCP1 --> S1["Go Server 1 (Перегружен: 600 RPC)"]
TCP2 --> S1
S2["Go Server 2 (Простаивает: 0 RPC)"]
end
subgraph Good ["Nginx grpc_pass (Балансировка по потокам)"]
Client["Клиенты gRPC"]:::network --> Ingress["Nginx (grpc_pass + least_conn)"]
Ingress -->|"RPC Streams 1..300"| G1["Go Server 1 (300 RPC)"]
Ingress -->|"RPC Streams 301..600"| G2["Go Server 2 (300 RPC)"]
end
Для корректной балансировки gRPC в Nginx используется специальный модуль:
upstream grpc_backends {
# Для gRPC критически важен least_conn по числу активных стримов
least_conn;
server 10.0.1.10:4443;
server 10.0.1.11:4443;
# Пул открытых соединений HTTP/2 для минимизации задержек
keepalive 16;
}
server {
# Включаем поддержку HTTP/2 (или gRPC over cleartext h2c)
listen 443 ssl;
http2 on; # Современный синтаксис Nginx 1.25+
ssl_certificate /etc/ssl/certs/service.crt;
ssl_certificate_key /etc/ssl/private/service.key;
location / {
# Специализированное проксирование gRPC
grpc_pass grpc://grpc_backends;
# Настройка таймаутов для длительных Streaming RPC
grpc_read_timeout 300s;
grpc_send_timeout 300s;
}
}
Ключевые требования для gRPC:
- Использование директивы
grpc_passвместоproxy_pass. - Применение алгоритмов
least_connилиrandom two, которые отслеживают количество активных потоков (активных запросов) внутри соединения. - Обязательный
keepaliveв блокеupstream, предотвращающий постоянный пересчет параметров сжатия HPACK и пересоздание TLS-сессий.
Итог
- Least Connections — стандарт для Go: Алгоритм
least_connнивелирует естественный разброс во времени выполнения тяжелых и легких HTTP-хендлеров, исключая появление Hot Spots. - Опасность ip_hash: Привязка клиентов по IP нарушает принципы Stateless-архитектуры и приводит к перегрузке узлов из-за агрегации трафика под корпоративными NAT.
- Power of Two Choices: Математически выверенный выбор между двумя случайными узлами (
random two least_conn) обеспечивает эффективную балансировку с алгоритмической сложностью $O(1)$. - Пассивные проверки и отказоустойчивость: Связка директив
max_fails,fail_timeoutиproxy_next_upstreamпредотвращает появление ошибок 502 у пользователей при сбоях бэкендов. - Мультиплексирование gRPC: Балансировка gRPC требует специализированной директивы
grpc_passи поддержки HTTP/2, чтобы балансировать отдельные RPC-вызовы, а не монолитные TCP-трубы.
Балансировщик эффективно распределяет открытый HTTP-трафик, но передача незашифрованных данных через публичный интернет недопустима. В следующей лекции мы детально изучим криптографическую защиту соединений и разгрузку бэкенда: 4. TLS termination.