1. Solution
Lorsque chaque processus de travail est créé, la méthode ngx_worker_process_init() sera appelée pour initialiser le processus de travail actuel. Il y a une étape très importante dans ce processus, c'est-à-dire que chaque processus de travail appellera epoll_create. () La méthode crée un handle epoll unique pour elle-même. Pour chaque port qui doit être surveillé, il existe un descripteur de fichier qui lui correspond, et le processus de travail ajoute uniquement le descripteur de fichier au handle epoll du processus en cours via la méthode epoll_ctl() et écoute l'événement d'acceptation déclenché par. l'événement d'établissement de connexion du client pour gérer l'événement. On peut également voir à partir d'ici que si le processus de travail n'ajoute pas le descripteur de fichier correspondant au port qui doit être surveillé au handle epoll du processus, alors l'événement correspondant ne peut pas être déclenché. Sur la base de ce principe, nginx utilise un verrou partagé pour contrôler si le processus actuel a l'autorisation d'ajouter le port qui doit être surveillé au handle epoll du processus actuel. En d'autres termes, seul le processus qui acquiert le verrou écoutera. au port cible. De cette manière, il est garanti qu'un seul processus de travail sera déclenché à chaque fois qu'un événement se produit. La figure suivante est un diagramme schématique du cycle de travail du processus de travail :
Concernant le processus de la figure, une chose qui doit être expliquée est que chaque processus de travail tentera d'acquérir le verrou partagé après être entré dans le S'il ne l'obtient pas, le descripteur de fichier du port surveillé sera supprimé du handle epoll du processus en cours (il sera supprimé même s'il n'existe pas). perte d'événements de connexion client, même si cela peut causer un petit problème de troupeau Thundering, mais pas grave. Imaginez simplement que si, selon la théorie, le descripteur de fichier du port d'écoute est supprimé du handle epoll lorsque le processus actuel libère le verrou, alors avant que le processus de travail suivant n'acquière le verrou, le descripteur de fichier correspondant à chaque port pendant cette période est S'il n'y a pas de handle epoll à écouter, l'événement sera perdu. Si, en revanche, les descripteurs de fichiers surveillés sont supprimés uniquement lorsque l'acquisition du verrou échoue, comme le montre la figure. Puisque l'acquisition du verrou échoue, cela signifie qu'il doit y avoir actuellement un processus surveillant ces descripteurs de fichiers, donc il est sûr. pour les supprimer à ce moment-là. Mais un problème que cela entraînera est que, selon la figure ci-dessus, lorsque le processus en cours termine l'exécution d'une boucle, il libère le verrou et gère ensuite d'autres événements. Notez qu'il ne libère pas le descripteur de fichier surveillé pendant ce processus. . À ce stade, si un autre processus acquiert le verrou et surveille le descripteur de fichier, alors deux processus surveillent le descripteur de fichier à ce moment-là. Par conséquent, si un événement d'établissement de connexion se produit sur le client, deux processus seront déclenchés. Ce problème est tolérable pour deux raisons principales :
Ce phénomène de troupeau tonitruant ne déclenchera qu'un plus petit nombre de processus de travail, ce qui est bien mieux que de réveiller tous les processus de travail à chaque fois
se produira. La raison principale de cela Le problème de panique est que le processus actuel libère le verrou, mais ne libère pas le descripteur de fichier surveillé. Cependant, après avoir libéré le verrou, le processus de travail traite principalement les événements de lecture et d'écriture de la connexion client et vérifie le bit d'indicateur. très court. Après le traitement, il tentera d'acquérir le verrou. À ce moment, le descripteur de fichier surveillé sera libéré. En comparaison, le processus de travail qui acquiert le verrou attend de traiter l'événement d'établissement de connexion du client. , donc la probabilité d’un problème de troupeau tonitruant est encore relativement faible.
2. Explication du code source
La méthode d'événement initiale du processus de travail est principalement exécutée dans la méthode ngx_process_events_and_timers(). Voyons comment cette méthode gère l'ensemble du processus. méthode Code source :
void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ngx_uint_t flags; ngx_msec_t timer, delta; if (ngx_trylock_accept_mutex(cycle) == ngx_error) { return; } // 这里开始处理事件,对于kqueue模型,其指向的是ngx_kqueue_process_events()方法, // 而对于epoll模型,其指向的是ngx_epoll_process_events()方法 // 这个方法的主要作用是,在对应的事件模型中获取事件列表,然后将事件添加到ngx_posted_accept_events // 队列或者ngx_posted_events队列中 (void) ngx_process_events(cycle, timer, flags); // 这里开始处理accept事件,将其交由ngx_event_accept.c的ngx_event_accept()方法处理; ngx_event_process_posted(cycle, &ngx_posted_accept_events); // 开始释放锁 if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } // 如果不需要在事件队列中进行处理,则直接处理该事件 // 对于事件的处理,如果是accept事件,则将其交由ngx_event_accept.c的ngx_event_accept()方法处理; // 如果是读事件,则将其交由ngx_http_request.c的ngx_http_wait_request_handler()方法处理; // 对于处理完成的事件,最后会交由ngx_http_request.c的ngx_http_keepalive_handler()方法处理。 // 这里开始处理除accept事件外的其他事件 ngx_event_process_posted(cycle, &ngx_posted_events); }
Dans le code ci-dessus, nous avons omis la plupart du travail de vérification, ne laissant que le code squelette. Tout d'abord, le processus de travail appellera la méthode ngx_trylock_accept_mutex() pour obtenir le verrou. Si le verrou est obtenu, il écoutera le descripteur de fichier correspondant à chaque port. Ensuite, la méthode ngx_process_events() sera appelée pour traiter les événements surveillés dans le handle epoll. Ensuite, le verrou partagé sera libéré et enfin les événements de lecture et d'écriture du client connecté seront traités. Voyons comment la méthode ngx_trylock_accept_mutex() acquiert le verrou partagé :
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) { // 尝试使用cas算法获取共享锁 if (ngx_shmtx_trylock(&ngx_accept_mutex)) { // ngx_accept_mutex_held为1表示当前进程已经获取到了锁 if (ngx_accept_mutex_held && ngx_accept_events == 0) { return ngx_ok; } // 这里主要是将当前连接的文件描述符注册到对应事件的队列中,比如kqueue模型的change_list数组 // nginx在启用各个worker进程的时候,默认情况下,worker进程是会继承master进程所监听的socket句柄的, // 这就导致一个问题,就是当某个端口有客户端事件时,就会把监听该端口的进程都给唤醒, // 但是只有一个worker进程能够成功处理该事件,而其他的进程被唤醒之后发现事件已经过期, // 因而会继续进入等待状态,这种现象称为"惊群"现象。 // nginx解决惊群现象的方式一方面是通过这里的共享锁的方式,即只有获取到锁的worker进程才能处理 // 客户端事件,但实际上,worker进程是通过在获取锁的过程中,为当前worker进程重新添加各个端口的监听事件, // 而其他worker进程则不会监听。也就是说同一时间只有一个worker进程会监听各个端口, // 这样就避免了"惊群"问题。 // 这里的ngx_enable_accept_events()方法就是为当前进程重新添加各个端口的监听事件的。 if (ngx_enable_accept_events(cycle) == ngx_error) { ngx_shmtx_unlock(&ngx_accept_mutex); return ngx_error; } // 标志当前已经成功获取到了锁 ngx_accept_events = 0; ngx_accept_mutex_held = 1; return ngx_ok; } // 前面获取锁失败了,因而这里需要重置ngx_accept_mutex_held的状态,并且将当前连接的事件给清除掉 if (ngx_accept_mutex_held) { // 如果当前进程的ngx_accept_mutex_held为1,则将其重置为0,并且将当前进程在各个端口上的监听 // 事件给删除掉 if (ngx_disable_accept_events(cycle, 0) == ngx_error) { return ngx_error; } ngx_accept_mutex_held = 0; } return ngx_ok; }
Dans le code ci-dessus, elle fait essentiellement trois choses principales :
Essayez d'utiliser la méthode cas pour acquérir le verrou partagé via ngx_shmtx_trylock( ) ;
Après avoir acquis le verrou, appelez la méthode ngx_enable_accept_events() pour surveiller le descripteur de fichier correspondant au port cible ;
Si le verrou n'est pas acquis, appelez la méthode ngx_disable_accept_events() pour libérer le port surveillé ; descripteur de fichier.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!