Je fais évoluer de manière ciblée le nombre de workers Nginx afin de traiter des milliers de requêtes simultanées avec un faible Latence à gérer. La clé réside dans une combinaison bien équilibrée entre worker_processes, worker_connections, les descripteurs de fichiers et Événements.
Points centraux
- Capacité = worker_processes × worker_connections ; dans le cas d'un proxy inverse, souvent déterminé par les connexions client et en amont doublé.
- Descripteurs de fichiers (worker_rlimit_nofile, ulimit) en fonction de la charge de connexion prévue soulever.
- Événements- Blocage avec epoll, multi_accept et les files d'attente du noyau en cas de charge élevée ajuster.
- Suivi via stub_status et des tests de charge pour les itérations Adaptation.
- Mise à l'échelle Combiner verticalement et horizontalement, configuration découpler.
Architecture NGINX : Master, Worker et événements
NGINX s'appuie sur un processus maître qui lance plusieurs processus de travail et les gère efficacement avec Événements traitées. Au lieu de traiter des threads par requête, chaque worker gère de nombreuses connexions de manière non bloquante via un modèle basé sur les événements, avec une faible Overhead. Je règle la directive `worker_processes` sur `auto` afin que NGINX exploite les cœurs du processeur et que chaque unité dispose de son propre worker. Cela me permet de mieux répartir les connexions entrantes et de maintenir la latence à un niveau acceptable même en période de pic de trafic faible. Pour approfondir la question de la planification des processus, je vous renvoie à Optimiser les processus « worker », car un parallélisme correctement configuré détermine la capacité de connexion réalisable. Il est essentiel que le paramètre `worker_connections` soit correctement dimensionné pour chaque worker, afin que sa multiplication par le nombre de processus donne le résultat attendu Charge de pointe couvre.
Formule de capacité : worker_processes × worker_connections
Je calcule la capacité approximative en multipliant worker_processes par worker_connections, sachant que les requêtes proxy occupent souvent deux connexions par accès utilisateur, ce qui réduit de moitié le nombre effectif. peut. De nombreuses installations standard démarrent avec 512 connexions par worker, ce qui s'avère souvent insuffisant pour les charges de travail en production est. Les valeurs de départ pratiques se situent généralement entre 1 024 et 4 096 et dépendent du profil de trafic et du matériel. Je prévois une marge de sécurité, c'est-à-dire au moins un facteur deux par rapport à la charge de pointe mesurée, afin de pouvoir gérer les pics de trafic en toute sécurité amortir. Il reste important de procéder à une validation à l'aide de tests et de métriques en temps réel, afin que les chiffres ne deviennent pas un simple jeu théorique seront.
| Scénario | worker_processes | worker_connections | Max. théorique. | Effectif (proxy) | FD par travailleur |
|---|---|---|---|---|---|
| Petit site | 2 | 1024 | 2048 | ~1024 | ≥1024 |
| API charge moyenne | 4 | 2048 | 8192 | ~4096 | ≥ 2 048 |
| Heures de pointe de la boutique | 8 | 4096 | 32768 | ~16384 | ≥ 4 096 |
HTTP/1.1, HTTP/2 et TLS : impact sur les workers et la latence
Les protocoles déterminent le profil de connexion. Avec HTTP/1.1, j'observe souvent un grand nombre de connexions TCP simultanées par client, tandis qu'HTTP/2 les réduit à quelques flux, mais qui sont en revanche davantage sollicités. regroupe. Cela permet d'économiser des descripteurs de fichiers, mais transfère la charge vers les tampons et la gestion des priorités. Sous TLS, je veille à réutiliser les sessions afin d'éviter que des « handshakes » coûteux ne soient effectués à chaque requête freiner. Un cache de session partagé et des délais d'expiration adaptés permettent de réduire les pics d'utilisation du processeur. De plus, je ne règle pas la valeur de `keepalive_requests` trop bas, afin que les connexions de longue durée puissent offrir tous leurs avantages jouer. Pour HTTP/2, je prévois une concurrence plus élevée par connexion et je veille à disposer de tampons d'envoi et de réception suffisamment grands, sans pour autant utiliser trop de mémoire gaspiller. En cas de trafic mixte, j'adopte une approche prudente et je vérifie les répercussions pour chaque variante de protocole dans le Test.
Configurer correctement les descripteurs de fichiers et ulimit
Chaque connexion nécessite au moins un descripteur de fichier ; dans le cas d'un proxy inverse, il en faut souvent deux, c'est pourquoi des valeurs ulimit trop basses posent de sérieux Frontières définir. J’augmente la valeur de `worker_rlimit_nofile` de manière à ce que `worker_processes` × `worker_connections` soit réalisable et qu’il reste des réserves pour les journaux, les sockets et les caches. Au niveau du système, j’ajuste les fichiers `limits.conf` et `fs.file-max` afin que le système d’exploitation autorise le nombre prévu de fichiers ouverts et ne s’arrête pas prématurément freine. À l'aide de la commande `ulimit -n` et du paramètre Systemd (LimitNOFILE), je vérifie si la configuration est persistante et adaptée à NGINX. Si vous ignorez ce paramètre, vous risquez de voir apparaître soudainement des connexions refusées et une augmentation de Latence.
Réglage fin du bloc « Events » : epoll, multi_accept, backlogs
Sous Linux, j'utilise epoll, car ce mécanisme permet de gérer efficacement un grand nombre de connexions grâce à la gestion asynchrone Événements gère. Avec l'option « multi_accept on », un worker accepte plusieurs nouvelles connexions par événement, ce qui permet de lisser les pics de charge et de réduire les délais d'acceptation réduit. J'ajuste les paramètres du noyau tels que net.core.somaxconn et net.ipv4.tcp_max_syn_backlog afin d'éviter que les files d'attente d'acceptation ne débordent en cas de pic de trafic. Les optimisations TIME_WAIT telles que tcp_tw_reuse réduisent les goulots d'étranglement au niveau des ports et maintiennent la courbe de débit élevé. Pour approfondir vos connaissances sur la concurrence et les files d'attente, nous vous recommandons de consulter Optimisation du pool de threads, même si NGINX fonctionne principalement de manière événementielle et est donc très économe en ressources mis à l'échelle.
Bien répartir les sockets de liste : reuseport, backlog et accept_mutex
Lorsque le nombre de connexions simultanées est très élevé, je redimensionne activement le chemin de réception. Avec reuseport Chaque worker dispose de son propre socket d'écoute ; cela élimine la concurrence lors de l'acceptation et la charge est répartie de manière homogène sur tous les cœurs. Je définis explicitement la file d'attente d'écoute afin de gérer les pics de trafic de courte durée. Dans cette configuration, le mutex `accept_mutex` n’est plus nécessaire. En revanche, sans `reuseport`, `accept_mutex` peut aider, afin d'atténuer les effets de meute au niveau de la commande « accept ». Important : les tailles des files d'attente dans NGINX et le noyau (somaxconn) doivent aller ensemble, sinon l'effet sera réduit à néant.
events {
use epoll;
worker_connections 4096;
# accept_mutex on; # : généralement inutile avec reuseport
}
server {
listen 443 ssl http2 reuseport backlog=65535;
# ...
}
De plus, j'affecte les workers à des cœurs de processeur si nécessaire (worker_cpu_affinity), afin de garantir la stabilité des lignes de cache et de la charge IRQ. Dans les environnements fortement NUMA, cela réduit les circulation transversale dans la mémoire.
Proxy inverse, serveurs en amont et Keep-Alive
En tant que proxy inverse, NGINX maintient souvent deux connexions par requête : une vers le client et une vers le backend, ce qui permet une planification réaliste des capacités double compte. J'active Keep-Alive de manière judicieuse afin que les connexions en amont restent réutilisables et que la surcharge par requête baisse. Cela me permet de réduire la charge sur PHP-FPM, le serveur d'applications ou les microservices et de libérer des slots pour de nouvelles sessions utilisateur. L'équilibre entre les délais d'expiration, la durée d'inactivité et la réutilisation détermine la manière dont les connexions sont recyclées seront. Si vous souhaitez en savoir plus sur les principes de base, vous trouverez dans Connexions persistantes conseils pratiques sur l'utilisation des ressources et l'optimisation du réseauUtilisez.
Pools en amont, délais d'attente et tentatives de reconnexion
Pour éviter que les workers n'aient à attendre des backends lents, je gère des délais d'expiration courts et des tentatives de reconnexion bien dosées. Je maintiens les pools de keepalive en amont à une taille suffisante pour que les connexions restent actives, mais pas trop grande pour éviter que les descripteurs de fichiers inactifs n'occupent de la mémoire et des slots lier. Je limite le nombre de tentatives à quelques-unes et je ne bascule que en cas d'erreurs de transport manifestes ; cela me permet d'éviter les effets de « thundering herd » en cas de brèves interruptions du backend.
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 ; Connexions en amont réutilisables #
}
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;
}
}
En parallèle, je règle les paramètres de Keep-Alive (délais d'expiration, nombre de requêtes par connexion) afin de libérer rapidement les ressources occupées par des clients peu actifs à valider.
Planifier judicieusement la mise à l'échelle : combiner les approches verticale et horizontale
Pour les volumes de trafic élevés, je privilégie une approche combinant l'évolutivité verticale et horizontale Considérer. Je procède à une évolutivité verticale en augmentant le nombre de cœurs de processeur, la mémoire vive, en utilisant des SSD rapides et en optimisant la configuration réseau, afin que chaque worker fonctionne de manière fluide travaille. Pour l'évolutivité horizontale, j'utilise des nœuds NGINX sans état, une configuration gérée de manière centralisée et une journalisation distribuée, afin que la capacité totale augmente de manière linéaire grandit. Les caches sur site et les politiques bien définies via Maps ou l'API permettent de déployer rapidement les modifications. Cette séparation réduit les effets secondaires et aide à gérer de nouveaux modèles de trafic sans avoir à modifier chaque nœud. servir.
Point de vue de l'hébergement : latence, taux d'erreur et expérience utilisateur
Un nombre insuffisant de `worker_connections` entraîne des connexions refusées, des délais d'attente et une baisse des performances Expérience utilisateur. Les applications dynamiques telles que les CMS ou les boutiques en ligne en ressentent immédiatement les effets, car l'affichage d'une page génère plusieurs requêtes vers le backend et les slots sont saturés plus rapidement à peine . C'est pourquoi je commence par des valeurs modérées, telles que 1024 ou 2048 par worker, puis je les augmente progressivement en fonction des mesures réelles. En parallèle, je veille à ce que les services en amont restent performants et je m'assure qu'il y ait suffisamment de descripteurs de fichiers pour éviter tout Limites intervenir. Les tests de performance montrent que des plateformes soigneusement optimisées offrent ici de réels avantages et permettent de gérer de manière fiable les pics de trafic intercepter.
Mémoire, mise en mémoire tampon et chemins d'E/S
Chaque connexion occupe de la mémoire vive pour les métadonnées et les tampons. Je dimensionne les paramètres `proxy_buffers`, `client_body_buffer_size` et `large_client_header_buffers` de manière à ce que les requêtes typiques puissent y tenir, sans pour autant allouer systématiquement trop de RAM en cas de valeurs aberrantes. lier. Pour les contenus statiques, les options `sendfile` et `tcp_nopush` accélèrent la diffusion, tandis que l'option `tcp_nodelay` est utilisée pour les petites réponses où la latence est un facteur critique important reste. Si les ressources sont stockées sur un support de stockage plus lent, les options `aio threads` et `thread_pool` permettent d'atténuer les effets de blocage. Avec `open_file_cache`, je réduis les accès aux fichiers et les appels à `stat()`, mais je tiens compte des besoins supplémentaires en descripteurs de fichiers (FD). J'enregistre les journaux en mode tamponné (access_log … buffer=… flush=…), afin que les pics d'E/S n'affectent pas les temps de réponse influencer.
Équilibre entre sécurité et performances TLS
Les protocoles TLS sollicitent fortement le processeur. Je combine la réutilisation des sessions avec des paramètres de clé modérés et j'active les optimisations cumulables telles que les caches de session et les tickets, dans la mesure où cela est compatible avec l'exploitation correspondent à. Le juste équilibre entre sécurité et performances permet de maintenir des latences stables sans compromettre la qualité du chiffrement. En cas de charge élevée, j'observe séparément les 95e et 99e centiles, car sinon les pics TLS passeraient inaperçus derrière les valeurs moyennes cacher. HTTP/2 réduit le nombre de connexions, mais nécessite une attention particulière en matière de contrôle de flux et de compression des en-têtes afin de maîtriser les profils de CPU et de mémoire conserver.
La résilience en situation de surcharge : limites et lâcher-prise en douceur
Afin de préserver la latence, il convient de procéder à un modelage indispensable en cas de pic de charge. Avec `limit_conn`, je limite le nombre de connexions parallèles par clé (par exemple, une adresse IP ou une session) ; `limit_req` régule les pics de charge et protège les backends contre les requêtes synchrones Prendre d'assaut. J'applique des règles plus strictes aux points de terminaison critiques qu'aux ressources statiques. En cas de pic de trafic soudain, je renvoie des codes d'erreur 429/503 bien définis avec un délai de réessai (« Retry-After »), plutôt que de traiter toutes les requêtes de manière uniforme mourir de faim Je suspends les connexions persistantes (lingering_close) afin de libérer les ressources de manière contrôlée et d'éviter les attaques de type Slowloris. réfuter. Ce « sheddage » actif maintient la latence p95/p99 dans la zone verte, même lorsque la demande totale dépasse temporairement la capacité nominale se trouve.
Intégration de conteneurs et de systèmes : lever les limites là où elles apparaissent
Dans les conteneurs, les limites sont souvent plus strictes. Je vérifie les limites cgroup (CPU, RAM), je configure ulimit -n de manière appropriée au sein du conteneur et j'intègre LimitNOFILE dans la définition du service. Les paramètres sysctl tels que somaxconn et tcp_max_syn_backlog doivent être définis sur le Hôte prennent effet ; les espaces de noms n'isolent pas toujours ces paramètres de manière transparente. Sur les plateformes orchestrées, je planifie la capacité par pod/nœud, j'affecte les workers aux cœurs qui leur sont attribués et je veille à ce que les chemins réseau soient stables (par exemple, pas de sauts NAT inutiles), afin que la courbe de latence calme reste. J'accompagne les mises à jour progressives avec `worker_shutdown_timeout` afin que les connexions existantes soient fermées correctement s'éteindre.
Suivi et optimisation itérative
Sans visibilité, les étapes de mise au point restent Risque. J'active stub_status ou d'autres solutions pour surveiller en continu les connexions actives, les taux d'acceptation et les rejets. Lors des tests de charge, je simule des modèles d'accès réalistes et j'identifie les goulots d'étranglement au niveau des files d'attente d'acceptation, des latences en amont ou du CPU-Saturation. Ensuite, j'ajuste avec précaution les paramètres `worker_connections`, les processus, les limites de fichiers et les paramètres TCP, puis je vérifie à nouveau l'effet obtenu. Ce cycle garantit la fiabilité de la plateforme et évite les mauvaises surprises Temps.
Exemple de configuration et méthode de calcul
Supposons que je prévoie 2 000 requêtes « in-flight » simultanées aux heures de pointe et que j'utilise un proxy inverse ; dans ce cas, je prévois environ 4 000 slots de connexion, plus Tampon. Lorsque NGINX fonctionne sur quatre cœurs de processeur, je commence par exemple avec `worker_processes auto` et `worker_connections` compris entre 1 000 et 2 000 par worker. Je définis la limite de descripteurs de fichiers par worker à une valeur suffisamment élevée pour que les connexions, les journaux et les sockets internes disposent de suffisamment de Place . Je configure le bloc « Events » en mode epoll, j'active multi_accept et j'augmente les backlogs du noyau en fonction de mon trafic de pointe. En voici un extrait minimaliste, que j'affine ensuite à l'aide de benchmarks vote:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
use epoll;
worker_connections 2048;
multi_accept on;
}
http {
keepalive_timeout 65;
sendfile on;
# autres options de proxy/cache...
}
De plus, j'ajoute des optimisations au niveau des listes et en amont afin d'optimiser le chemin de réception et le chemin backend sous charge :
events {
use epoll;
worker_connections 4096;
# worker_cpu_affinity auto; # : attribuer des cœurs de manière fixe si nécessaire
}
http {
# Optimisations TLS/session à titre d'exemple
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;
}
}
}
En résumé : des repères concrets
J'adapte le paramètre `worker_processes` au nombre de cœurs du processeur et je définis généralement le paramètre `worker_connections` entre 1024 et 4096. Avec le proxy inverse, je prévois deux connexions par requête et je conserve une marge d'au moins le double du pic mesuréDernier. Je définis worker_rlimit_nofile ainsi que les limites système à des valeurs suffisamment élevées pour que les paramètres du fichier nginx.conf restent réellement exploitables. Je limite le bloc Events à epoll et multi_accept, tandis que les backlogs du noyau permettent de gérer de brèves pics de trafic amortir. Grâce à un suivi et à des ajustements progressifs, j'en fais un moteur de trafic fiable, capable de gérer proprement l'augmentation du nombre de visites porte.


