Проблема

При нагрузке свыше 8 000 RPS на входной балансировщик Nginx бэкенды на Go начали отдавать 502 Bad Gateway. Логи Nginx заполнились сообщениями:

TEXT
connect() failed (99: Cannot assign requested address) while connecting to upstream

Одновременно средняя задержка ответа сервиса (p99) выросла с 8 мс до 140 мс.

Анализ сокетов

Смотрим статус сокетов через ss -s:

TEXT
Total: 34100
TCP:   32800 (estab 4200, closed 27500, orphaned 12, timewait 27480)

Более 27 000 сокетов висят в состоянии TIME_WAIT!

Смотрим доступный диапазон портов:

BASH
sysctl net.ipv4.ip_local_port_range
# net.ipv4.ip_local_port_range = 32768 60999

Доступно всего 28 231 локальный порт. Nginx на каждый входящий HTTP-запрос открывал новое TCP-соединение к бэкенду (127.0.0.1:8080), отправлял один запрос, получал ответ и закрывал соединение. Сокет переходил в TIME_WAIT на 60 секунд (стандартный TCP_TIMEWAIT_LEN).

Правильная конфигурация Nginx Upstream

Чтобы Nginx держал пул постоянных соединений к бэкендам, необходимо обязательно настроить keepalive в блоке upstream и передать правильные заголовки в блоке location:

NGINX
upstream backend_cluster {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
    
    # Размер пула постоянных keepalive соединений на каждый воркер
    keepalive 128;
    keepalive_requests 10000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name api.internal;

    location / {
        proxy_pass http://backend_cluster;
        
        # КРИТИЧЕСКИ ВАЖНО: переопределить HTTP/1.0 на HTTP/1.1
        proxy_http_version 1.1;
        
        # Очистить заголовок Connection (по умолчанию Nginx шлет "close")
        proxy_set_header Connection "";
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Тюнинг ядра для плотного сетевого трафика

На уровне sysctl добавляем возможность переиспользования TIME_WAIT сокетов:

INI
# /etc/sysctl.d/99-network-highload.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15

Применяем: sysctl -p /etc/sysctl.d/99-network-highload.conf.

Результат

  • Количество сокетов в TIME_WAIT упало с 27 500 до менее 350.
  • Задержка p99 нормализовалась до 4.5 мс при стабильных 12 000 RPS.
  • Загрузка CPU на воркерах Nginx упала на 35% благодаря отсутствию постоянного TCP 3-way handshake.