...

Optymalna konfiguracja funkcji „Upstream Keepalive” w NGINX w celu uzyskania maksymalnej wydajności jako serwer proxy odwrotny

Konfiguruję funkcję „Upstream Keepalive” w NGINX tak, aby serwer proxy odwrotny nawiązywał mniej połączeń, zapewniał mniejsze opóźnienia i niezawodnie radził sobie ze szczytami obciążenia. W tym celu dostosowuję Wielkość basenu, limity czasowe i nagłówki w sposób ukierunkowany, tak aby połączenia były ponownie wykorzystywane, a ścieżka danych pozostawała zoptymalizowana.

Punkty centralne

  • HTTP/1.1 wymusić i oczyścić nagłówki połączenia
  • keepalive Dopasować rozmiar do każdego pracownika
  • Limity czasu dostosować do wartości backendu
  • Żądania/Połączenie ograniczać i poddawać recyklingowi
  • Monitoring w odniesieniu do szybkości połączenia i opóźnienia

Dlaczego funkcja Upstream Keepalive radykalnie zmniejsza obciążenie związane z nawiązywaniem połączeń

Bez ponownego wykorzystania NGINX otwiera nowe połączenie z backendem dla każdego żądania, co wiąże się z dodatkowymi procedurami uzgadniania połączenia, większym zużyciem cykli procesora oraz dodatkowymi zasobami jądra; właśnie w tym miejscu wkracza Keepalive . Ustawiam NGINX tak, aby buforował już utworzone, obecnie nieaktywne gniazda i wykorzystywał je do kolejnych żądań, co w wymierny sposób skraca czas nawiązywania połączeń. Zmniejsza to liczbę połączeń na sekundę, ogranicza szczyty zaległości i spowalnia zmiany kontekstu w systemie operacyjnym. Szczególnie w przypadku połączeń TLS z backendem odczuwalnie oszczędzam czas dzięki ponownemu wykorzystaniu sesji. Dzięki temu łańcuch odpowiedzi pozostaje stabilny nawet przy wysokiej przepustowości niezawodny i działa płynnie.

Podstawowa zasada i dyrektywa „keepalive” w upstreemie

Dyrektywa keepalive W bloku upstream ogranicza liczbę buforowanych, nieaktywnych połączeń z serwerem backendowym na każdy proces roboczy. Limit ten nie ma charakteru globalnego, lecz dotyczy ściśle każdego procesu roboczego, dlatego zawsze zwracam uwagę na liczbę procesów roboczych. Gdy pula jest pełna, NGINX zamyka najpierw połączenie pozostające bezczynne najdłużej, aby zwolnić miejsce dla nowych gniazd. Aby umożliwić ponowne wykorzystanie, strona proxy wymaga protokołu HTTP/1.1 oraz zneutralizowanego nagłówka Connection. Bez tych warunków pula pozostaje pusta, mimo że w ustawieniach upstream ustawiłem opcję „keepalive“, co wielu administratorów na początku zaskoczony.

upstream backend_pool {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    keepalive 32; # połączeń bezczynnych na pracownika
    keepalive_requests 1000;   # recykling po N żądaniach
    keepalive_timeout 60s;     # czas trwania bezczynności
}

server {
    listen 80;
    location / {
 proxy_pass http://backend_pool;
 proxy_http_version 1.1;
 proxy_set_header Connection "";
    }
}

Wymagane dyrektywy w bloku „Location”: HTTP/1.1 i kontrola nagłówków

W ścieżce proxy wymuszam na NGINX użycie protokołu HTTP/1.1, ponieważ funkcja Keepalive nie działa poprawnie z protokołem HTTP/1.0, co powoduje niepotrzebne rozłączanie połączeń; dyrektywa proxy_http_version W związku z tym 1.1 jest obowiązkowe. Dodatkowo usuwam nagłówek „Connection“ z typowych żądań, aby backend nie otrzymywał polecenia „close“. W przypadku aktualizacji, takich jak WebSockets, za pomocą mapy celowo ustawiam „Connection: Upgrade”, nie zakłócając przy tym normalnego ponownego wykorzystania. Dzięki temu polityka połączeń pozostaje spójna i niezależna od nagłówków klienta. Właśnie ta niewielka zmiana zapobiega wielu trudnym do uchwycenia Obrazy błędów.

location / {
    proxy_pass http://backend_pool;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    "" "";
}

Precyzyjna regulacja: właściwy dobór wartości keepalive_requests i keepalive_timeout

Za pomocą dwóch śrub regulacyjnych kontroluję trwałość i odnawianie połączeń, aby basen pozostawał w dobrym stanie i nie przeszkadzały żadne opuszczone gniazda; są to keepalive_requests oraz keepalive_timeout. Po N żądaniach NGINX celowo zamyka połączenie i w razie potrzeby nawiązuje je ponownie, co łagodzi skutki starzenia się sieci. Limit czasu bezczynności ustalam raczej na niskim poziomie, zazwyczaj między 30 a 120 sekund, aby serwery backendowe nie przerywały połączenia przedwcześnie. Ważne jest odpowiednie dostrojenie: wartość w NGINX nigdy nie może przekraczać limitu czasu serwerów aplikacji, w przeciwnym razie będzie dochodziło do częstych resetów połączeń. Osoby pragnące pogłębić swoją wiedzę na ten temat znajdą praktyczne wskazówki w artykule Limit czasu keepalive, który wyjaśnia typowe wartości i zależności.

Aby ułatwić orientację, przedstawiam typowe wartości początkowe wraz z ich przeznaczeniem w przejrzystej Tabela. Wartości orientacyjne służą jako punkt wyjścia i po monitorowaniu często okazują się nieco wyższe lub niższe. Zbyt krótki okres powoduje niepotrzebne ponowne tworzenie połączeń, natomiast zbyt długi utrzymuje stare połączenia. Liczba żądań na połączenie chroni mnie przed wartościami odstającymi, nie opróżniając przy tym puli. Dzięki tym kluczowym danym bardzo szybko podejmuję skuteczne Ustawienia domyślne.

Parametry Cel wartość orientacyjna Wskazówka dotycząca tuningu
keepalive Rozmiar puli stanów bezczynności na jednego pracownika 32-64 Dostosować do równoczesnego obciążenia na pracownika
keepalive_requests Maksymalna liczba żądań na połączenie 500–1000 W przypadku długich transmisji należy ustawić nieco wyższą wartość
keepalive_timeout Maksymalny czas bezczynności na połączenie lata 60. Krótszy lub taki sam czas wygaśnięcia bezczynności backendu

Określenie wielkości puli na podstawie liczby jednoczesnych połączeń

Wielkość puli nie wybieram na podstawie liczby żądań na sekundę, lecz na podstawie Współbieżność na pracownika. Najpierw obliczam średnią i maksymalną liczbę równoległych żądań do backendu. Następnie dzielę te liczby przez liczbę pracowników NGINX i zaokrąglam w górę. Przy 200 równoczesnych żądaniach i czterech procesach pracowniczych otrzymuję wynik około 50 na proces, co oznacza, że wartość początkowa keepalive 64 jest odpowiednia. W ten sposób zapewniam dostępność gniazd bez niepotrzebnego otwierania zbyt wielu Połączenia wiązać.

Świadome wykorzystywanie szczególnych cech nowszych wersji NGINX

Najnowsze wersje często domyślnie zezwalają na ponowne wykorzystanie, ale nakładają raczej konserwatywne ograniczenia; mimo to wpisuję te wartości wyraźnie . Zapewnia to powtarzalność, ułatwia dostosowywanie i zapobiega nieoczekiwanym sytuacjom po aktualizacji. Za pomocą parametru „local“ mogę opcjonalnie ograniczyć ponowne wykorzystanie do jednej lokalizacji, jeśli profile bezpieczeństwa lub zasady dotyczące nagłówków się różnią. W ten sposób rozdzielenie pozostaje wyraźne, bez utraty globalnych korzyści płynących z ponownego wykorzystania. Dzięki jasno określonym wartościom dokumentuję zamierzenia i oszczędzam czas w przyszłości Czas analizy.

Monitorowanie i wskaźniki: czy ta konfiguracja naprawdę działa?

Najpierw sprawdzam liczbę nowych połączeń z backendem na sekundę; wyraźny spadek wskazuje na skuteczność Ponowne użycie. Następnie obserwuję parametr `upstream_connect_time`, którego wartość w przypadku trafień w puli jest bliska zeru. Błędy w logach, zwłaszcza resetowanie połączeń, wskazują na limity czasowe leżące u podstaw wartości backendu. Ponadto koreluję obciążenie procesora backendu i opóźnienia z odsetkiem ponownie wykorzystywanych połączeń. Aby lepiej zrozumieć Ponowne wykorzystanie połączeń Pomocne są przykłady ilustrujące te efekty w różnych schematach obciążenia.

Szybkie wyeliminowanie typowych źródeł błędów

Jeśli brakuje protokołu HTTP/1.1 w backendzie, połączenia są krótkotrwałe, niezależnie od tego, jak wysoko ustawię keepalive ustawiam. Jeśli klient wyśle komunikat „Connection: close“, a ja przekażę ten nagłówek bez filtrowania, backend zamknie każde połączenie bezpośrednio po wysłaniu odpowiedzi. Jeśli limity czasu bezczynności nie są zsynchronizowane, strona aplikacji jako pierwsza zakończy połączenie, a NGINX otrzyma reset przy następnym żądaniu. Zbyt duża pula utrzymuje zbyt wiele otwartych gniazd, marnując pamięć operacyjną i porty. Podczas każdej analizy sprawdzam te cztery punkty jako Po pierwsze, ponieważ wyjaśniają one 90 % wszystkich problemów.

Przykład praktyczny: Konfiguracja referencyjna zapewniająca wysoką przepustowość

Wystarczy kilka poleceń, by przywrócić mocno obciążony serwer proxy do stanu, w którym działa szybko i niezawodnie, oraz zapewnić prawidłowe przekazywanie nagłówków; poniższy wzór sprawdził się w praktyce i można go łatwo dostosowanie. Ustawiam keepalive na 64, ograniczam liczbę żądań na połączenie do 1000 i utrzymuję 60-sekundowy czas bezczynności. Ponadto poprawnie przekazuję informacje o hoście i przekazanych danych, aby serwery backendowe mogły stosować logikę i ograniczenia przepustowości. Takie połączenie odciąża procesor, skraca czasy odpowiedzi i pozwala spokojniej radzić sobie ze szczytami obciążenia. Właśnie w ten sposób osiągam dobrze przewidywalną Wydajność.

upstream app_backend {
    server 10.0.1.10:3000 max_fails=2 fail_timeout=30s;
    server 10.0.1.11:3000 max_fails=2 fail_timeout=30s;

    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name example.com;

    location / {
 proxy_pass http://app_backend;
 proxy_http_version 1.1;
 proxy_set_header Connection "";
 proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Środowiska hostingowe i aspekty operacyjne, które naprawdę mają znaczenie

Często umieszczam NGINX przed usługami PHP-FPM, Node.js lub Java i dbam o to, by opóźnienia sieciowe pozostawały na niskim poziomie, a limity czasu backendu były spójne; zapewnia to Możliwość planowania. Solidna konfiguracja sieciowa jądra z odpowiednimi limitami gniazd zapobiega kolizjom między wieloma otwartymi połączeniami. Równomierny przydział zasobów procesora oraz szybkie ścieżki dostępu do pamięci masowej pomagają serwerom backendowym utrzymać krótkie czasy odpowiedzi. Ponadto dbam o wersjonowanie konfiguracji, aby zmiany pozostawały przejrzyste. Dzięki tej dyscyplinie system działa stabilnie nawet podczas szczytów ruchu reagowalny.

Najlepsze praktyki dla bieżących operacji

Zaczynam od keepalive 32–64, 500–1000 żądań na połączenie i 60 sekund czasu bezczynności, a następnie systematycznie dokonuję pomiarów i dostosowuję wartości; to pozwala szybko sukcesy. Każdą zmianę monitoruję za pomocą wskaźników dotyczących szybkości połączeń, opóźnień i wzorców błędów, aż krzywe staną się bardziej stabilne. Wielkość puli dostosowuję do liczby jednoczesnych żądań, a nie do surowej przepustowości na sekundę. Limity czasu nigdy nie są dłuższe niż ich odpowiedniki w stosie backendowym, w przeciwnym razie grożą sporadyczne resetowania. Osoby, które chcą bardziej precyzyjnie dostosować parametry, znajdą wskazówki dotyczące precyzyjnego dostrajania pod adresem Optymalizacja żądań keepalive, co sprawia, że recykling jest łatwy do opanowania.

Dostosowanie limitów czasu serwera proxy i funkcji TCP Keepalive

Oprócz samych parametrów keepalive precyzyjnie dostosowuję limity czasu transmisji. Ta trójca, składająca się z proxy_connect_timeout, proxy_send_timeout oraz proxy_read_timeout określa, jak cierpliwy jest NGINX podczas nawiązywania połączeń, wysyłania i odbierania danych. Nigdy nie ustawiam tych wartości na poziomie wyższym niż ich odpowiedniki w backendzie, lecz nieco poniżej, aby błędy były widoczne na wczesnym etapie i nie eskalowały po stronie aplikacji. Dodatkowo włączam proxy_socket_keepalive, aby system operacyjny wysyłał w regularnych odstępach czasu sygnały aktywności dotyczące nieaktywnych gniazd i wykrywał połączenia półotwarte. Zapobiega to pozostawaniu martwych połączeń w puli i powodowaniu skoków opóźnień przy następnym żądaniu.

server {
    listen 80;

    location / {
 proxy_pass http://backend_pool;

 proxy_connect_timeout 3s;   # szybkie zakończenie, jeśli nie można nawiązać połączenia
 proxy_send_timeout    30s;  # Zapis do backendu
 proxy_read_timeout    30s;  # Odpowiedzi z backendu
 proxy_socket_keepalive on;  # Włącz keepalive protokołu TCP systemu operacyjnego
    }
}

W przypadku długotrwałych strumieni (np. SSE lub WebSockets) zwiększam wyłącznie limit czasu odczytu, podczas gdy połączenie pozostaje aktywne. W ten sposób szybko reaguję na uszkodzone cele, ale nie zakłócam przepływu prawidłowych, długich odpowiedzi.

Planowanie zasobów: worker_connections, FD i porty efemeryczne

Czysta pula keepalive nie ma sensu, jeśli wyczerpią się limity deskryptorów plików lub zakresy portów. Dlatego planuję worker_connections oraz worker_rlimit_nofile z rezerwą. Z grubsza obliczam: otwarte FD ≈ (jednoczesne połączenia klienckie + jednoczesne połączenia z backendem + zgrupowane gniazda w stanie bezczynności) na każdy proces roboczy. Jeśli wykorzystuję kilka strumieni upstream z pulami, zapotrzebowanie się zwielokrotnia. Zwracam również uwagę na zakres portów efemerycznych w systemie, ponieważ NGINX działa jako klient TCP w kierunku backendu i gromadzi stany TIME_WAIT.

worker_processes auto;
worker_rlimit_nofile 131072;

events {
    worker_connections 8192;
}
Przykłady dla systemu Linux # (sysctl):
net.core.somaxconn = 4096
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15

Postępuję ostrożnie: nie wyłączam TIME_WAIT w sposób agresywny, lecz zmniejszam częstotliwość połączeń za pomocą keepalive. Dzięki temu parametry jądra pozostają na niekrytycznym poziomie, a zachowanie systemu jest przewidywalne.

Strefy upstream, strategia równoważenia obciążenia i rotacja DNS

W przypadku wielu procesów dzielę stan balancera za pomocą strefa, aby awarie i obciążenia pozostawały spójne. Gniazda Keepalive nadal pozostają przypisane do poszczególnych workerów, ale ich rozkład staje się bardziej równomierny. W przypadku dynamicznych backendów, które zmieniają lokalizację za pośrednictwem DNS, ustawiam „rozwiązać“ w wierszach serwera i zdefiniuj resolver. Ważne: Gdy adresy IP ulegają rotacji, pula nie odzyskuje od razu wszystkich starych gniazd; dlatego uważam, że keepalive_requests oraz realistyczne terminy, aby zmiany zaczęły szybko przynosić efekty.

upstream backend_pool {
    zone backend_zone 128k;  # dzieli stan balanceru
    least_conn; # sprawiedliwy rozkład przy długich żądaniach

 server app-1.internal:8080 resolve;
    server app-2.internal:8080 resolve;

 keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

resolver 10.0.0.2 valid=30s;
resolver_timeout 5s;

proxy_next_upstream error timeout http_502 http_504;
proxy_next_upstream_tries 2;  # — niewiele, ukierunkowanych powtórzeń

W przypadku sesji powiązanych z konkretnym węzłem zaplecza (np. w trybie sticky-state) łączę ponowne wykorzystanie z ip_hash lub zewnętrznego mechanizmu sesji. Zapobiega to zakłócaniu spójności sesji przez pulę połączeń.

TLS w backendzie: SNI, ponowne wykorzystanie sesji i szyfry

Im częściej wykorzystuje się TLS w ścieżce backendowej, tym większe znaczenie ma funkcja Keepalive. Włączam SNI, ustalam oczekiwaną nazwę i dbam o ponowne wykorzystanie sesji TLS. Obniża to koszty uzgadniania połączenia i wygładza skoki opóźnień. Zestawy szyfrów i protokoły wybieram w sposób restrykcyjny, nie blokując przy tym starszych backendów. Podczas weryfikacji certyfikatu (opcjonalnie) łańcuch zaufania musi być kompletny, w przeciwnym razie połączenia będą sporadycznie się rozłączać.

upstream https_backend {
    server backend.example.local:443;
    keepalive 32;
}

server {
    listen 443 ssl;

 location / {
 proxy_pass https://https_backend;
 proxy_http_version 1.1;
 proxy_set_header Connection "";

        proxy_ssl_server_name on;
 proxy_ssl_name backend.example.local;
 proxy_ssl_session_reuse on;
 proxy_ssl_protocols TLSv1.2 TLSv1.3;
        proxy_ssl_ciphers HIGH:!aNULL:!MD5;
 # opcjonalnie: proxy_ssl_verify on;
 # opcjonalnie: proxy_ssl_trusted_certificate /etc/nginx/ca.pem;
    }
}

Jeśli sam zarządzam stroną backendową, włączam tam sesyjne tokeny lub pamięć podręczną i sprawdzam na podstawie wskaźników, czy wskaźniki wznowienia rosną. W połączeniu z funkcją Keepalive uzyskuję w ten sposób stale niskie czasy nawiązywania połączeń i uzgadniania.

Przypadki szczególne: gRPC, WebSockets i uwierzytelnianie oparte na połączeniu

Na stronie gRPC NGINX działa w trybie upstream przez HTTP/2. W tym przypadku najlepsze wyniki często zapewniają nieliczne, długotrwałe połączenia z dużą liczbą strumieni; pula pozostaje niewielka, ale stabilna. W przypadku WebSockets Ustawiam długie limity czasu odczytu i zachowuję logikę obsługi nagłówków z rozwiązania „map”, aby połączenia aktualizacyjne nie zostały przypadkowo zamknięte. NTLM lub inne metody uwierzytelniania oparte na połączeniu wymagają przypisania stałego połączenia (connection pinning); wyodrębniam takie ścieżki do osobnych lokalizacji i ograniczam tam pulowanie lub ponowne wykorzystywanie, aby uniknąć pomylenia procedur bezpieczeństwa między klientami.

Przykład # z gRPC
location /grpc.Service/ {
    grpc_pass grpc://backend_pool;
    grpc_read_timeout 300s;  zezwól na długie strumienie #
}

Kluczowe znaczenie ma ustalenie spójnej polityki połączeń dla każdej ścieżki oraz stosowanie funkcji Keepalive na szeroką skalę wyłącznie tam, gdzie nie ma to znaczenia semantycznego.

Mierzalność w praktyce: logi dostępu z czasami transmisji w kierunku upstream

Rozszerzam dziennik Access o wskaźniki upstream. Dzięki temu od razu widzę, czy odpowiedź pochodziła z gniazda w puli (bardzo krótki czas połączenia) oraz jak często występują błędy backendu. Ponadto rejestruję numer połączenia oraz liczbę żądań przesłanych przez bieżące połączenie klienckie, aby zidentyfikować powiązania.

log_format upstream_timing '$remote_addr - $host "$request" '
 'up=$upstream_addr '
 'sc=$status usc=$upstream_status '
                           'cc=$connection cr=$connection_requests '
 'tc=$upstream_connect_time '
 'th=$upstream_header_time '
 'tr=$upstream_response_time';

access_log /var/log/nginx/access_upstream.log upstream_timing;

Dodatkowo korzystam z punktów końcowych stanu oraz statystyk gniazd systemu operacyjnego. Prawidłowy stan charakteryzuje się: malejącą liczbą połączeń z serwerem zaplecza, krótszym czasem upstream_connect_time, stabilnymi czasami odpowiedzi oraz rzadkimi resetami połączeń. Odchylenia od normy niemal zawsze wskazują na niedostosowane limity czasowe lub zbyt małe/zbyt duże pule.

Strategia wdrażania i dostosowywanie przy ograniczonym ryzyku

Postępuję iteracyjnie: małe kroki, pomiary, dostosowywanie. Najpierw umiarkowanie włączam keepalive, a następnie dostosowuję limity czasu i liczbę żądań na połączenie. Zmiany wprowadzam poprzez odświeżenie strony, bez rozłączania aktywnych połączeń. Dzięki temu ryzyko pozostaje niewielkie, a efekty można jednoznacznie przypisać.

# Sprawdź zmiany i załaduj je bez przestoju
nginx -t && nginx -s reload

Jeśli obsługuję kilka źródeł danych, optymalizuję je po kolei, zaczynając od ścieżki o największym znaczeniu. Każdemu etapowi przypisuję okno obserwacyjne, aby wyraźnie wyodrębnić wzorce w wskaźnikach. Dopiero potem zwiększam lub zmniejszam wartości.

Zwięzłe podsumowanie dotyczące Twojego serwera proxy odwrotnego

Stawiam na HTTP/1.1, usuwam nagłówek „Connection” i dobieram wielkość puli na podstawie liczby jednoczesnych żądań, a nie na podstawie RPS; to zapewnia Wydajność. Dzięki parametrom `keepalive_requests` i `keepalive_timeout` utrzymuję połączenia w aktywnym stanie i unikam nieoczekiwanych problemów związanych z nieaktualnymi gniazdami. Monitorowanie pokazuje, czy wartość `upstream_connect_time` dąży do zera oraz czy zmniejsza się liczba połączeń z backendem. W przypadku błędów najpierw sprawdzam wersję protokołu, przekazywanie nagłówków, limity czasowe i rozmiar puli. Dzięki temu Twój serwer proxy NGINX będzie działał stabilnie nawet przy dużym obciążeniu responsywny i przewidywalne.

Artykuły bieżące

Nowoczesne serwery o zoptymalizowanej wydajności wygasania kluczy Redis
Bazy danych

Analiza i optymalizacja wydajności wygasania kluczy w Redis

Dowiedz się, jak zoptymalizować wydajność wygasania kluczy w Redis dzięki odpowiednim strategiom TTL, zasadom usuwania danych oraz ukierunkowanemu monitorowaniu, a także jak zapewnić stabilność pamięci podręcznej. Temat: Wygasanie kluczy w Redis.