Celowo skaluję liczbę procesów roboczych serwera nginx, aby obsłużyć tysiące jednoczesnych żądań przy niewielkim Opóźnienie obsługiwać. Kluczem jest odpowiednio dobrana kombinacja parametrów worker_processes, worker_connections, deskryptorów plików oraz Wydarzenia.
Punkty centralne
- Pojemność = worker_processes × worker_connections; w przypadku odwrotnego proxy często wynika to z połączeń klienckich i upstreamowych podwojono.
- Deskryptory plików (worker_rlimit_nofile, ulimit) dostosowane do przewidywanego obciążenia połączeniami winda.
- Wydarzenia-Blok z epoll, multi_accept i zaległościami jądra przy dużym obciążeniu przycinać.
- Monitoring za pomocą funkcji `stub_status` i testów obciążeniowych dla iteracyjnych Personalizacja.
- Skalowanie łączenie w pionie i w poziomie, konfiguracja odłączenie.
Architektura NGINX: serwer główny, serwery robocze i zdarzenia
NGINX opiera się na procesie głównym, który uruchamia kilka procesów roboczych i efektywnie zarządza nimi za pomocą Wydarzenia obsługuje. Zamiast przetwarzać wątki na żądanie, każdy proces roboczy obsługuje wiele połączeń w trybie nieblokującym, wykorzystując model oparty na zdarzeniach o niskim Nad głową. Ustawiam opcję worker_processes na „auto”, aby NGINX wykorzystywał rdzenie procesora i przydzielał każdemu procesowi własnego pracownika. W ten sposób lepiej rozdzielam przychodzące połączenia i utrzymuję opóźnienie na niskim poziomie nawet przy szczytowym obciążeniu niski. Aby uzyskać bardziej szczegółowe informacje na temat planowania procesów, odsyłam do Optymalizacja procesów typu „worker”, ponieważ prawidłowe rozłożenie zadań na wiele procesów decyduje o realnej przepustowości połączeń. Kluczowe znaczenie ma to, aby liczba połączeń worker_connections na każdy procesor roboczy była odpowiednio dobrana, tak aby ich pomnożenie przez liczbę procesów dało oczekiwaną Obciążenie szczytowe obejmuje.
Wzór na przepustowość: worker_processes × worker_connections
Przybliżoną przepustowość obliczam jako iloczyn worker_processes i worker_connections, przy czym żądania obsługiwane przez serwer proxy często zajmują dwa połączenia na jedno wejście użytkownika, co w efekcie zmniejsza tę liczbę o połowę Puszka. Wiele standardowych instalacji rozpoczyna pracę z 512 połączeniami na każdy proces roboczy, co w przypadku obciążeń produkcyjnych często okazuje się niewystarczające jest. Praktyczne wartości początkowe wynoszą zazwyczaj od 1024 do 4096 i zależą od profilu ruchu oraz sprzętu. Planuję z uwzględnieniem rezerwy, czyli co najmniej dwukrotności zmierzonego obciążenia szczytowego, aby bezpiecznie obsłużyć skoki obciążenia złagodzić. Ważna pozostaje weryfikacja za pomocą testów i wskaźników z rzeczywistego środowiska, aby liczby nie stały się jedynie teoretyczną zabawą stać się.
| Scenariusz | worker_processes | worker_connections | Teoretycznie maks. | Efektywne (proxy) | FD na pracownika |
|---|---|---|---|---|---|
| Mała strona | 2 | 1024 | 2048 | ~1024 | ≥1024 |
| API – średnie obciążenie | 4 | 2048 | 8192 | ~4096 | ≥2048 |
| Godziny szczytu w sklepie | 8 | 4096 | 32768 | ~16384 | ≥4096 |
HTTP/1.1, HTTP/2 i TLS: wpływ na procesy robocze i opóźnienia
Protokoły określają profil połączenia. W przypadku HTTP/1.1 często obserwuję wiele równoczesnych połączeń TCP na jednego klienta, podczas gdy HTTP/2 ogranicza ich liczbę do kilku, za to bardziej obciążonych strumieni pakiety. Pozwala to zaoszczędzić deskryptory plików, ale przenosi obciążenie na bufory i mechanizmy ustalania priorytetów. W przypadku TLS zwracam uwagę na ponowne wykorzystanie sesji, aby uniknąć kosztownych procedur uzgadniania przy każdym żądaniu zwolnić. Wspólna pamięć podręczna sesji oraz odpowiednie limity czasu zmniejszają skoki obciążenia procesora. Ponadto nie ustawiam wartości keepalive_requests zbyt nisko, aby połączenia długotrwałe mogły w pełni wykorzystać swoje zalety play out. W przypadku protokołu HTTP/2 zakładam wyższą współbieżność na każde połączenie i dbam o to, by bufory wysyłania i odbierania były wystarczająco duże, bez zajmowania pamięci marnować. W przypadku ruchu mieszanego planuję ostrożnie i weryfikuję skutki dla poszczególnych wariantów protokołu w Test.
Prawidłowe ustawienie deskryptorów plików i ulimit
Każde połączenie wymaga co najmniej jednego deskryptora pliku, a w przypadku odwrotnego proxy często dwóch, dlatego niskie wartości ulimit mogą stanowić poważny problem Granice ustawić. Zwiększam wartość worker_rlimit_nofile tak, aby można było zrealizować ustawienie worker_processes × worker_connections oraz zapewnić rezerwy na logi, gniazda i pamięci podręczne. W skali całego systemu dostosowuję pliki limits.conf i fs.file-max, aby system operacyjny zezwolił na planowaną liczbę otwartych plików i nie zakończył działania przedwcześnie hamulce. Za pomocą polecenia `ulimit -n` oraz parametrów Systemd (LimitNOFILE) sprawdzam, czy konfiguracja jest trwała i zgodna z NGINX. Kto zignoruje tę opcję, ten mimo wysokiej wartości `worker_connections` nagle doświadczy odrzucanych połączeń i rosnącego Opóźnienia.
Precyzyjne dostosowanie bloku zdarzeń: epoll, multi_accept, backlogi
W systemie Linux korzystam z epoll, ponieważ mechanizm ten pozwala efektywnie obsługiwać dużą liczbę połączeń za pomocą komunikacji asynchronicznej Wydarzenia obsługuje. Przy ustawieniu multi_accept na „on” pracownik akceptuje kilka nowych połączeń na jedno zdarzenie, co pozwala wyrównać szczyty obciążenia i opóźnienia w przyjmowaniu połączeń obniżki. Odpowiednio zwiększam parametry jądra, takie jak net.core.somaxconn i net.ipv4.tcp_max_syn_backlog, aby kolejki Accept nie uległy przepełnieniu podczas natłoku połączeń. Optymalizacje stanu TIME_WAIT, takie jak tcp_tw_reuse, zmniejszają wąskie gardła portów i utrzymują krzywą przepustowości wysoki. Aby uzyskać bardziej szczegółowe informacje na temat wielowątkowości i kolejek, warto zajrzeć do Optymalizacja puli wątków, nawet jeśli NGINX działa przede wszystkim w oparciu o zdarzenia, dzięki czemu jest bardzo oszczędny skalowany.
Prawidłowe przydzielanie gniazd listowych: reuseport, backlog i accept_mutex
W przypadku bardzo dużej liczby jednoczesnych połączeń aktywnie skaluję ścieżkę obsługi. Za pomocą reuseport Każdy proces roboczy otrzymuje własny gniazdo nasłuchujące; dzięki temu eliminuje się konkurencja przy operacji `accept`, a obciążenie jest równomiernie rozkładane na wszystkie rdzenie. Wyraźnie ustawiam wielkość kolejki nasłuchującej, aby złagodzić skutki krótkotrwałych szczytów obciążenia. W tej konfiguracji accept_mutex nie jest już potrzebny. Natomiast bez opcji reuseport accept_mutex może pomóc, aby ograniczyć efekt stada w module Accept. Ważne: wielkości backlogu w NGINX i jądrze (somaxconn) powinny pasują do siebie, w przeciwnym razie efekt zostanie zniweczony.
events {
use epoll;
worker_connections 4096;
# accept_mutex on; # z opcją reuseport zazwyczaj nie jest to konieczne
}
server {
listen 443 ssl http2 reuseport backlog=65535;
# ...
}
Dodatkowo, w razie potrzeby przypisuję procesy robocze do rdzeni procesora (worker_cpu_affinity), aby zapewnić stabilność linii pamięci podręcznej i obciążenia IRQ. W środowiskach o silnej architekturze NUMA zmniejsza to niepotrzebne Ruch poprzeczny w pamięci.
Proxy odwrotne, serwery źródłowe i Keep-Alive
Jako serwer proxy odwrotny NGINX często utrzymuje dwa połączenia na każde żądanie: jedno z klientem i jedno z serwerem zaplecza, co pozwala na realistyczne planowanie wydajności podwójny liczy się. Włączam funkcję Keep-Alive w rozsądny sposób, aby połączenia upstreamowe mogły być ponownie wykorzystywane, a obciążenie na żądanie spadki. W ten sposób zmniejszam obciążenie serwera PHP-FPM, serwera aplikacji lub mikrousług i zyskuję wolne sloty dla nowych sesji użytkowników. Równowaga między limitami czasu, czasem bezczynności i ponownym wykorzystaniem decyduje o tym, jak sprawnie odbywa się recykling połączeń stać się. Jeśli ktoś chciałby zapoznać się z podstawowymi informacjami na ten temat, znajdzie je w Trwałe połączenia praktyczne wskazówki dotyczące wykorzystania przepustowości i poprawy wydajności sieciUżyj.
Pule upstream, limity czasu i ponowne próby
Aby procesy Worker nie musiały czekać na powolne serwery zaplecza, stosuję krótkie limity czasu i odpowiednio dobrane ponowne próby. Pule keepalive upstream utrzymuję na wystarczająco dużym poziomie, aby połączenia pozostawały aktywne, ale nie na tyle dużym, by nieaktywne deskriptory plików zajmowały pamięć i sloty wiązanie. Ograniczam liczbę ponownych prób do kilku i przełączam się tylko w przypadku wyraźnych błędów transmisji – w ten sposób zapobiegam efektowi „thundering herd” w przypadku krótkotrwałych awarii backendu.
upstream app_backend {
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
keepalive 64; Połączenia upstream typu # z możliwością ponownego wykorzystania
}
server {
location / {
proxy_pass http://app_backend;
proxy_connect_timeout 2s;
proxy_read_timeout 15s;
proxy_send_timeout 15s;
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
}
}
Jednocześnie dostosowuję parametry Keep-Alive (limity czasu, liczba żądań na połączenie), aby szybko zwolnić zasoby zajmowane przez rzadko aktywnych klientów udostępnić.
Rozsądne planowanie skalowania: łączenie skalowania pionowego i poziomego
W przypadku dużego natężenia ruchu stosuję jednocześnie skalowanie pionowe i poziome w Rozważ. Skaluję w pionie poprzez zwiększenie liczby rdzeni procesora, pamięci RAM, szybkich dysków SSD oraz zoptymalizowaną konfigurację sieciową, aby każdy procesor pracował płynnie prace. Rozbudowę horyzontalną realizuję za pomocą bezstanowych węzłów NGINX, centralnie zarządzanej konfiguracji i rozproszonego rejestrowania, dzięki czemu całkowita pojemność rośnie liniowo rośnie. Lokalne pamięci podręczne i jasno określone zasady za pośrednictwem Maps lub API umożliwiają szybkie wdrażanie zmian. Takie rozdzielenie ogranicza skutki uboczne i pomaga dostosowywać się do nowych wzorców ruchu bez konieczności wprowadzania zmian w każdym węźle obsługiwać.
Perspektywa hostingu: opóźnienia, wskaźniki błędów i wrażenia użytkownika
Zbyt mała liczba połączeń typu `worker_connections` powoduje odrzucanie połączeń, przekroczenia limitów czasu i pogorszenie Doświadczenie użytkownika. Aplikacje dynamiczne, takie jak systemy CMS czy sklepy internetowe, od razu to odczuwają, ponieważ wywołanie strony generuje wiele zapytań do zaplecza, a sloty są wykorzystywane szybciej krótki . Dlatego zaczynam od umiarkowanych wartości, takich jak 1024 lub 2048 na każdy proces roboczy, i stopniowo je zwiększam na podstawie rzeczywistych pomiarów. Równolegle dbam o wydajność usług nadrzędnych i zapewniam wystarczającą liczbę deskryptorów plików, aby nie dochodziło do sztucznych Ograniczenia wykorzystać. Testy porównawcze pokazują, że starannie dostosowane platformy zapewniają w tym zakresie rzeczywiste korzyści i niezawodnie obsługują szczytowe natężenie ruchu przechwycenie.
Pamięć, buforowanie i ścieżki wejścia/wyjścia
Każde połączenie zajmuje pamięć operacyjną na metadane i bufory. Dostosowuję wartości proxy_buffers, client_body_buffer_size i large_client_header_buffers tak, aby pomieścić typowe żądania, nie rezerwując przy tym zbyt dużej ilości pamięci RAM na wypadek wartości odstających wiązanie. W przypadku treści statycznych funkcje `sendfile` i `tcp_nopush` przyspieszają dostarczanie danych, natomiast `tcp_nodelay` sprawdza się w przypadku krótkich odpowiedzi, dla których opóźnienie ma kluczowe znaczenie Ważne pozostaje. Jeśli zasoby znajdują się na wolniejszym nośniku, funkcja aio wraz z opcjami threads i thread_pool pomaga złagodzić efekty blokowania. Dzięki open_file_cache ograniczam liczbę operacji na plikach i wywołań funkcji stat(), należy jednak pamiętać o dodatkowym zapotrzebowaniu na deskryptory plików (FD). Logi zapisuję z buforowaniem (access_log … buffer=… flush=…), aby szczytowe obciążenia we/wy nie wpływały na czasy odpowiedzi wpływać na.
Równowaga między bezpieczeństwem a wydajnością protokołu TLS
Procesy uzgadniania TLS są bardzo obciążające dla procesora. Łączę ponowne wykorzystanie sesji z umiarkowanymi parametrami kluczy i włączam optymalizacje warstwowe, takie jak pamięci podręczne sesji i bilety, o ile jest to uzasadnione z operacyjnego punktu widzenia dopasowanie. Optymalny kompromis między bezpieczeństwem a wydajnością pozwala utrzymać stabilne opóźnienia bez uszczerbku dla jakości szyfrowania. Przy większym obciążeniu obserwuję osobno 95. i 99. percentyl, ponieważ w przeciwnym razie szczytowe wartości TLS pozostawałyby ukryte za wartościami średnimi ukrycie. Protokół HTTP/2 zmniejsza liczbę połączeń, wymaga jednak staranności w zakresie kontroli przepływu danych i kompresji nagłówków, aby utrzymać pod kontrolą obciążenie procesora i pamięci zachować.
Odporność w warunkach przeciążenia: granice i łagodne odciążanie
Aby zachować poufność, konieczne jest celowe Kształtowanie niezbędne w okresach szczytowego obciążenia. Za pomocą parametru `limit_conn` ograniczam liczbę równoległych połączeń na klucz (np. adres IP lub sesja), a parametr `limit_req` ogranicza obciążenie impulsowe i chroni serwery zaplecza przed synchronicznymi Sztormy. Punkty krytyczne izoluję, stosując bardziej rygorystyczne zasady niż w przypadku zasobów statycznych. Jeśli obciążenie wzrośnie nagle, zwracam jasno zdefiniowane kody błędów 429/503 z parametrem „Retry-After”, zamiast równomiernie rozdzielać wszystkie żądania umrzeć z głodu . Zatrzymuję połączenia, które się przedłużają (lingering_close), aby w kontrolowany sposób zwolnić zasoby i zapobiec wzorcom typu Slowloris obalić. To aktywne odciążanie utrzymuje opóźnienie p95/p99 w dopuszczalnym zakresie, nawet jeśli całkowite zapotrzebowanie chwilowo przekracza nominalną przepustowość kłamstwa.
Integracja kontenerów i systemów: znoszenie ograniczeń tam, gdzie się pojawiają
W kontenerach często obowiązują bardziej restrykcyjne ograniczenia. Sprawdzam limity cgroup (CPU, RAM), odpowiednio ustawiam opcję ulimit -n wewnątrz kontenera i definiuję LimitNOFILE w definicji usługi. Parametry sysctl, takie jak somaxconn i tcp_max_syn_backlog, muszą być ustawione na Gospodarz zaczynają obowiązywać; przestrzenie nazw nie zawsze izolują te ustawienia w sposób przejrzysty. Na platformach orkiestrowanych planuję pojemność na pod/węzeł, przypisuję procesy robocze do przydzielonych rdzeni i dbam o stabilne ścieżki sieciowe (np. bez zbędnych przeskoków NAT), aby krzywa opóźnienia spokój pozostaje. Aktualizacje typu rolling update zabezpieczam za pomocą worker_shutdown_timeout, aby istniejące połączenia zostały poprawnie zamknięte wycofać się.
Monitorowanie i optymalizacja iteracyjna
Bez widoczności działania związane z tuningiem pozostają Ryzyko. Włączam moduł `stub_status` lub jego alternatywy, aby na bieżąco monitorować aktywne połączenia, wskaźniki akceptacji i odrzucenia. W testach obciążeniowych symuluję realistyczne wzorce dostępu i identyfikuję wąskie gardła w kolejkach akceptacji, opóźnieniach upstream lub obciążeniu procesora (CPU) –Nasycenie. Następnie ostrożnie dostosowuję liczbę połączeń worker_connections, liczbę procesów, limity plików oraz parametry TCP i ponownie sprawdzam efekt. Ten cykl zapewnia niezawodne działanie platformy i zapobiega nieoczekiwanym sytuacjom w niekorzystnych Czasy.
Przykładowa konfiguracja i sposób obliczeń
Załóżmy, że w godzinach szczytu spodziewam się 2000 równoczesnych żądań w trakcie przetwarzania i korzystam z serwera proxy odwrotnego – w takim przypadku szacuję z grubsza 4000 slotów połączeń plus Bufor. Jeśli NGINX działa na czterech rdzeniach procesora, ustawiam na początek np. `worker_processes auto` oraz `worker_connections` od 1000 do 2000 na każdy proces roboczy. Limit deskryptorów plików ustalam na wystarczająco wysokim poziomie dla każdego procesu roboczego, aby połączenia, logi i wewnętrzne gniazda miały wystarczająco dużo Miejsce . Blok Events ustawiam na epoll, włączam multi_accept i zwiększam backlogi jądra odpowiednio do mojego szczytowego ruchu. Minimalistyczny fragment może wyglądać tak, a następnie dopracowuję go za pomocą testów wydajnościowych Głosowanie:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
use epoll;
worker_connections 2048;
multi_accept on;
}
http {
keepalive_timeout 65;
sendfile on;
# dodatkowe opcje proxy/pamięci podręcznej ...
}
Dodatkowo wprowadzam optymalizacje list i przepływu danych w górę, aby udoskonalić ścieżkę przyjmowania danych i ścieżkę zaplecza pod obciążeniem:
events {
use epoll;
worker_connections 4096;
# worker_cpu_affinity auto; # w razie potrzeby przypisz rdzenie na stałe
}
http {
# Przykładowe optymalizacje TLS/sesji
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1h;
upstream app_backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 64;
}
server {
listen 443 ssl http2 reuseport backlog=65535;
location / {
proxy_pass http://app_backend;
proxy_connect_timeout 2s;
proxy_read_timeout 15s;
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
}
}
}
W skrócie: konkretne wartości orientacyjne
Dostosowuję liczbę procesów roboczych (worker_processes) do liczby rdzeni procesora, a liczbę połączeń roboczych (worker_connections) zazwyczaj ustawiam na wartość z przedziału od 1024 do 4096. W przypadku serwera proxy odwrotnego planuję dwa połączenia na każde żądanie i zapewniam co najmniej dwukrotny zapas w stosunku do zmierzonej wartości szczytowejObciążenie. Ustawiam wartości `worker_rlimit_nofile` oraz limity systemowe na wystarczająco wysokie, aby wartości z pliku `nginx.conf` pozostały faktycznie użyteczne. Blok `events` ograniczam do `epoll` i `multi_accept`, podczas gdy zaległości jądra radzą sobie z krótkotrwałymi szczytami ruchu amortyzować. Dzięki monitorowaniu i stopniowym dostosowaniom tworzę na tej podstawie niezawodny mechanizm generowania ruchu, który sprawnie obsługuje rosnącą liczbę odwiedzin nośniki.


