...

Configurare in modo ottimale i processi di lavoro di NGINX per ottenere le massime prestazioni

Configurazione NGINX Worker in modo che worker_processes, worker_connections e worker_rlimit_nofile corrispondano esattamente e Epoll funzioni nel ciclo di eventi. In questo modo utilizzo Core della CPU Garantisci l'efficienza, rendi scalabili le connessioni simultanee in modo pianificabile e mantieni basse le latenze nei picchi di carico.

Punti centrali

I seguenti aspetti fondamentali ti forniranno indicazioni immediate per una configurazione robusta dei worker NGINX.

  • processi_lavoratori associarlo al numero di core logici, idealmente con „auto“.
  • connessioni_lavoratore impostare in modo tale che i picchi reali siano coperti senza problemi.
  • rlimit_nofile e aumentare i limiti del sistema operativo in base al volume di traffico.
  • epoll e attivare multi_accept per sfruttare in modo efficiente il ciclo degli eventi.
  • Prove di carico procedere e apportare piccole modifiche graduali.

Architettura NGINX: comprendere i master e i worker

Distinguo i compiti da Maestro e i worker: il master carica le configurazioni, apre i socket e avvia i processi, mentre i worker elaborano le richieste nell’event loop. Ogni worker opera in modo autonomo, reagisce agli eventi ed è in grado di gestire migliaia di connessioni senza causare blocchi. Questo modello dà il meglio di sé quando assegno i core della CPU in modo adeguato e gestisco in modo ottimale il ciclo di eventi tramite epoll. Tengo presente che ogni hop aggiuntivo del proxy consuma risorse di connessione, il che si riflette nei limiti. Chi comprende i ruoli, decide consapevolmente in merito a Risorse e previene tempestivamente le strozzature.

Abbinare correttamente le tre direttive chiave

Considero processi_lavoratori, worker_connections e worker_rlimit_nofile non vanno mai considerati isolatamente, ma come un’unica entità. Il numero totale di connessioni possibili è dato dal numero di worker moltiplicato per le connessioni per worker; da questo calcolo ricavo i limiti per i descrittori di file. Se questi parametri non sono ben coordinati, mi ritrovo a dover affrontare errori del tipo „too many open files“ o a subire timeout severi. Per carichi elevati ho bisogno di una catena ben coordinata: processi sufficienti, connessioni generose, un valore di rlimit_nofile adeguatamente aumentato e parametri del sistema operativo appropriati. In questo modo evito che un valore troppo basso Limite ne ha compromesso l'intera capacità.

worker_processes: scegliere il numero in modo mirato

Ho impostato processi_lavoratori Di norma su „auto“, in modo che NGINX riconosca il numero di core logici della CPU e possa utilizzare ogni singolo core. Un worker per ogni core evita inutili cambi di contesto e distribuisce il carico in modo ottimale, mantenendo prevedibili i tempi di risposta. Su macchine con un numero molto elevato di core, provo intenzionalmente anche un numero inferiore di worker per confrontare gli hit della cache e l’utilizzo dei core. Se le metriche indicano che i core sono sovraccarichi o che aumentano i TLB miss, adeguo gradualmente il numero di worker. Prima misuro, poi modifico: è così che mi assicuro risultati affidabili Risultati.

worker_connections: aumentare le connessioni in modo pianificato

Scelgo il connessioni_lavoratore a seconda del traffico di destinazione e della combinazione di protocolli, spesso a partire da 2048 o 4096. Per le API molto trafficate prendo in considerazione 8192, purché i limiti del sistema operativo e la RAM lo consentano. Verifico ogni aumento con test di carico, poiché le connessioni aperte occupano memoria e influenzano il comportamento upstream. Se prevalgono gli handshake SSL o gli upload di grandi dimensioni, mi baso maggiormente sui profili della CPU e dell’I/O, non solo sul numero di connessioni in sé. In questo modo mi assicuro che il numero definito per ogni worker Capacità rimanga effettivamente utilizzabile.

Sincronizzare worker_rlimit_nofile e i limiti del sistema operativo

Mi assicuro che rlimit_nofile copra almeno la capacità totale teorica e spesso sia configurato con una riserva. Per gli scenari con proxy inverso, calcolo un secondo descrittore per l’upstream per ogni connessione client. Di conseguenza, imposto rlimit_nofile a un valore pari al doppio delle connessioni simultanee previste. Aumento i limiti del kernel e dell’utente (ulimit -n, fs.file-max) in modo tale che NGINX possa effettivamente sfruttarne i valori. Se nel log degli errori compaiono messaggi relativi a file aperti, aumento rapidamente i valori e osservo il Latenza sotto carico, di nuovo.

Blocco "Eventi": utilizzare efficacemente epoll e multi_accept

Nel blocco "Eventi" attivo epoll e imposto multi_accept su „on“, in modo che i worker accettino le connessioni in attesa in un unico ciclo. Epoll riduce l’overhead in presenza di molti socket simultanei e si integra perfettamente con il design non bloccante di NGINX. Queste opzioni danno i loro frutti durante i picchi di traffico, poiché accelerano la fase di accettazione e consentono di passare più rapidamente all’elaborazione vera e propria. Per Linux questa è la mia configurazione standard, che modifico solo in rari casi particolari. Chi desidera approfondire l’argomento può confrontare il modello dell’event loop con Thread pool vs. ciclo di eventi e ne deduce conclusioni per il proprio ambiente.

Affinità della CPU: associare i worker ai core

Ho impostato affinità_CPU_del_processore in modo mirato quando i carichi di lavoro sono costanti e vincolati alla CPU. Distribuisco lo schema di assegnazione tramite maschere di bit per evitare i cambi di contesto e favorire la località della cache. Con quattro core, assegno le maschere in modo che ogni worker abbia un proprio core. Successivamente, analizzo i tassi di cache miss, le latenze mediane e il 99° percentile per valutare chiaramente l’effetto. Trovi spiegazioni più dettagliate sull’affinità e sul NUMA in forma sintetica su Affinità della CPU: aspetti pratici, il che è utile per la messa a punto di Lavoratore-Aiuta con i layout.

Pianificazione della capacità: margine di sicurezza e test di carico

Quando si tratta di collegamenti, prevedo un Buffer un valore nettamente superiore ai picchi osservati, in modo che i picchi momentanei non superino direttamente i limiti. Se raddoppio il carico di picco come punto di partenza, in molti scenari dispongo di un solido margine di sicurezza. In caso di traffico fortemente fluttuante, allargo ulteriormente il margine di sicurezza fino a quando i percentili al 99° non risultano stabili. Successivamente, verifico i colli di bottiglia con strumenti come wrk o k6, osservo i tassi di errore e controllo le connessioni aperte nella sezione “Stato”. Solo quando le metriche sono coerenti, aumento o riduco in modo mirato i singoli Valori.

Configurazione ed esempi di calcolo

Calcolo la capacità di connessione moltiplicando il numero di worker per le connessioni per worker e, sulla base di questo, imposto limiti più elevati. Con quattro core della CPU, l’opzione “auto” e 4096 connessioni per worker, ottengo un risultato teorico di 16384 connessioni simultanee. Negli scenari proxy, tendo a impostare rlimit_nofile a 32768 o oltre, in modo da includere i socket upstream. Per macchine di piccole dimensioni con due core, spesso sono sufficienti 2048 connessioni per worker, a condizione che la percentuale di upload e TLS rimanga moderata. La tabella seguente aiuta a classificare i valori iniziali:

Core della CPU processi_lavoratori worker_connections (Inizio) rlimit_nofile min. (valore indicativo) Suggerimento
2 auto (≈2) 2048 ≥ 4096 Riserva Pianificare per TLS/proxy
4 auto (≈4) 4096 ≥ 16384 Con il proxy, spesso il fattore è 2 per gli FD
8 auto (≈8) 4096-8192 ≥ 32768 Prova di carico decide in merito all'aumento
16+ auto, eventualmente meno 8192+ ≥ 65535 Testare con sensibilità e discrezione

NGINX Worker e Upstream: ponderare correttamente gli scenari

Distinguo tra distribuzione statica, funzionamento con proxy inverso e carico del gateway API, poiché essi costituiscono la Lavoratore-Configurazione diversa. I contenuti statici impiegano meno risorse, mentre TLS, la compressione e le connessioni upstream gravano maggiormente sulla CPU e sugli FD. Più grandi sono le chiavi SSL e più numerosi sono gli handshake, maggiore è il vantaggio offerto dall’impostazione „un worker per core“. I caricamenti di grandi dimensioni spostano il profilo verso l’I/O, il che mi porta a prestare maggiore attenzione a rlimit_nofile e ai buffer di rete. In caso di tempi di attesa percepibili per l’accettazione o le risposte del backend, mi è utile avere una panoramica per Code e latenza, per evitare i punti di strozzatura mirato risolvere.

Flusso di lavoro pratico: passo dopo passo verso un server più veloce

Inizio con un'analisi della situazione attuale di tutti gli elementi rilevanti Valori Nel file nginx.conf, controllo i core della CPU, i valori di ulimit e i parametri del kernel. Successivamente imposto worker_processes su auto, imposto worker_connections, ad esempio, su 4096 e aumento generosamente il valore di rlimit_nofile. Nel blocco Events attivo epoll e multi_accept e, dopo un reload, controllo i log. Seguono test di carico in condizioni riproducibili, durante i quali osservo i tempi di risposta, i tassi di errore e le connessioni aperte. Nella fase di messa a punto, modifico sempre una sola variabile alla volta, documento ogni passaggio e ne verifico gli effetti nei Metriche.

Ambiente di hosting: risorse, kernel, rete

Mi assicuro che ci sia una quantità sufficiente di CPU-Core, RAM sufficiente, SSD veloci o NVMe e un kernel Linux aggiornato. Solo così epoll, i moderni stack TCP e le funzionalità di offload efficaci funzionano in modo affidabile. Adatto i parametri di rete come somaxconn e tcp_max_syn_backlog al numero desiderato di connessioni, per mantenere brevi le code di accettazione. Un provider con prestazioni I/O affidabili e una configurazione di sistema liberamente accessibile ripaga chiaramente in questo senso. Dai confronti emerge che i servizi con Risorse Aumentare notevolmente le possibilità offerte da NGINX.

Strategia Keepalive: connessioni client e upstream

Utilizzo Keepalive in modo mirato come strumento per ottimizzare la capacità e la latenza. Sul lato client impiego keepalive_timeout non troppo alto, in modo che i socket inattivi non vengano inutilmente connessioni_lavoratore bloccare. Valori compresi tra 10 e 30 s mi offrono spesso un buon compromesso tra riutilizzo e impiego di risorse. Con keepalive_requests Limito il numero di richieste per connessione, per interrompere le sessioni di lunga durata ed evitare il sovraccarico della memoria. Sul lato upstream (proxy inverso) mantengo connessioni persistenti con keepalive nel blocco upstream, in modo da evitare gli handshake e la configurazione TCP. A tal fine, dimensiono il numero per ogni backend in modo prudente in base alla capacità del backend (max_conns), altrimenti mi ritrovo con code di attesa nell'upstream. Importante: ogni socket keepalive conta come connessione aperta e richiede FD; ne tengo conto in rlimit_nofile e la mia pianificazione dell'headroom.

Ottimizzazione delle liste: reuseport, backlog e strategia di accettazione

Distribuisco il carico in modo uniforme, in modo da SO_REUSEPORT attiva (listen … reuseport). Ogni worker dispone così di una propria coda di accettazione, il che riduce il fenomeno dei „thundering herds“ ed evita la formazione di punti di congestione. In combinazione con multi_accept accelero notevolmente la fase di accettazione. L'elenco-arretrato (listen … backlog=) e le corrispondenti impostazioni del kernel (somaxconn, tcp_max_syn_backlog) le imposto con valori generosi, in modo che i picchi non vadano persi all’ingresso del socket. L’opzione differito rimanda l'Accept fino a quando non sono disponibili i dati – in caso di numerose richieste di breve durata questo può essere d'aiuto, altrimenti faccio un confronto nei test. Se io accept_mutex Valuto di cosa ho bisogno nel benchmark: con reuseport, nella maggior parte dei casi è superfluo; senza reuseport può migliorare l’equità, ma richiede un maggiore coordinamento. In questo caso prendo decisioni basate sui dati, mai d’istinto.

Configurare in modo stabile i timeout e le code

Ho impostato Timeout in modo che i client lenti non intasino i worker: client_header_timeout e timeout_corpo_cliente Lo mantengo abbastanza conciso da evitare intoppi, ma abbastanza ampio da soddisfare le esigenze degli utenti reali. send_timeout impedisce che le risposte verso il client vengano bloccate. Nel contesto del proxy, definisco timeout_connessione_proxy, proxy_read_timeout e proxy_send_timeout in modo rigoroso, affinché i backend in sospeso non blocchino il frontend. Nel caso di backend con parallelismo limitato, utilizzo coda nel blocco upstream con timeout, per attenuare i picchi e segnalare in modo controllato il codice di errore 503, invece di legare tutti i worker ai socket upstream in attesa. Inoltre, stabilizzo il sistema con limit_req (Burst/Delay) e limit_conn percorsi sensibili, in modo che i singoli client o bot non consumino risorse in modo sproporzionato.

Buffering, sendfile e AIO: scegliere con consapevolezza le modalità di I/O

Ho impostato sendfile per i file statici e combinalo con tcp_nopush/tcp_nodelay a seconda del carico di lavoro, per raggruppare i pacchetti in modo efficiente o ridurre le latenze interattive. Per i file di grandi dimensioni utilizzo direzione a partire da una certa soglia, in modo da evitare il cache pollution e impedire che la cache delle pagine venga sovrascritta. In modalità proxy, decido se proxy_buffering sia d'aiuto (trasferimento rapido al client, lettura upstream disaccoppiata) oppure se, in caso di carichi di streaming, sia preferibile proxy_request_buffering ridurre, per avviare i caricamenti in anticipo. Le dimensioni di proxy_buffers, proxy_buffer_size e buffer di intestazione dei client di grandi dimensioni Lo gestisco in modo mirato, affinché l'utilizzo della memoria per ogni connessione non aumenti a dismisura. Per garantire un accesso ai file che non sovraccarichi la CPU, sto valutando aio (nativo o thread), ma esegui dei test approfonditi, poiché le caratteristiche dell'event loop e dell'I/O si influenzano a vicenda.

HTTP/2/HTTP/3 e TLS: impatto sulla capacità dei worker

Tengo conto del fatto che HTTP/2 e HTTP/3 Modificare la dinamica delle connessioni: molte richieste vengono eseguite come Streaming tramite un numero ridotto di connessioni TCP o QUIC. Ciò riduce il numero di connessioni, ma aumenta il carico sulla CPU e il consumo di memoria per ogni connessione (multiplexing, compressione delle intestazioni, TLS/QUIC). Il mio connessioni_lavoratore Pertanto non lo interpreto acriticamente come „lo stesso numero di richieste“. Osservo flussi concorrenti per ogni connessione e adattamento keepalive_timeout e, se necessario,. http2_max_concurrent_streams . Dal punto di vista del TLS, traggo vantaggio dal Session Resumption (ticket/cache) e dall’OCSP Stapling; in questo modo evito costosi handshake e mantengo basse le latenze. Il rovescio della medaglia: i keepalive più lunghi occupano FD e RAM – quindi pianifico rlimit_nofile e quote di memoria con riserve realistiche. Per gli algoritmi di cifratura che richiedono un elevato carico di CPU, vale la pena provare l'affinità e le moderne tecniche di accelerazione crittografica.

Monitorabilità: stato, registri e metriche

Garantisco la trasparenza con un approccio snello Stato-Endpoint (ad es. stub_status) per visualizzare le connessioni attive, gli stati di lettura/scrittura/attesa e le richieste accettate. Nei log cerco di ridurre al minimo il rumore: un formato compatto log_format con tempo, stato, tempi di upstream e byte è sufficiente per la maggior parte delle analisi. In caso di QPS molto elevato, disattivo il log di accesso in modo selettivo (in base alla posizione) oppure memorizzo i log in buffer in modo asincrono, in modo che l'I/O non rallenti il sistema. Impostare il log degli errori su avvertire oppure errore e passa temporaneamente a debug. Correlando costantemente le latenze (mediana/95°/99° percentile), le connessioni aperte, i tassi di errore del backend e il carico della CPU per ciascun worker, ne deduco gli adeguamenti delle tre direttive chiave e rilevo tempestivamente eventuali effetti di saturazione.

Container e ambienti virtuali: superare i limiti in modo pulito

Sto verificando nei container il cgroup-Imposta i limiti per CPU, RAM e PID e allineali alle impostazioni di NGINX. ulimit -n deve essere sufficientemente alto all'interno del container, altrimenti le mie modifiche a `rlimit_nofile` non avranno effetto. Per le quote CPU (ad es. 2 vCPU) imposto processi_lavoratori Di conseguenza, affinché la pianificazione non crei un sovraccarico artificiale. In prossimità della rete, nelle modalità di rete „host“ traggo vantaggio da una minore latenza di overhead, mentre gli overlay comportano hop aggiuntivi. Su host multi-NUMA, presto attenzione all’affinità e ai socket di memoria, in modo che i worker non operino trasversalmente ai nodi. Lo stesso vale per l’affinità IRQ e RPS/XPS: se i percorsi dalla scheda di rete (NIC) attraverso l’IRQ fino al kernel del worker sono corretti, i picchi di latenza diminuiscono in modo misurabile.

Ciclo di vita delle connessioni: porte effimere, TIME_WAIT e riserve

Prevedo di avere abbastanza porte effimere (ip_local_port_range), quando NGINX agisce come client attivo nei confronti degli upstream. In caso di throughput di connessione molto elevato, evito un’eccessiva fluttuazione delle porte utilizzando l’upstream keepalive, il che riduce le code TIME_WAIT. Utilizzo con cautela le opzioni del kernel relative al „riutilizzo“; gli stack moderni ottimizzano già gran parte dei processi internamente. È più stabile controllare la durata delle connessioni tramite valori ragionevoli di keepalive e timeout e riuso per garantire una distribuzione equa. Nel calcolo della capacità, oltre ai client, tengo sempre in considerazione anche il lato upstream: spesso sono proprio gli FD a rappresentare il vero fattore limitante, non il frontdoor.

Ricaricamenti e distribuzioni fluidi senza interruzioni

Utilizzo il modello master/worker per ricariche fluide: Il master carica le nuove configurazioni; i vecchi worker si disconnettono, mentre quelli nuovi subentrano senza interruzioni. Con worker_shutdown_timeout Lascio che le richieste si completino correttamente senza bloccare le risorse. Abbino le distribuzioni a tempo zero sull'upstream a controlli di integrità e proxy_next_upstream-Regole per evitare che singoli backend difettosi facciano aumentare la latenza globale. Quando apporto modifiche alla configurazione, cambio sempre un solo parametro alla volta e ne verifico gli effetti nei log e nelle metriche: in questo modo evito errori dovuti alla confusione e mantengo le prestazioni riproducibili.

Sintesi concisa

Mi collego processi_lavoratori Imposta il numero di core (idealmente in modalità automatica), configura worker_connections in base al picco di carico e aumenta generosamente rlimit_nofile insieme ai limiti del sistema operativo. Nel blocco Events utilizzo epoll e multi_accept, verifico il tutto con test di carico riproducibili e poi effettuo le regolazioni necessarie a piccoli passi. Per i carichi di lavoro proxy, prevedo descrittori aggiuntivi e testo l’affinità della CPU quando i carichi di lavoro sono costanti. Uno stack configurato correttamente con un kernel adeguato, I/O veloce e parametri di rete ottimali fa la differenza. Ecco come ottengo NGINX affidabile nel campo delle prestazioni richiesto dalle pagine e dalle API più esigenti.

Articoli attuali