Detaillierte Erklärung der Lösung für das Nginx-Panikproblem

Detaillierte Erklärung der Lösung für das Nginx-Panikproblem

In Bezug auf das Nginx-Panikproblem müssen wir zunächst verstehen, dass der Masterprozess während des Nginx-Startvorgangs die in der Konfigurationsdatei angegebenen Ports abhört und dann die Methode fork () aufruft, um jeden untergeordneten Prozess zu erstellen. Gemäß dem Arbeitsprinzip des Prozesses erbt der untergeordnete Prozess alle Speicherdaten und die Abhörports des übergeordneten Prozesses, was bedeutet, dass der Worker-Prozess nach dem Start auch jeden Port abhört. In Bezug auf den Herdenschock bedeutet dies, dass, wenn ein Client eine neue Verbindung anfordert, dieser das Verbindungsaufbauereignis jedes Arbeitsprozesses auslöst, jedoch nur ein Arbeitsprozess das Ereignis normal verarbeiten kann und die anderen Arbeitsprozesses feststellen, dass das Ereignis abgelaufen ist, und daher erneut in den Wartezustand wechseln. Dieses Phänomen, bei dem alle Arbeitsprozesse aufgrund eines Ereignisses „aufgeschreckt“ werden, wird als Herdenschreckproblem bezeichnet. Wenn alle Arbeitsprozesse ausgelöst werden, werden natürlich viele Ressourcen verbraucht. In diesem Artikel wird hauptsächlich erläutert, wie Nginx mit dem Herdenproblem umgeht.

1. Lösung

Im vorherigen Artikel haben wir erwähnt, dass beim Erstellen jedes Arbeitsprozesses die Methode ngx_worker_process_init () aufgerufen wird, um den aktuellen Arbeitsprozess zu initialisieren. In diesem Prozess gibt es einen sehr wichtigen Schritt, nämlich, dass jeder Arbeitsprozess die Methode epoll_create () aufruft, um einen eindeutigen Epoll-Handle für sich selbst zu erstellen. Für jeden Port, der abgehört werden muss, gibt es einen entsprechenden Dateideskriptor. Der Worker-Prozess fügt den Dateideskriptor nur über die Methode epoll_ctl() zum Epoll-Handle des aktuellen Prozesses hinzu und wartet auf das Accept-Ereignis. Erst dann wird er durch das Verbindungsaufbauereignis des Clients ausgelöst und verarbeitet das Ereignis. Hier ist auch ersichtlich, dass das entsprechende Ereignis nicht ausgelöst werden kann, wenn der Worker-Prozess dem Epoll-Handle des Prozesses nicht den Dateideskriptor hinzufügt, der dem abzuhörenden Port entspricht. 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, überwacht den Zielport. Auf diese Weise wird sichergestellt, dass bei jedem Auftreten eines Ereignisses nur ein Worker-Prozess ausgelöst wird. Die folgende Abbildung zeigt ein schematisches Diagramm des Arbeitszyklus des Worker-Prozesses:

Zu dem Prozess in der Abbildung muss erklärt werden, dass jeder Worker-Prozess nach dem Eintritt in die Schleife versucht, die gemeinsame Sperre zu erhalten. Wenn dies nicht gelingt, wird der Dateideskriptor des überwachten Ports aus dem Epoll-Handle des aktuellen Prozesses entfernt (selbst wenn er nicht existiert). Der Hauptzweck dieser Vorgehensweise besteht darin, den Verlust von Client-Verbindungsereignissen zu verhindern. Auch wenn dies ein kleines Panikproblem verursachen kann, ist es nicht schwerwiegend. Stellen Sie sich vor, dass der Dateideskriptor des überwachten Ports theoretisch aus dem Epoll-Handle entfernt wird, wenn der aktuelle Prozess die Sperre aufhebt. Bevor der nächste Worker-Prozess die Sperre erhält, werden die jedem Port entsprechenden Dateideskriptoren während dieser Zeit von keinem Epoll-Handle überwacht, was zum Verlust von Ereignissen führt. Wenn andererseits die überwachten Dateideskriptoren nur entfernt werden, wenn der Erwerb der Sperre fehlschlägt, wie in der Abbildung gezeigt, bedeutet dies, dass es einen Prozess geben muss, der diese Dateideskriptoren bereits abgehört hat, sodass sie zu diesem Zeitpunkt sicher entfernt werden können, da der Erwerb der Sperre fehlschlägt. Dies führt jedoch zu einem Problem: Gemäß der obigen Abbildung gibt der aktuelle Prozess, wenn er eine Schleife abschließt, die Sperre frei und verarbeitet dann andere Ereignisse. Beachten Sie, dass der überwachte Dateideskriptor während dieses Vorgangs nicht freigegeben wird. Wenn zu diesem Zeitpunkt ein anderer Prozess die Sperre erhält und den Dateideskriptor abhört, gibt es zwei Prozesse, die den Dateideskriptor abhören. Wenn daher auf dem Client ein Verbindungsaufbauereignis auftritt, werden zwei Arbeitsprozesse ausgelöst. Dieses Problem ist aus zwei Hauptgründen tolerierbar:

  1. Das zu diesem Zeitpunkt auftretende Herdenschockphänomen löst nur weniger Arbeitsprozesse aus, was viel besser ist, als jedes Mal alle Arbeitsprozesse zu schockieren.
  2. Der Hauptgrund für dieses Herdenpanikproblem besteht darin, dass der aktuelle Prozess die Sperre freigibt, aber den überwachten Dateideskriptor nicht freigibt. Nach dem Freigeben der Sperre verarbeitet der Worker-Prozess hauptsächlich die Lese- und Schreibereignisse der Clientverbindung und überprüft das Flag. Dieser Prozess ist sehr kurz. Nach der Verarbeitung versucht er, die Sperre zu erhalten und gibt dann den überwachten Dateideskriptor frei. Im Vergleich dazu benötigt der Worker-Prozess, der die Sperre erhält, länger, um auf das Verbindungsaufbauereignis des Clients zu warten, sodass die Wahrscheinlichkeit eines Herdenpanikproblems relativ gering ist.

2. Quellcode-Erklärung

Die Methode zum Initialisieren des Ereignisses des Arbeitsprozesses wird hauptsächlich in der Methode ngx_process_events_and_timers() ausgeführt. Schauen wir uns an, wie diese Methode den gesamten Prozess handhabt. Das Folgende ist der Quellcode dieser Methode:

void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
 ngx_uint_t-Flags;
 ngx_msec_t-Zeitgeber, Delta;

 wenn (ngx_trylock_accept_mutex(Zyklus) == NGX_ERROR) {
  zurückkehren;
 }

 // Beginnen Sie hier mit der Verarbeitung von Ereignissen. Beim kqueue-Modell verweist es auf die Methode ngx_kqueue_process_events().
 // Für das Epoll-Modell verweist es auf die Methode ngx_epoll_process_events() // Die Hauptfunktion dieser Methode besteht darin, die Ereignisliste im entsprechenden Ereignismodell abzurufen und das Ereignis dann zu ngx_posted_accept_events hinzuzufügen
 // Warteschlange oder ngx_posted_events-Warteschlange (leer) ngx_process_events (Zyklus, Timer, Flags);

 // Beginnen Sie hier mit der Verarbeitung des Accept-Ereignisses und übergeben Sie es an die Methode ngx_event_accept() von ngx_event_accept.c;
 ngx_event_process_posted(Zyklus, &ngx_posted_accept_events);

 // Beginnen Sie mit der Freigabe der Sperre, wenn (ngx_accept_mutex_held) {
  ngx_shmtx_unlock(&ngx_accept_mutex);
 }

 // Wenn es nicht in der Ereigniswarteschlange verarbeitet werden muss, verarbeiten Sie das Ereignis direkt. // Bei der Ereignisverarbeitung wird es, wenn es sich um ein Akzeptanzereignis handelt, zur Verarbeitung an die Methode ngx_event_accept() von ngx_event_accept.c übergeben.
 // Wenn es sich um ein Leseereignis handelt, wird es von der Methode ngx_http_wait_request_handler() von ngx_http_request.c behandelt.
 // Ereignisse, die verarbeitet wurden, werden letztendlich von der Methode ngx_http_keepalive_handler() von ngx_http_request.c behandelt.

 // Beginnen Sie mit der Verarbeitung anderer Ereignisse außer dem Akzeptanzereignis ngx_event_process_posted(cycle, &ngx_posted_events);
}

Im obigen Code haben wir den Großteil der Prüfarbeit weggelassen und nur den Skelettcode belassen. Zuerst ruft der Worker-Prozess die Methode ngx_trylock_accept_mutex() auf, um die Sperre zu erhalten. Wenn die Sperre erhalten wurde, hört er auf die Dateideskriptoren, die den einzelnen Ports entsprechen. 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 abschließend die Lese- und Schreibereignisse der verbundenen Clients abgearbeitet. Sehen wir uns an, wie die Methode ngx_trylock_accept_mutex() eine gemeinsame Sperre erwirbt:

ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *Zyklus) {
 // Versuchen Sie, den CAS-Algorithmus zu verwenden, um eine gemeinsame Sperre zu erhalten, wenn (ngx_shmtx_trylock(&ngx_accept_mutex)) {

  // ngx_accept_mutex_held ist 1, was bedeutet, dass der aktuelle Prozess die Sperre erhalten hat, wenn (ngx_accept_mutex_held && ngx_accept_events == 0) {
   gib NGX_OK zurück;
  }

  // Hier wird der Dateideskriptor der aktuellen Verbindung in der Warteschlange des entsprechenden Ereignisses registriert, beispielsweise im Array change_list des kqueue-Modells. // Wenn nginx jeden Worker-Prozess aktiviert, erbt der Worker-Prozess standardmäßig den vom Master-Prozess überwachten Socket-Handle.
  // Dies führt zu einem Problem, d. h. wenn ein Port ein Client-Ereignis aufweist, werden alle Prozesse aufgeweckt, die auf dem Port lauschen.
  // Allerdings kann nur ein Arbeitsprozess das Ereignis erfolgreich verarbeiten, und andere Prozesse stellen nach dem Aufwachen fest, dass das Ereignis abgelaufen ist.
  // Daher wird es weiterhin in den Wartezustand wechseln, der als „Herdenschock“-Phänomen bezeichnet wird.
  // Nginx löst das Herdenpanik-Phänomen hier durch die Methode der gemeinsamen Sperre, d. h. nur der Arbeitsprozess, der die Sperre erhält, kann // Client-Ereignisse verarbeiten. Tatsächlich fügt der Arbeitsprozess jedoch beim Erhalt der Sperre die Abhörereignisse jedes Ports für den aktuellen Arbeitsprozess erneut hinzu.
  // Andere Arbeitsprozesse führen keine Überwachung durch. Das heißt, dass an jedem Port gleichzeitig nur ein Arbeitsprozess lauscht.
  // Dadurch wird das Problem des „Herdenschocks“ vermieden.
  // Die Methode ngx_enable_accept_events() dient hier dazu, Abhörereignisse für jeden Port für den aktuellen Prozess erneut hinzuzufügen.
  wenn (ngx_enable_accept_events(Zyklus) == NGX_ERROR) {
   ngx_shmtx_unlock(&ngx_accept_mutex);
   gib NGX_ERROR zurück;
  }

  // Das Flag zeigt an, dass die Sperre erfolgreich erworben wurde ngx_accept_events = 0;
  ngx_accept_mutex_held = 1;

  gib NGX_OK zurück;
 }

 // Der Sperrvorgang ist zuvor fehlgeschlagen, daher müssen wir den Status von ngx_accept_mutex_held zurücksetzen und die Ereignisse der aktuellen Verbindung löschen, wenn (ngx_accept_mutex_held) {
  // Wenn der ngx_accept_mutex_held des aktuellen Prozesses 1 ist, setze ihn auf 0 zurück und lösche die Überwachungsereignisse // des aktuellen Prozesses auf jedem Port, wenn (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
   gib NGX_ERROR zurück;
  }

  ngx_accept_mutex_held = 0;
 }

 gib NGX_OK zurück;
}

Im obigen Code werden im Wesentlichen drei Dinge erledigt:

  1. Versuchen Sie, mit der CAS-Methode eine gemeinsame Sperre über die Methode ngx_shmtx_trylock() zu erhalten.
  2. Nach dem Erwerb der Sperre wird die Methode ngx_enable_accept_events() aufgerufen, um den Dateideskriptor abzuhören, der dem Zielport entspricht.
  3. Wenn die Sperre nicht erworben wurde, rufen Sie die Methode ngx_disable_accept_events() auf, um den überwachten Dateideskriptor freizugeben.

3. Zusammenfassung

In diesem Artikel werden zunächst die Ursachen des Herdenpanik-Phänomens erläutert, dann wird vorgestellt, wie nginx das Herdenpanik-Problem löst, und abschließend wird erläutert, wie nginx das Herdenpanik-Problem aus der Quellcode-Perspektive behandelt.

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

<<:  Miniprogramm zur Implementierung der Rechnerfunktion

>>:  Beispielanalyse der Auswirkungen des MySQL-Index auf die Sortierung

Artikel empfehlen

So erstellen Sie ein CentOS-Basisimage

Vorwort Derzeit ist das von meiner Firma verwende...

Der HTML-Seitenkopfcode ist völlig klar

Alle folgenden Codes stehen zwischen <head>....

So ermitteln Sie die Größe eines Linux-Systemverzeichnisses mit dem Befehl du

Jeder, der das Linux-System verwendet hat, sollte...

Vue+Swiper realisiert Timeline-Effekt

In diesem Artikel wird der spezifische Code von v...

Zwei Möglichkeiten zum Löschen von Floats in HTML

1. Methode 1 zum Löschen von Floating Legen Sie d...

So behandeln Sie einen Überlauf numerischer MySQL-Typen

Lassen Sie mich Ihnen nun eine Frage stellen. Was...

jQuery zum Erreichen des Sperrfeuereffekts

In diesem Artikel wird der spezifische Code von j...

Detaillierte Erklärung der Text-Fill-Color-Eigenschaft in CSS3

Was bedeutet Textfüllfarbe? Rein wörtlich bedeute...

Zusammenfassung der MySQL-Datenbanknutzungsspezifikationen

Einführung: Ich glaube, dass jeder einige Dokumen...

Automatisierte Schnittstellentests mit Postman

Inhaltsverzeichnis Hintergrundbeschreibung Erstel...

CSS3 erzielt einen unendlichen Scroll-/Karusselleffekt der Liste

Effektvorschau Ideen Scrollen Sie durch die aktue...