1. Lösung
Wenn jeder Worker-Prozess erstellt wird, wird die Methode ngx_worker_process_init() aufgerufen, um den aktuellen Worker-Prozess zu initialisieren, nämlich dass jeder Worker-Prozess epoll_create aufruft ()-Methode erstellt ein eindeutiges Epoll-Handle für sich selbst. Für jeden Port, der überwacht werden muss, gibt es einen entsprechenden Dateideskriptor. Der Arbeitsprozess fügt den Dateideskriptor nur über die Methode epoll_ctl () zum Epoll-Handle des aktuellen Prozesses hinzu und lauscht auf das Akzeptanzereignis das Verbindungsaufbauereignis des Clients, um das Ereignis zu verarbeiten. Daraus ist auch ersichtlich, dass das entsprechende Ereignis nicht ausgelöst werden kann, wenn der Arbeitsprozess den Dateideskriptor, der dem zu überwachenden Port entspricht, nicht zum Epoll-Handle des Prozesses hinzufügt. Basierend auf diesem Prinzip verwendet Nginx eine gemeinsame Sperre, um zu steuern, ob der aktuelle Prozess die Berechtigung hat, den zu überwachenden Port zum Epoll-Handle des aktuellen Prozesses hinzuzufügen. Mit anderen Worten: Nur der Prozess, der die Sperre erhält, wird überwacht zum Zielport. Auf diese Weise ist gewährleistet, dass bei jedem Auftreten eines Ereignisses nur ein Worker-Prozess ausgelöst wird. Die folgende Abbildung ist ein schematisches Diagramm des Arbeitszyklus des Worker-Prozesses:
In Bezug auf den Prozess in der Abbildung muss Folgendes erklärt werden: Jeder Worker-Prozess versucht, nach dem Betreten des die gemeinsame Sperre zu erhalten Wenn die Schleife nicht abgerufen wird, wird der Dateideskriptor des überwachten Ports aus dem Epoll-Handle des aktuellen Prozesses entfernt (er wird entfernt, auch wenn er nicht vorhanden ist). Verlust von Client-Verbindungsereignissen, auch wenn dies zu einem geringfügigen, aber nicht schwerwiegenden Thundering-Herd-Problem führen kann. Stellen Sie sich vor, wenn theoretisch der Dateideskriptor des Überwachungsports aus dem Epoll-Handle entfernt wird, wenn der aktuelle Prozess die Sperre aufhebt, dann ist der Dateideskriptor, der jedem Port in diesem Zeitraum entspricht, bevor der nächste Arbeitsprozess die Sperre erhält Wenn kein Epoll-Handle zum Abhören vorhanden ist, geht das Ereignis verloren. Wenn andererseits die überwachten Dateideskriptoren nur dann entfernt werden, wenn die Sperrenerfassung fehlschlägt, wie in der Abbildung gezeigt, bedeutet dies, dass es einen Prozess geben muss, der diese Dateideskriptoren derzeit überwacht, sodass dies sicher ist um sie zu diesem Zeitpunkt zu entfernen. Ein Problem, das dadurch entsteht, besteht laut obiger Abbildung darin, dass der aktuelle Prozess, wenn er die Ausführung einer Schleife abschließt, die Sperre aufhebt und dann andere Ereignisse verarbeitet. Beachten Sie, dass er den überwachten Dateideskriptor während dieses Prozesses nicht freigibt . Wenn zu diesem Zeitpunkt ein anderer Prozess die Sperre erhält und den Dateideskriptor überwacht, werden zu diesem Zeitpunkt zwei Prozesse den Dateideskriptor überwachen. Wenn daher ein Verbindungsaufbauereignis auf dem Client auftritt, werden zwei Worker-Prozesse ausgelöst. Dieses Problem ist aus zwei Hauptgründen tolerierbar:
Dieses donnernde Herdenphänomen löst nur eine geringere Anzahl von Worker-Prozessen aus, was viel besser ist, als jedes Mal alle Worker-Prozesse aufzuwecken
wird passieren Der Hauptgrund dafür Das Panikproblem besteht darin, dass der aktuelle Prozess die Sperre aufhebt, den überwachten Dateideskriptor jedoch nicht freigibt. Nach dem Aufheben der Sperre verarbeitet der Arbeitsprozess jedoch hauptsächlich die Lese- und Schreibereignisse der Clientverbindung und überprüft das Flag-Bit Sehr kurz. Nach der Verarbeitung wird versucht, die Sperre zu erhalten. Im Vergleich dazu wartet der Worker-Prozess, der die Sperre erhält, auf die Verarbeitung des Verbindungsaufbauereignisses Daher ist die Wahrscheinlichkeit eines donnernden Herdenproblems noch relativ gering.
2. Erklärung des Quellcodes
Die Methode der Anfangsereignisse des Arbeitsprozesses wird hauptsächlich in der Methode ngx_process_events_and_timers() ausgeführt Quellcode der Methode:
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); }
Im obigen Code haben wir den größten Teil der Prüfarbeit weggelassen und nur den Grundcode übrig gelassen. Zuerst ruft der Arbeitsprozess die Methode ngx_trylock_accept_mutex() auf, um die Sperre zu erhalten. Wenn die Sperre erhalten wird, überwacht er den Dateideskriptor, der jedem Port entspricht. Anschließend wird die Methode ngx_process_events() aufgerufen, um die im Epoll-Handle überwachten Ereignisse zu verarbeiten. Anschließend wird die gemeinsame Sperre aufgehoben und schließlich werden die Lese- und Schreibereignisse des verbundenen Clients verarbeitet. Werfen wir einen Blick darauf, wie die Methode ngx_trylock_accept_mutex() die gemeinsame Sperre erhält:
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; }
Im obigen Code führt sie im Wesentlichen drei Hauptaufgaben aus:
Versuchen Sie, die Methode cas zu verwenden, um die gemeinsame Sperre über ngx_shmtx_trylock( )-Methode;
Nach dem Erwerb der Sperre rufen Sie die Methode ngx_enable_accept_events() auf, um den dem Zielport entsprechenden Dateideskriptor zu überwachen Dateideskriptor.
Das obige ist der detaillierte Inhalt vonSo lösen Sie das Nginx-Panikgruppenproblem. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!