J'ai un petit problème avec Apache2 Event, pour un serveur miroir à forte audience (contenu statique) C'est une question pour ceux qui connaissent bien Apache2, mais je commence par décrire un peu Apache Event pour ceux qui vont tomber sur cette discussion :
Le moteur (MPM) event est le serveur web haute performance d'Apache, concurrent de Nginx. Il implèmente un serveur hybride multi-processus et multi-threads. Contrairement aux autres serveurs web d'Apache qui imposent de réserver un couple processus enfant/thread pour attendre les données en provenance du client, Apache2 Event ne nécessite plus de thread de travail dédié pour les "Connexions asynchrones". Concrètement seule les connexions a qui le serveur envoi réellement des données utilisent couple processus enfant/thread.
Les connexions qui sont en pause (tampon d'écriture TCP plein ou connexion internet du client trop lente), celles qui Keep-alive (en attendant une autre demande sur la même connexion TCP) ou celles qui court de fermeture progressive, n'utilisent plus de thread de travail dédié.
Vous allez me dire qu'un problème peut se produire : Que se passe-t-il si un processus enfant, capable de gérer 100 connections a qui le serveur envoi réellement des données a 200 connexions en attente (Connexions asynchrones) qui se réveillent simultanèment ? Pour minimiser les effets de ce problème, le Apache2 event utilise deux méthodes :
- il limite le nombre de connexions simultanées par thread en fonction du nombre de processus inactifs;
- si tous les processus sont occupés, il ferme des connexions permanentes, même si la limite de durée de la connexion n'a pas été atteinte. Ceci autorise les clients concernés à se reconnecter à un autre processus possèdant encore des threads disponibles.
Pour limiter le nombre de connexions, c'est la Directive AsyncRequestWorkerFactor. Cette directive permet de personnaliser finement la limite du nombre de connexions par thread. Un processus n'acceptera de nouvelles connexions que si le nombre actuel de connexions (sans compter les connexions à l'état "closing") est inférieur à : ThreadsPerChild + (AsyncRequestWorkerFactor * nombre de threads inactifs)
Voici mon problème avec Apache2 event :
La configuration utilisée :<IfModule mpm_event_module>
StartServers 2
ServerLimit 100
MinSpareThreads 128
MaxSpareThreads 512
ThreadLimit 128
ThreadsPerChild 100
MaxRequestWorkers 1600
MaxConnectionsPerChild 0
AsyncRequestWorkerFactor 2
</IfModule>
-
StartServers : Nombre de processus enfants du serveur créés au démarrage. Apache va rapidement créer d'autres processus enfants, si la demande est suffisamment forte.
-
ServerLimit : Limite supérieure de la définition du nombre de processus. J'utilise la formule
ServerLimit = MaxRequestWorkers / 16 pour le définir, mais je ne suis pas sur de moi (je me demande si le mettre à
MaxRequestWorkers / ThreadsPerChild n'est pas suffisant.
-
MinSpareThreads : Nombre minimum de threads inactifs qui seront disponibles pour pouvoir traiter les pics de requêtes
-
MaxSpareThreads : Nombre maximum de threads inactifs. Permet de libérer de la RAM en réduisant les enfants inutiles, après un pic momentané de trafic.
-
ThreadLimit : Max de
ThreadsPerChild => Le nombre de threads maximum que l'on peut définir par processus enfant. Il est possible de modifier
ThreadsPerChild par un simple reload jusqu'à
ThreadLimit. Pour modifier
ThreadLimit un restart est nécessaire (donc coupure des connexions en cours)
-
ThreadsPerChild : Nombre de threads créés par chaque processus enfant (
ThreadsPerChild est < ou = à
ThreadLimit)
-
MaxRequestWorkers : Nombre maximum de connexions pouvant être traitées simultanèment. Il est inutile de mettre
MaxRequestWorkers > ThreadsPerChild * ServerLimit.
-
MaxConnectionsPerChild : Limite le nombre de connexions qu'un processus enfant va traiter au cours de son fonctionnement. Utile en cas de fuites accidentelles de mémoire.
-
AsyncRequestWorkerFactor : Permet de personnaliser finement la limite du nombre de connexions par thread. Un processus n'acceptera de nouvelles connexions que si le nombre actuel de connexions (sans compter les connexions à l'état "closing") est inférieur à:
ThreadsPerChild + (AsyncRequestWorkerFactor * nombre de threads inactifs)Cette configuration est en théorie capable de servir 1600 clients actif simultanèment et 1600 * 2 clients inactif qui attendent que le buffer se vide, soit au total 4800 connexions, sans compter les connexion en cour de fermeture, qui sont en bonus.
Mon problème avec Apache2 Event :
Quand un processus enfant (100 connexions activers par processus enfant dans mon cas) ne va plus accepter de nouvelles connexions, il lui reste de nombreux threads inactifs, c'est normal, il faut des threads inactifs en réserve, pour les connexions Connexions asynchrones qui peuvent se réveiller à n'importe quel moment.
Problème, un nouveau processus enfant n'est crée que si il y a moins de MinSpareThreads threads inactifs (128 dans mon cas).
En réalité, dès que la charge augmente, de nombreux processus enfants ne vont plus accepter de connexions, mais en additionnant les threads inactifs de tous les processus enfants, on est bien au-delà à de 128 threads inactifs, donc apache n'augmente pas le nombre d'enfants. Et il arrive un moment où tous les enfants refusent les nouvelles connexions. A ce moment là il est impossible d'établir une nouvelle connexion. Le service est indisponible.
J'ai donc tenté de jouer avec AsyncRequestWorkerFactor, j'ai passé le ratio à 1.3, ce qui limite encore plus le nb de connexion => Le blocage arrive encore plus vite.
J'ai passé le ratio à 4, cela repousse le blocage (il faut plus de charge pour arriver dans la situation de blocage où tous les process enfants n’accepte plus de nouvelles connexions)
J'ai passé le ratio à 8 et là c'est parti en vrille : il n'y a toujours pas de création de nouveaux process enfant et on a des enfants qui se retrouvent en famine : des connexions inactives basculent en active alors qu'il y a déjà 100 connexions actives servies par cet enfant.
J'ai réalisé un petit gif animé pour montrer le phénomène : (AsyncRequestWorkerFactor = 4 dans le gif ci-dessous)
- Vert = process enfant qui accepte des connexions
- Jaune = process enfant en cour de suppression (Gracefully finishing) : Il n'accepte plus de nouvelles connexions et il reste en vie jusqu'à ce que les connexions en cours se terminent, et cela peut prendre plusieurs heures, si le client à une connexion à petit débit et qu'il souhaite télécharger des gros fichiers. ici on a le PID 5972 qui a 2 connexions encore en vie.
- Rouge = process enfant qui refuse les nouvelles connexions.
Je n'ai pas de copie d'écran avec aucun process en vert, cas dans ce cas là la page ne s'affiche pas : ( Le cas s'est produit plusieurs fois entre les copies d'écran présentées dans le GIF.Voici les différentes copie d'écran de ce GIF, par heure de la copie d'écran :
6h47 -
7h55 -
7h58 -
7h59 -
8h01 -
8h02 -
8h04 -
8h06 -
8h10 -
8h13 (toutes prises le 22 mars 2016)