Detaillierte Erläuterung des Mechanismus und der Implementierung der Accept-Sperre in Nginx

Detaillierte Erläuterung des Mechanismus und der Implementierung der Accept-Sperre in Nginx

Vorwort

nginx verwendet ein Multiprozessmodell. Wenn eine Anfrage eingeht, sperrt das System den Prozess, um sicherzustellen, dass nur ein Prozess die Anfrage akzeptiert.

Dieser Artikel basiert auf dem Nginx 0.8.55-Quellcode und basiert auf der Analyse des Epoll-Mechanismus

1. Implementierung der Akzeptanzsperre

1.1 Was ist eine Kontosperre?

Wenn wir über die Akzeptanzsperre sprechen, müssen wir das Thundering-Herd-Problem erwähnen.

Das sogenannte Herdenwurfproblem bezieht sich auf einen Multiprozessserver wie Nginx. Wenn dieser nach dem Forking gleichzeitig auf demselben Port lauscht und eine externe Verbindung eingeht, werden alle ruhenden Kindprozesse geweckt, und letztendlich kann nur ein Kindprozess das Akzeptanzereignis erfolgreich verarbeiten, und die anderen Prozesse gehen wieder in den Ruhezustand. Dies führt zu vielen unnötigen Zeitplänen und Kontextwechseln und dieser Overhead ist völlig unnötig.

In den neueren Versionen des Linux-Kernels wurde das durch den Accept-Aufruf selbst verursachte Thundering-Herd-Problem gelöst. In Nginx wird Accept jedoch vom Epoll-Mechanismus gehandhabt, und das durch Accept von Epoll verursachte Thundering-Herd-Problem wurde nicht gelöst (epoll_wait selbst kann nicht unterscheiden, ob das Leseereignis von einem Listen-Socket stammt, sodass alle Prozesse, die auf dieses Ereignis hören, von diesem epoll_wait geweckt werden.), sodass das Accept-Thundering-Herd-Problem von Nginx immer noch eine angepasste Lösung erfordert.

Die Accept-Sperre ist die Lösung von nginx. Im Wesentlichen handelt es sich dabei um eine prozessübergreifende Mutex-Sperre, die sicherstellt, dass nur ein Prozess das Accept-Ereignis abhören kann.

Die Accept-Sperre ist in der Praxis eine prozessübergreifende Sperre. Sie ist eine globale Variable in Nginx und wird wie folgt deklariert:

ngx_shmtx_t ngx_accept_mutex;

Dies ist eine Sperre, die zugewiesen wird, wenn das Ereignismodul initialisiert und in einem gemeinsam genutzten Speicher zwischen Prozessen abgelegt wird, um sicherzustellen, dass alle Prozesse auf diese Instanz zugreifen können. Das Sperren und Entsperren erfolgt durch CAS unter Verwendung atomarer Linux-Variablen. Wenn die Sperre fehlschlägt, wird sie sofort zurückgegeben. Es handelt sich um eine nicht blockierende Sperre. Der Freischaltcode lautet:

statisch ngx_inline ngx_uint_t             
ngx_shmtx_trylock(ngx_shmtx_t *mtx)           
{                    
 Rückgabe (*mtx->lock == 0 und ngx_atomic_cmp_set (mtx->lock, 0, ngx_pid));  
}                    
                    
#define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)   
                    
#define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)

Es ist ersichtlich, dass nach dem fehlgeschlagenen Aufruf von ngx_shmtx_trylock eine sofortige Rückkehr ohne Blockierung erfolgt.

1.2 Wie stellt die Akzeptanzsperre sicher, dass nur ein Prozess neue Verbindungen verarbeiten kann?

Das durch Epoll verursachte Problem der Accept-Sperre lässt sich auch ganz einfach lösen. Sie müssen lediglich sicherstellen, dass gleichzeitig nur ein Prozess das Accept-Epoll-Ereignis registriert.
Der von Nginx verwendete Verarbeitungsmodus ist nichts Besonderes. Die Logik ist ungefähr wie folgt:

Versuchen Sie, die Akzeptanzsperre zu erhalten
Wenn die Übernahme erfolgreich ist:
Registrieren Sie das Accept-Ereignis in epoll
anders:
Heben Sie die Registrierung des Accept-Ereignisses in Epoll auf, um alle Ereignisse zu verarbeiten und die Accept-Sperre aufzuheben.

Dabei bleibt natürlich die Verarbeitung verzögerter Ereignisse außer Acht, worauf wir später noch eingehen werden.

Die Verarbeitung der Akzeptanzsperre sowie die Registrierung und Stornierung von Akzeptanzereignissen in Epoll werden alle in ngx_trylock_accept_mutex durchgeführt. Diese Reihe von Prozessen wird in void ngx_process_events_and_timers(ngx_cycle_t *cycle) ausgeführt, das in der Nginx-Hauptschleife wiederholt aufgerufen wird.

Das heißt, jede Runde der Ereignisverarbeitung konkurriert zunächst um die Akzeptanzsperre. Wenn der Wettbewerb erfolgreich ist, wird das Akzeptanzereignis in epoll registriert. Wenn er fehlschlägt, wird die Registrierung des Akzeptanzereignisses aufgehoben. Nachdem das Ereignis verarbeitet wurde, wird die Akzeptanzsperre freigegeben. Auf diese Weise hört nur ein Prozess auf einen Listen-Socket, wodurch das Thundering-Herd-Problem vermieden wird.

1.3 Welche Anstrengungen unternimmt der Ereignisbehandlungsmechanismus, um zu verhindern, dass die Akzeptanzsperre für längere Zeit belegt ist?

Die Lösung, das Thundering-Herd-Problem mithilfe einer Accept-Sperre zu lösen, scheint sehr schön zu sein, aber wenn die obige Logik vollständig angewendet wird, treten Probleme auf: Wenn der Server sehr ausgelastet ist und viele Ereignisse zu verarbeiten hat, dauert die „Verarbeitung aller Ereignisse“ sehr lange, d. h. ein Prozess belegt die Accept-Sperre für eine lange Zeit und hat keine Zeit, neue Verbindungen zu verarbeiten; andere Prozesse belegen die Accept-Sperre nicht und können auch keine neuen Verbindungen verarbeiten – an diesem Punkt befindet sich die neue Verbindung in einem Zustand, in dem sie von niemandem verarbeitet wird, was zweifellos fatal für die Echtzeitleistung des Dienstes ist.

Um dieses Problem zu lösen, verschiebt Nginx die Ereignisverarbeitung. Das heißt, bei der Verarbeitung von ngx_process_events werden Ereignisse nur in zwei Warteschlangen gestellt:

ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;        
ngx_thread_volatile ngx_event_t *ngx_posted_events;

Verarbeiten Sie nach der Rückkehr zuerst ngx_posted_accept_events, heben Sie die Akzeptanzsperre sofort auf und verarbeiten Sie dann langsam andere Ereignisse.

Das heißt, ngx_process_events verarbeitet nur epoll_wait, und der Verbrauch von Ereignissen erfolgt nach der Freigabe der Accept-Sperre, um die Zeit, die für die Accept-Belegung benötigt wird, zu minimieren und anderen Prozessen genügend Zeit für die Verarbeitung von Accept-Ereignissen zu geben.

Wie wird dies also konkret erreicht? Tatsächlich geht es darum, ein NGX_POST_EVENTS-Flag im Flags-Parameter von static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) zu übergeben und dieses Flag bei der Verarbeitung von Ereignissen zu überprüfen.

Dadurch wird lediglich die langfristige Belegung der Akzeptanzsperre durch Ereignisverbrauch vermieden. Was also, wenn epoll_wait selbst lange dauert? Es ist nicht unmöglich, dass dies passiert. Die Verarbeitung in dieser Hinsicht ist ebenfalls sehr einfach. epoll_wait selbst hat eine Timeout-Periode, also begrenzen Sie einfach seinen Wert. Dieser Parameter wird in der globalen Variable ngx_accept_mutex_delay gespeichert.

Unten finden Sie den Implementierungscode von ngx_process_events_and_timers, der Ihnen eine ungefähre Vorstellung von der zugehörigen Verarbeitung geben kann:

Leere                   
ngx_process_events_and_timers(ngx_cycle_t *Zyklus)        
{                    
 ngx_uint_t-Flags;               
 ngx_msec_t-Zeitgeber, Delta;             
    
	
	/* Etwas Code für die Verarbeitung von Zeitereignissen weglassen*/                    
 // Dies ist die Zeit, um die Lastausgleichssperre zu verarbeiten und die Sperre zu akzeptieren, wenn (ngx_use_accept_mutex) {            
  // Wenn der Wert des Load Balancing Tokens größer als 0 ist, bedeutet dies, dass die Last voll ist und die Annahme nicht mehr verarbeitet wird. Gleichzeitig wird der Wert um eins reduziert, wenn (ngx_accept_disabled > 0) {           
   ngx_accept_disabled--;            
                    
  } anders {                
   // Versuchen Sie, die Akzeptanzsperre zu erhalten, wenn (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {    
    zurückkehren;              
   }                 
                    
   // Nachdem du die Sperre erhalten hast, füge das Flag zum Post-Flag hinzu, um die Verarbeitung aller Ereignisse zu verschieben. // Um ​​zu vermeiden, dass die Accept-Sperre zu lange belegt wird, if (ngx_accept_mutex_held) {          
    Flags |= NGX_POST_EVENTS;          
                    
   } anders {               
    wenn (Timer == NGX_TIMER_INFINITE        
     || Timer > ngx_accept_mutex_delay)       
    {                
     Timer = ngx_accept_mutex_delay; // Warten Sie höchstens ngx_accept_mutex_delay Millisekunden, um zu verhindern, dass die Akzeptanzsperre zu lange belegt ist}                
   }                 
  }                  
 }                    
 delta = ngx_aktuelle_msec;             
                    
 // Rufen Sie process_events des Ereignisverarbeitungsmoduls auf, um eine epoll_wait-Methode zu verarbeiten (void) ngx_process_events(cycle, timer, flags);       
                    
 delta = ngx_current_msec - delta; //Berechnen Sie die für die Verarbeitung von Ereignissen benötigte Zeit ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,       
     „Timer-Delta: %M“, delta);         
                    
 // Wenn ein verzögertes Akzeptanzereignis vorliegt, dann verschiebe die Verarbeitung dieses Ereignisses, wenn (ngx_posted_accept_events) {           
  ngx_event_process_posted(Zyklus, &ngx_posted_accept_events);   
 }                   
                    
 // Akzeptanzsperre aufheben, wenn (ngx_accept_mutex_held) {            
  ngx_shmtx_unlock(&ngx_accept_mutex);         
 }                   
                    
 // Alle Timeout-Ereignisse verarbeiten if (delta) {                
  ngx_event_expire_timers();            
 }                   
                    
 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, Zyklus->Protokoll, 0,       
     "gepostete Ereignisse %p", ngx_posted_events);      
                    
 wenn (ngx_posted_events) {             
  wenn (ngx_threaded) {             
   ngx_wakeup_worker_thread(Zyklus);         
                    
  } anders {                
   // Alle aufgeschobenen Ereignisse verarbeiten ngx_event_process_posted(cycle, &ngx_posted_events);    
  }                  
 }                   
}

Werfen wir einen Blick auf die zugehörige Verarbeitung von ngx_epoll_process_events:

  // Ereignisse lesen if ((revents & EPOLLIN) && rev->active) {
   wenn ((flags & NGX_POST_THREAD_EVENTS) und !rev->accept) {
    rev->gepostet_bereit = 1;

   } anders {
    rev->bereit = 1;
   }                                        
   wenn (Flags & NGX_POST_EVENTS) {
    Warteschlange = (ngx_event_t **) (rev->akzeptieren?
        &ngx_posted_accept_events: &ngx_posted_events);
    ngx_locked_post_event(rev, Warteschlange);
   } anders {
    rev->handler(rev);
   }
  }                                         
  wev = c->schreiben;

  // Ereignis schreiben, wenn ((revents & EPOLLOUT) && wev->active) {
   wenn (Flags & NGX_POST_THREAD_EVENTS) {
    wev->gepostet_bereit = 1;
   } anders {
    wir->bereit = 1;
   }

   wenn (Flags & NGX_POST_EVENTS) {
    ngx_locked_post_event(wev, &ngx_posted_events);
   } anders {
    wev->handler(wev);
   }
  }

Die Verarbeitung ist auch relativ einfach. Wenn die Akzeptanzsperre erhalten wird, wird ein NGX_POST_EVENTS-Flag angezeigt und in die entsprechende Warteschlange gestellt. Wenn nicht, wird das Ereignis direkt verarbeitet.

Zusammenfassen

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Lernwert für Ihr Studium oder Ihre Arbeit hat. Wenn Sie Fragen haben, können Sie eine Nachricht hinterlassen. Vielen Dank für Ihre Unterstützung von 123WORDPRESS.COM.

Das könnte Sie auch interessieren:
  • Lösen Sie das Problem, dass Nginx nach der Konfiguration von proxy_pass 404 zurückgibt
  • Lösung für den Konfigurationsfehler des Nginx-SSL-Zertifikats
  • Ursachen und Lösungen für den Nginx 502 Bad Gateway-Fehler
  • Proxy_pass-Methode in mehreren if in Nginx-Standorten
  • Beispielcode für die Nginx-Konfiguration zum Herunterladen von Dateien
  • Implementierung mehrerer Nginx-Standorte, die alle Anfragen weiterleiten oder auf statische Ressourcendateien zugreifen
  • So zeigen Sie den Nginx-Konfigurationsdateipfad und den Ressourcendateipfad an
  • Detaillierte Erläuterung der Implementierung der Nginx-Prozesssperre

<<:  So fragen Sie doppelte Daten in einer MySQL-Tabelle ab

>>:  Die Verwendung von setState in React und die Verwendung von synchron und asynchron

Artikel empfehlen

Mehrere Möglichkeiten zum Zentrieren einer Box in der Webentwicklung

1. Notieren Sie mehrere Methoden zum Zentrieren d...

Linux-Grundlagen-Tutorial: Sonderberechtigungen SUID, SGID und SBIT

Vorwort Für Datei- oder Verzeichnisberechtigungen...

W3C Tutorial (2): W3C Programme

Der W3C-Standardisierungsprozess ist in 7 verschi...

CSS3 zum Erzielen eines dynamischen Hintergrundverlaufseffekts

Beim Erlernen von CSS3 geht es mehr darum, sich m...

So konfigurieren Sie Nginx, um die Zugriffshäufigkeit derselben IP zu begrenzen

1. Fügen Sie den folgenden Code zu http{} in ngin...

VMware vSAN - Zusammenfassung der ersten Schritte

1. Hintergrund 1. Stellen Sie kurz den Shared Sto...

JavaScript imitiert den Taobao-Lupeneffekt

In diesem Artikel wird der spezifische Code für J...

Vue kapselt die öffentliche Funktionsmethode zum Exportieren von Excel-Daten

vue+element UI kapselt eine öffentliche Funktion ...

Detaillierte Beispiele zur Ajax-Verwendung in js und jQuery

Inhaltsverzeichnis Natives JS So senden Sie eine ...

Wie implementiert die MySQL-Datenbank die XA-Spezifikation?

MySQL-Konsistenzprotokoll Was passiert mit nicht ...

MySQL-Datentabellenpartitionierungsstrategie und Vor- und Nachteileanalyse

Inhaltsverzeichnis Warum brauchen wir Partitionen...

Detaillierte Erläuterung des Nginx-Forward-Proxys und des Reverse-Proxys

Inhaltsverzeichnis Weiterleitungsproxy Nginx-Reve...

Mehrere Möglichkeiten zum Ändern der SELECT-Optionen in einer HTML-Dropdown-Box

Nachdem das Formular übermittelt wurde, wird die z...

Beispielanalyse der Verwendung gespeicherter MySQL-Prozeduren

Dieser Artikel beschreibt die Verwendung gespeich...