Detaillierte Erläuterung des Nginx-Strombegrenzungsmoduls in der Nginx-Quellcode-Recherche

Detaillierte Erläuterung des Nginx-Strombegrenzungsmoduls in der Nginx-Quellcode-Recherche

Systeme mit hoher Parallelität verfügen über drei leistungsstarke Tools: Caching, Downgrading und Strombegrenzung.

Der Zweck der Ratenbegrenzung besteht darin, das System durch Begrenzung der Rate gleichzeitiger Zugriffe/Anfragen zu schützen. Sobald die Ratenbegrenzung erreicht ist, kann das System den Dienst verweigern (direkt zur Fehlerseite), in eine Warteschlange stellen (Blitzverkauf) oder ein Downgrade durchführen (Sicherungsdaten oder Standarddaten zurückgeben).

Zu den gängigen Strombegrenzungsmethoden für Systeme mit hoher Parallelität gehören: Begrenzung der Gesamtzahl gleichzeitiger Verbindungen (Datenbankverbindungspool), Begrenzung der Anzahl momentaner gleichzeitiger Verbindungen (wie das Modul „limit_conn“ von nginx, das zur Begrenzung der Anzahl momentaner gleichzeitiger Verbindungen verwendet wird) und Begrenzung der Durchschnittsrate innerhalb eines Zeitfensters (Modul „limit_req“ von nginx, das zur Begrenzung der Durchschnittsrate pro Sekunde verwendet wird).

Darüber hinaus können Sie den Datenfluss auch basierend auf der Anzahl der Netzwerkverbindungen, dem Netzwerkverkehr, der CPU- oder Speicherauslastung usw. begrenzen.

1. Strombegrenzungsalgorithmus

Der einfachste und gröbste Algorithmus zur Strombegrenzung ist die Zählermethode, und die am häufigsten verwendeten sind der Leaky-Bucket-Algorithmus und der Token-Bucket-Algorithmus.

1.1 Zähler

Die Zählermethode ist der einfachste und am leichtesten zu implementierende Strombegrenzungsalgorithmus. Beispielsweise legen wir fest, dass für Schnittstelle A die Anzahl der Besuche pro Minute 100 nicht überschreiten darf.

Dann können wir einen Zähler einstellen, dessen Gültigkeitsdauer 1 Minute beträgt (dh der Zähler wird jede Minute auf 0 zurückgesetzt). Jedes Mal, wenn eine Anfrage eingeht, erhöht sich der Zähler um 1. Wenn der Wert des Zählers größer als 100 ist, bedeutet dies, dass die Anzahl der Anfragen zu groß ist.

Obwohl dieser Algorithmus einfach ist, weist er ein sehr schwerwiegendes Problem auf, nämlich das kritische Problem.

Wie in der folgenden Abbildung gezeigt, treffen 100 Anfragen kurz vor 1:00 Uhr ein, der Zähler wird um 1:00 Uhr zurückgesetzt und 100 Anfragen treffen kurz nach 1:00 Uhr ein. Offensichtlich überschreitet der Zähler 100 nicht und alle Anfragen werden nicht blockiert.

Allerdings hat die Zahl der Anfragen in diesem Zeitraum 200 erreicht und liegt damit deutlich über 100.

1.2 Leaky-Bucket-Algorithmus

Wie in der folgenden Abbildung gezeigt, gibt es einen undichten Eimer mit einem festgelegten Fassungsvermögen und die Wassertropfen fließen mit einer konstanten, festgelegten Geschwindigkeit heraus. Wenn der Eimer leer ist, fließen keine Wassertropfen heraus. Die Geschwindigkeit des in den undichten Eimer einfließenden Wassers ist beliebig. Wenn die einfließende Wassermenge das Fassungsvermögen des Eimers überschreitet, läuft das einfließende Wasser über (wird verworfen).

Es ist ersichtlich, dass der Leaky-Bucket-Algorithmus die Anforderungsrate von Natur aus begrenzt und zur Verkehrsformung und Strombegrenzungssteuerung verwendet werden kann.

1.3 Token-Bucket-Algorithmus

Ein Token-Bucket ist ein Bucket, in dem Token mit fester Kapazität gespeichert werden. Token werden dem Bucket mit einer festen Rate r hinzugefügt. Der Bucket kann maximal b Token speichern. Wenn der Bucket voll ist, werden neu hinzugefügte Token verworfen.

Wenn eine Anfrage eingeht, wird versucht, ein Token aus dem Bucket zu erhalten. Wenn eines vorhanden ist, wird die Bearbeitung der Anfrage fortgesetzt. Wenn nicht, wird es in die Warteschlange gestellt oder direkt verworfen.

Es kann festgestellt werden, dass die Abflussrate des Leaky-Bucket-Algorithmus konstant oder 0 ist, während die Abflussrate des Token-Bucket-Algorithmus größer als r sein kann;

2. Grundkenntnisse von nginx

Nginx verfügt derzeit über zwei Hauptbegrenzungsmethoden: Begrenzung durch die Anzahl der Verbindungen (ngx_http_limit_conn_module) und Begrenzung durch die Anforderungsrate (ngx_http_limit_req_module).

Bevor Sie das Strombegrenzungsmodul lernen, müssen Sie auch die Verarbeitung von HTTP-Anforderungen durch nginx, den Ereignisverarbeitungsablauf von nginx usw. verstehen.

2.1HTTP-Anforderungsverarbeitung

Nginx unterteilt den HTTP-Anforderungsverarbeitungsablauf in 11 Phasen. Die meisten HTTP-Module fügen einer bestimmten Phase ihre eigenen Handler hinzu (vier davon können keine benutzerdefinierten Handler hinzufügen). Bei der Verarbeitung von HTTP-Anforderungen ruft Nginx alle Handler nacheinander auf.

Typdefinition Aufzählung {
 NGX_HTTP_POST_READ_PHASE = 0, //Derzeit registriert nur das Realip-Modul den Handler (nützlich, wenn Nginx als Proxyserver verwendet wird; das Backend nutzt dies, um die ursprüngliche IP des Clients abzurufen)
 
 NGX_HTTP_SERVER_REWRITE_PHASE, //Die Rewrite-Direktive ist im Serverblock so konfiguriert, dass die URL neu geschrieben wird
 
 NGX_HTTP_FIND_CONFIG_PHASE, //Übereinstimmenden Ort finden; Handler kann nicht angepasst werden;
 NGX_HTTP_REWRITE_PHASE, //Die Umschreibeanweisung wird im Standortblock konfiguriert, um die URL umzuschreiben
 NGX_HTTP_POST_REWRITE_PHASE, // Überprüfen Sie, ob eine URL-Umschreibung stattgefunden hat. Wenn ja, kehren Sie zur Phase FIND_CONFIG zurück. Der Handler kann nicht angepasst werden.
 
 NGX_HTTP_PREACCESS_PHASE, // Zugriffskontrolle, das aktuelle Begrenzungsmodul registriert den Handler für diese PhaseNGX_HTTP_ACCESS_PHASE, // ZugriffsrechtekontrolleNGX_HTTP_POST_ACCESS_PHASE, // Entsprechend der Phase der Zugriffsrechtekontrolle entsprechend behandeln, der Handler kann nicht angepasst werden.
 
 NGX_HTTP_TRY_FILES_PHASE, //Diese Phase tritt nur auf, wenn die try_files-Direktive konfiguriert ist; der Handler kann nicht angepasst werden;
 NGX_HTTP_CONTENT_PHASE, //Phase der Inhaltsgenerierung, Antwort an Client zurückgeben NGX_HTTP_LOG_PHASE //Protokolldatensatz} ngx_http_phases;

Nginx verwendet die Struktur ngx_module_s, um ein Modul darzustellen, wobei das Feld ctx ein Zeiger auf die Modulkontextstruktur ist; die HTTP-Modulkontextstruktur von nginx ist wie folgt (die Felder der Kontextstruktur sind alle Funktionszeiger):

typedef-Struktur {
 ngx_int_t (*Vorkonfiguration)(ngx_conf_t *cf);
 ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //Diese Methode registriert den Handler für die entsprechende Phase void *(*create_main_conf)(ngx_conf_t *cf); //Hauptkonfiguration im HTTP-Block char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
 
 void *(*create_srv_conf)(ngx_conf_t *cf); //Serverkonfiguration char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
 
 void *(*create_loc_conf)(ngx_conf_t *cf); //Standortkonfiguration char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

Am Beispiel des Moduls ngx_http_limit_req_module wird die Postkonfigurationsmethode einfach wie folgt implementiert:

statisches ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf)
{
 h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
 
 *h = ngx_http_limit_req_handler; // Aktuelle Begrenzungsmethode des Moduls ngx_http_limit_req_module; wenn nginx HTTP-Anfragen verarbeitet, ruft es diese Methode auf, um zu bestimmen, ob die Anfrage fortgesetzt oder abgelehnt werden soll. Return NGX_OK;
}

2.2 Kurze Einführung in die Nginx-Ereignisverarbeitung

Gehen Sie davon aus, dass Nginx Epoll verwendet.

Nginx muss alle betroffenen FDS bei Epoll registrieren und die Methodendeklaration wie folgt hinzufügen:

statisches ngx_int_t ngx_epoll_add_event (ngx_event_t *ev, ngx_int_t Ereignis, ngx_uint_t Flags);

Der erste Parameter der Methode ist ein Zeiger auf die Struktur ngx_event_t, die ein relevantes Lese- oder Schreibereignis darstellt. Nginx kann einen Timeout-Timer für das Ereignis festlegen, um Ereignis-Timeouts zu verarbeiten. Die Definition lautet wie folgt:

Struktur ngx_event_s {
 
 ngx_event_handler_pt handler; //Funktionszeiger: Ereignisverarbeitungsfunktion ngx_rbtree_node_t timer; //Timeout-Timer, gespeichert im Rot-Schwarz-Baum (der Schlüssel des Knotens ist das Timeout des Ereignisses)
 
 unsigned timedout:1; //Aufzeichnen, ob das Ereignis abgelaufen ist};

Im Allgemeinen wird epoll_wait in einer Schleife aufgerufen, um alle FDS zu überwachen und die auftretenden Lese- und Schreibereignisse zu verarbeiten. epoll_wait ist ein blockierender Aufruf. Der letzte Parameter timeout ist die Timeout-Periode. Wenn innerhalb der maximalen Blockierungszeit kein Ereignis auftritt, wird die Methode zurückgegeben.

Beim Festlegen des Timeouts sucht nginx nach dem nächstgelegenen Knoten im Rot-Schwarz-Baum des oben erwähnten Timeout-Timers, dessen Zeit abgelaufen ist, und verwendet ihn als Timeout von epoll_wait, wie im folgenden Code gezeigt.

ngx_msec_t ngx_event_find_timer(void)
{
 Knoten = ngx_rbtree_min(Wurzel, Wächter);
 Timer = (ngx_msec_int_t) (Knoten -> Schlüssel – ngx_current_msec);
 
 Rückgabe (ngx_msec_t) (Timer > 0? Timer: 0);
}

Gleichzeitig prüft nginx am Ende jeder Schleife, ob ein Ereignis im Rot-Schwarz-Baum abgelaufen ist. Wenn es abgelaufen ist, wird timeout=1 markiert und der Ereignishandler aufgerufen.

ungültig ngx_event_expire_timers(void)
{
 für ( ;; ) {
  Knoten = ngx_rbtree_min(Wurzel, Wächter);
 
  if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) { //Das aktuelle Ereignis ist abgelaufen ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
 
   ev->timedout = 1;
 
   ev->handler(ev);
 
   weitermachen;
  }
 
  brechen;
 }
}

Nginx verwendet die obige Methode, um die Verarbeitung von Socket-Ereignissen und zeitgesteuerten Ereignissen zu implementieren.

ngx_http_limit_req_module Modulanalyse

Das Modul ngx_http_limit_req_module begrenzt die Anforderungsrate, dh es begrenzt die Anforderungsrate des Benutzers innerhalb eines bestimmten Zeitraums und verwendet den Token-Bucket-Algorithmus.

3.1 Konfigurationshinweise

Das Modul ngx_http_limit_req_module bietet Benutzern die folgenden Konfigurationsanweisungen zum Konfigurieren der aktuellen Begrenzungsstrategie

//Jede Konfigurationsanweisung enthält hauptsächlich zwei Felder: Name, Analysekonfiguration, Verarbeitungsmethode static ngx_command_t ngx_http_limit_req_commands[] = {
 
 //Allgemeine Verwendung: limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
 //$binary_remote_addr stellt die IP des Remote-Clients dar;
 //Zone konfiguriert einen Speicherplatz (Speicherplatz muss zugewiesen werden, um die Zugriffsrate jedes Clients aufzuzeichnen, und das Timeout-Speicherplatzlimit wird mithilfe des LRU-Algorithmus aufgehoben. Beachten Sie, dass dieser Speicherplatz im gemeinsam genutzten Speicher zugewiesen wird und von allen Arbeitsprozessen aufgerufen werden kann).
 //rate gibt die Ratenbegrenzung an, in diesem Fall 1qps
 { ngx_string("Anforderungslimitzone"),
  ngx_http_limit_req_zone,
  },
 
 //Verwendung: limit_req zone=one burst=5 nodelay;
 //Zone gibt an, welcher gemeinsam genutzte Speicherplatz verwendet werden soll//Werden Anfragen, die diese Rate überschreiten, direkt verworfen? Die Burst-Konfiguration wird zur Handhabung von Burst-Verkehr verwendet. Sie gibt die maximale Anzahl in die Warteschlange gestellter Anfragen an. Wenn die Client-Anforderungsrate den aktuellen Grenzwert überschreitet, wird die Anfrage in die Warteschlange gestellt. Nur Anfragen, die den Burst überschreiten, werden direkt abgelehnt.
 //nodelay muss zusammen mit Burst verwendet werden. Zu diesem Zeitpunkt werden die in der Warteschlange befindlichen Anfragen zuerst verarbeitet. Andernfalls kann es sein, dass die Zeit des Clients abgelaufen ist, bis der Server die Verarbeitung abgeschlossen hat, falls diese Anfragen immer noch mit der begrenzten Rate verarbeitet werden. { ngx_string("limit_req"),
  ngx_http_limit_req,
  },
 
 //Wenn die Anforderung begrenzt ist, die Protokollierungsebene; Verwendung: limit_req_log_level info | Hinweis | Warnung | Fehler;
 { ngx_string("limit_req_log_level"),
  ngx_conf_set_enum_slot,
  },
 
 //Wenn die Anforderung begrenzt ist, wird der Statuscode an den Client zurückgegeben. Verwendung: limit_req_status 503
 { ngx_string("limit_req_status"),
  ngx_conf_set_num_slot,
 },
};

Hinweis: $binary_remote_addr ist eine von nginx bereitgestellte Variable, die direkt in der Konfigurationsdatei verwendet werden kann. nginx stellt auch viele Variablen bereit, die im Array ngx_http_core_variables in der Datei ngx_http_variable.c zu finden sind:

statische ngx_http_variable_t ngx_http_core_variables[] = {
 
 { ngx_string("http_host"), NULL, ngx_http_variable_header,
  offsetof(ngx_http_request_t, headers_in.host), 0, 0 },
 
 { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,
  Offset von (ngx_http_request_t, Header in.user_agent), 0, 0 },
 …………
}

3.2 Quellcode-Analyse

Das ngx_http_limit_req_module registriert die Methode ngx_http_limit_req_handler während des Postkonfigurationsprozesses in der Phase NGX_HTTP_PREACCESS_PHASE der HTTP-Verarbeitung.

ngx_http_limit_req_handler führt den Leaky-Bucket-Algorithmus aus, um zu bestimmen, ob die konfigurierte aktuelle Grenzrate überschritten wurde, und verwirft sie dann, stellt sie in die Warteschlange oder übergibt sie.

Wenn der Benutzer die erste Anforderung stellt, wird ein neuer Datensatz hinzugefügt (der hauptsächlich die Anzahl der Zugriffe und die Zugriffszeit aufzeichnet) und der Hashwert der Client-IP-Adresse (Konfiguration $binary_remote_addr) wird als Schlüssel zum Speichern im Rot-Schwarz-Baum (für eine schnelle Suche) und auch in der LRU-Warteschlange verwendet (wenn der Speicherplatz nicht ausreicht, wird der Datensatz entfernt und jedes Mal vom Ende gelöscht). Wenn der Benutzer die nächste Anforderung stellt, wird der Datensatz im Rot-Schwarz-Baum gesucht und aktualisiert und der Datensatz wird an den Anfang der LRU-Warteschlange verschoben.

3.2.1 Datenstruktur

limit_req_zone konfiguriert den Speicherplatz (Name und Größe), die Grenzgeschwindigkeit und die Grenzvariablen (Client-IP usw.), die vom aktuellen Begrenzungsalgorithmus benötigt werden. Die Struktur ist wie folgt:

typedef-Struktur {
 ngx_http_limit_req_shctx_t *sh;
 ngx_slab_pool_t *shpool; //Speicherpool ngx_uint_t Rate; //Aktuelle Grenzgeschwindigkeit (qps multipliziert mit 1000 für die Speicherung)
 ngx_int_t index; // Variablenindex (nginx stellt eine Reihe von Variablen bereit, vom Benutzer konfigurierter Strombegrenzungsvariablenindex)
 ngx_str_t var; //aktueller Begrenzungsvariablenname ngx_http_limit_req_node_t *node;
} ngx_http_limit_req_ctx_t;
 
//Gleichzeitig der gemeinsam genutzte Speicherplatz struct ngx_shm_zone_s {
 void *data; //data zeigt auf die Struktur ngx_http_limit_req_ctx_t ngx_shm_t shm; //gemeinsam genutzter Speicherplatz ngx_shm_zone_init_pt init; //Funktionszeiger für Initialisierungsmethode void *tag; //zeigt auf die Struktur ngx_http_limit_req_module };

limit_req konfiguriert den für die aktuelle Begrenzung verwendeten Speicherplatz, die Warteschlangengröße und ob dringend darauf reagiert werden soll. Die Struktur ist wie folgt:

typedef-Struktur {
 ngx_shm_zone_t *shm_zone; //Gemeinsam genutzter Speicherplatz ngx_uint_t burst; //Warteschlangengröße ngx_uint_t nodelay; //Gibt an, ob die Verarbeitung dringend erfolgen soll, wenn sich Anforderungen in der Warteschlange befinden, und wird mit Burst verwendet (falls konfiguriert, werden Anforderungen in der Warteschlange dringend verarbeitet, andernfalls werden sie weiterhin entsprechend der aktuellen Grenzgeschwindigkeit verarbeitet)
} ngx_http_limit_req_limit_t; 

Wie bereits erwähnt, werden Benutzerzugriffsdatensätze sowohl im Rot-Schwarz-Baum als auch in der LRU-Warteschlange gespeichert. Die Struktur ist wie folgt:

//Datensatzstruktur typedef struct {
 u_char Farbe;
 u_char Dummy;
 u_short len; //Datenlänge ngx_queue_t queue; 
 ngx_msec_t last; //Letzte Zugriffszeit ngx_uint_t excess; //Die aktuelle Anzahl der noch zu verarbeitenden Anfragen (nginx verwendet dies, um den Algorithmus zur Strombegrenzung des Token-Buckets zu implementieren)
 ngx_uint_t count; //Gesamtzahl solcher Datensatzanfragen u_char data[1]; //Dateninhalt (zuerst per Schlüssel (Hashwert) suchen, dann vergleichen, ob der Dateninhalt gleich ist)
} ngx_http_limit_req_node_t;
 
//Rot-schwarzer Baumknoten, Schlüssel ist der Hash-Wert der vom Benutzer konfigurierten Strombegrenzungsvariablen;
Struktur ngx_rbtree_node_s {
 ngx_rbtree_key_t-Schlüssel;
 ngx_rbtree_node_t *links;
 ngx_rbtree_node_t *rechts;
 ngx_rbtree_node_t *übergeordnet;
 u_char Farbe;
 u_char-Daten;
};
 
 
typedef-Struktur {
 ngx_rbtree_t rbtree; //Rot-Schwarz-Baum ngx_rbtree_node_t sentinel; //NIL-Knoten ngx_queue_t queue; //LRU-Warteschlange } ngx_http_limit_req_shctx_t;
 
//Die Warteschlange hat nur vorherige und nächste Zeiger struct ngx_queue_s {
 ngx_queue_t *vorherige;
 ngx_queue_t *nächstes;
};

Gedanke 1: Der Datensatz ngx_http_limit_req_node_t bildet durch die vorherigen und nächsten Zeiger eine bidirektionale verknüpfte Liste, um die LRU-Warteschlange zu implementieren. Der zuletzt aufgerufene Knoten wird immer in den Kopf der verknüpften Liste eingefügt und der Knoten wird aus dem Ende gelöscht, wenn er eliminiert wird.

ngx_http_limit_req_ctx_t *ctx;
ngx_queue_t *q;
 
q = ngx_queue_last(&ctx->sh->Warteschlange);
 
lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);//Diese Methode erhält die erste Adresse der ngx_http_limit_req_node_t-Struktur von ngx_queue_t und wird wie folgt implementiert:
 
#define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link)) //Die Adresse des Warteschlangenfelds abzüglich ihres Offsets in der Struktur ist die erste Adresse der Struktur

Überlegung 2: Der aktuelle Begrenzungsalgorithmus verwendet zuerst den Schlüssel, um den Knoten des rot-schwarzen Baums zu finden und den entsprechenden Datensatz zu finden. Wie verknüpft sich der Knoten des rot-schwarzen Baums mit der Struktur des Datensatzes ngx_http_limit_req_node_t? Im Modul ngx_http_limit_req_module finden Sie den folgenden Code:

Größe = Offset von (ngx_rbtree_node_t, Farbe) // Speicher für neue Datensätze zuweisen und den erforderlichen Speicherplatz berechnen Größe + Offset von (ngx_http_limit_req_node_t, Daten)
  + Länge;
 
Knoten = ngx_slab_alloc_locked(ctx->shpool, Größe);
 
Knoten->Schlüssel = Hash;
 
lr = (ngx_http_limit_req_node_t *) &node->color; //Farbe ist vom Typ u_char, warum kann sie zwangsweise in den Zeigertyp ngx_http_limit_req_node_t konvertiert werden?
 
lr->Länge = (u_char)Länge;
lr->Überschuss = 0;
 
ngx_memcpy(lr->Daten, Daten, Länge);
 
ngx_rbtree_insert(&ctx->sh->rbtree, Knoten);
 
ngx_queue_insert_head(&ctx->sh->Warteschlange, &lr->Warteschlange);

Durch die Analyse des obigen Codes sind die Farb- und Datenfelder der Struktur ngx_rbtree_node_s tatsächlich bedeutungslos. Die Lebensform der Struktur unterscheidet sich von der endgültigen Speicherform. Nginx verwendet schließlich die folgende Speicherform, um jeden Datensatz zu speichern;

3.2.2 Strombegrenzungsalgorithmus

Wie oben erwähnt, wird die Methode ngx_http_limit_req_handler während des Postkonfigurationsprozesses in der Phase NGX_HTTP_PREACCESS_PHASE der HTTP-Verarbeitung registriert.

Daher wird bei der Verarbeitung von HTTP-Anfragen die Methode ngx_http_limit_req_handler ausgeführt, um zu bestimmen, ob eine aktuelle Begrenzung erforderlich ist.

3.2.2.1 Implementierung des Leaky-Bucket-Algorithmus

Benutzer können mehrere Strombegrenzungen gleichzeitig konfigurieren. Daher muss nginx bei HTTP-Anfragen alle Strombegrenzungsrichtlinien durchlaufen, um festzustellen, ob eine Strombegrenzung erforderlich ist.

Die Methode ngx_http_limit_req_lookup implementiert den Leaky-Bucket-Algorithmus und gibt drei Ergebnisse zurück:

  • NGX_BUSY: Die Anforderungsrate überschreitet die aktuelle Grenzkonfiguration und die Anforderung wird abgelehnt.
  • NGX_AGAIN: Die Anforderung hat die aktuelle Überprüfung der Strombegrenzungsstrategie bestanden und fährt mit der Überprüfung der nächsten Strombegrenzungsstrategie fort.
  • NGX_OK: Die Anfrage hat die Überprüfung aller aktuellen Begrenzungsstrategien bestanden und kann mit der nächsten Phase fortfahren.
  • NGX_ERROR: Fehler
//Limit, aktuelle Begrenzungsstrategie; Hash, zeichnet den Hash-Wert des Schlüssels auf; Daten, zeichnet den Dateninhalt des Schlüssels auf; Länge, zeichnet die Datenlänge des Schlüssels auf; Ep, Anzahl ausstehender Anfragen; Konto, ob es sich um die letzte aktuelle Begrenzungsstrategie handelt (static ngx_int_t ngx_http_limit_req_limit_t *Limit, ngx_uint_t Hash, u_char *Daten, size_t Länge, ngx_uint_t *Ep, ngx_uint_t Konto)
{
 //Rot-Schwarz-Baum-Suche nach angegebener Grenze while (node ​​​​!= sentinel) {
 
  wenn (Hash < Knoten->Schlüssel) {
   Knoten = Knoten->links;
   weitermachen;
  }
 
  wenn (Hash > Knoten->Schlüssel) {
   Knoten = Knoten->rechts;
   weitermachen;
  }
 
  //Die Hashwerte sind gleich, vergleichen Sie, ob die Daten gleich sind lr = (ngx_http_limit_req_node_t *) &node->color;
 
  rc = ngx_memn2cmp(Daten, lr->Daten, Länge, (Größe_t) lr->Länge);
  //Finden ob (rc == 0) {
   ngx_queue_remove(&lr->Warteschlange);
   ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); //Verschiebe den Datensatz an den Anfang der LRU-Warteschlange ms = (ngx_msec_int_t) (now - lr->last); //Aktuelle Zeit minus letzte Zugriffszeit excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; //Zu verarbeitende Anfragen - aktuelle Grenzrate * Zeitspanne + 1 Anfrage (Rate, Anzahl der Anfragen usw. werden mit 1000 multipliziert)
 
   wenn (Überschuss < 0) {
    Überschuss = 0;
   }
 
   *ep = Überschuss;
 
   // Die Anzahl der ausstehenden Anfragen überschreitet den Burst (Größe der Warteschlange), und NGX_BUSY wird zurückgegeben, um die Anfrage abzulehnen (wenn der Burst nicht konfiguriert ist, ist der Wert 0).
   wenn ((ngx_uint_t) Überschuss > Grenze->Burst) {
    gib NGX_BUSY zurück;
   }
 
   if (account) { //Wenn dies die letzte aktuelle Begrenzungsrichtlinie ist, aktualisiere die letzte Zugriffszeit, die Anzahl der ausstehenden Anfragen und gib NGX_OK zurück
    lr->Überschuss = Überschuss;
    lr->last = jetzt;
    gib NGX_OK zurück;
   }
   //Anzahl der Besuche erhöhen lr->count++;
 
   ctx->Knoten = lr;
 
   return NGX_AGAIN; //Nicht die letzte Strombegrenzungsstrategie, gib NGX_AGAIN zurück und überprüfe weiterhin die nächste Strombegrenzungsstrategie}
 
  Knoten = (rc < 0)? Knoten->links : Knoten->rechts;
 }
 
 //Wenn kein Knoten gefunden wird, muss ein neuer Datensatz erstellt werden *ep = 0;
 //Informationen zur Berechnungsmethode für die Speicherplatzgröße finden Sie in der Datenstruktur in Abschnitt 3.2.1 size = offsetof(ngx_rbtree_node_t, color)
   + Offset von (ngx_http_limit_req_node_t, Daten)
   + Länge;
 //Versuche, Datensätze zu eliminieren (LRU)
 ngx_http_limit_req_expire(ctx, 1);
 
  
 node = ngx_slab_alloc_locked(ctx->shpool, size); //Speicherplatz zuweisen if (node ​​​​== NULL) { //Nicht genügend Speicherplatz, Zuweisung fehlgeschlagen ngx_http_limit_req_expire(ctx, 0); //Erzwungene Löschung von Datensätzen node = ngx_slab_alloc_locked(ctx->shpool, size); //Speicherplatz zuweisen if (node ​​​​== NULL) { //Zuweisung fehlgeschlagen, return NGX_ERROR
   gib NGX_ERROR zurück;
  }
 }
 
 node->key = hash; //Wert zuweisen lr = (ngx_http_limit_req_node_t *) &node->color;
 lr->Länge = (u_char)Länge;
 lr->Überschuss = 0;
 ngx_memcpy(lr->Daten, Daten, Länge);
 
 ngx_rbtree_insert(&ctx->sh->rbtree, node); //Datensätze in den Rot-Schwarz-Baum und die LRU-Warteschlange einfügen ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
 
 if (account) { //Wenn dies die letzte aktuelle Begrenzungsrichtlinie ist, aktualisiere die letzte Zugriffszeit, die Anzahl der ausstehenden Anfragen und gib NGX_OK zurück
  lr->last = jetzt;
  lr->Anzahl = 0;
  gib NGX_OK zurück;
 }
 
 lr->letzter = 0;
 lr->Anzahl = 1;
 
 ctx->Knoten = lr;
 
 return NGX_AGAIN; //Nicht die letzte Strombegrenzungsstrategie, gib NGX_AGAIN zurück und überprüfe weiterhin die nächste Strombegrenzungsstrategie}

Wenn der Burst beispielsweise auf 0 konfiguriert ist, ist die Anzahl der ausstehenden Anfragen zunächst überzählig und die Token-Generierungsperiode beträgt T, wie in der folgenden Abbildung dargestellt

3.2.2.2LRU-Eliminierungsstrategie

Im vorherigen Abschnitt des Pain-Knocking-Algorithmus wird ngx_http_limit_req_expire ausgeführt, um einen Datensatz zu löschen, und zwar jedes Mal, wenn er vom Ende der LRU-Warteschlange gelöscht wird.

Der zweite Parameter n: Wenn n == 0 ist, wird der letzte Datensatz zwangsweise gelöscht. Anschließend wird versucht, einen oder zwei Datensätze zu löschen. Wenn n == 1 ist, wird versucht, einen oder zwei Datensätze zu löschen. Die Codeimplementierung lautet wie folgt:

statisches void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
{
 //Bis zu 3 Datensätze löschen while (n < 3) {
  //Endknoten q = ngx_queue_last(&ctx->sh->queue);
  //Datensatz abrufen lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
   
  //Hinweis: Wenn n 0 ist, kann der if-Codeblock nicht aufgerufen werden, sodass der Endknoten gelöscht wird. Wenn n ungleich 0 ist, wird der if-Codeblock aufgerufen, um zu prüfen, ob er gelöscht werden kann, if (n++ != 0) {
 
   ms = (ngx_msec_int_t) (jetzt – lr->letztes);
   ms = ngx_abs(ms);
   //Zugriff innerhalb kurzer Zeit, kann nicht gelöscht werden, direkt zurückkehren, wenn (ms < 60000) {
    zurückkehren;
   }
    
   //Es gibt ausstehende Anfragen, die nicht gelöscht werden können. Geben Sie direkt „excess = lr->excess - ctx->rate * ms / 1000“ zurück.
   wenn (Überschuss > 0) {
    zurückkehren;
   }
  }
 
  //löschen ngx_queue_remove(q);
 
  Knoten = (ngx_rbtree_node_t *)
     ((u_char *) lr - Offset von (ngx_rbtree_node_t, Farbe));
 
  ngx_rbtree_delete(&ctx->sh->rbtree, Knoten);
 
  ngx_slab_free_locked(ctx->shpool, Knoten);
 }
}

3.2.2.3 Burst-Implementierung

Burst dient zum Umgang mit plötzlich auftretendem Datenverkehr. Bei plötzlich auftretendem Datenverkehr sollte dem Server gestattet werden, mehr Anfragen zu verarbeiten.

Wenn der Burst 0 ist, werden Anfragen, die das aktuelle Limit überschreiten, abgelehnt. Wenn der Burst größer als 0 ist, werden Anfragen, die das aktuelle Limit überschreiten, zur Verarbeitung in die Warteschlange gestellt, anstatt direkt abgelehnt zu werden.

Wie wird der Warteschlangenprozess umgesetzt? Und nginx muss auch in die Warteschlange gestellte Anfragen regelmäßig verarbeiten;

In Abschnitt 2.2 wird erwähnt, dass jedes Ereignis einen Timer hat. Nginx verwendet Ereignisse und Timer, um Anforderungswarteschlangen und Zeitverarbeitung zu implementieren.

Die Methode ngx_http_limit_req_handler hat den folgenden Code:

//Berechnen Sie, wie lange die aktuelle Anfrage in der Warteschlange warten muss, bevor sie verarbeitet werden kann. Verzögerung = ngx_http_limit_req_account(limits, n, &excess, &limit);

//Lesbares Ereignis hinzufügen, wenn (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
 gibt NGX_HTTP_INTERNAL_SERVER_ERROR zurück;
}

r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_http_limit_req_delay; //Schreibbare Ereignisverarbeitungsfunktion ngx_add_timer(r->connection->write, delay); //Schreibbarer Ereignis-Hinzufügetimer (kann vor Timeout nicht zum Client zurückkehren)

Die Methode zur Berechnung der Verzögerung ist sehr einfach: Sie besteht darin, alle aktuellen Begrenzungsstrategien zu durchlaufen, die zum Verarbeiten aller ausstehenden Anforderungen erforderliche Zeit zu berechnen und den Maximalwert zurückzugeben.

if (limits[n].nodelay) { //Wenn nodelay konfiguriert ist, wird die Anfrage nicht verzögert, die Verzögerung ist 0
 weitermachen;
}
 
Verzögerung = Überschuss * 1000 / ctx->Rate;
 
if (Verzögerung > max_Verzögerung) {
 max_delay = Verzögerung;
 *ep = Überschuss;
 *Grenze = &Grenzen[n];
}

Werfen Sie einen kurzen Blick auf die Implementierung der beschreibbaren Ereignisverarbeitungsfunktion ngx_http_limit_req_delay

statisches void ngx_http_limit_req_delay(ngx_http_request_t *r)
{
 
 wev = r->Verbindung->Schreiben;
 
 if (!wev->timedout) { //Kein Timeout, keine Verarbeitung if (ngx_handle_write_event(wev, 0) != NGX_OK) {
   ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
  }
 
  zurückkehren;
 }
 
 wev->timedout = 0;
 
 r->read_event_handler = ngx_http_block_reading;
 r->write_event_handler = ngx_http_core_run_phases;
 
 ngx_http_core_run_phases(r); //Timeout, mit der Verarbeitung der HTTP-Anfragen fortfahren}

4. Tatsächlicher Kampf

4.1 Test der normalen Strombegrenzung

1) Konfigurieren Sie nginx so, dass die Rate auf 1 qps begrenzt wird und die Rate basierend auf der Client-IP-Adresse begrenzt wird (der Standard-Rückgabestatuscode ist 503), und zwar wie folgt:

http{
 limit_req_zone $binary_remote_addr Zone=Test:10m Rate=1r/s;
 
 Server {
  hören Sie 80;
  Servername localhost;
  Standort / {
   limit_req Zone=Test;
   Stamm-HTML;
   Index Index.html Index.htm;
  }
}

2) Mehrere Anfragen gleichzeitig initiieren; 3) Überprüfen Sie das Serverzugriffsprotokoll. Sie sehen, dass 3 Anfragen in 22 Sekunden eingehen, aber nur 1 Anfrage verarbeitet wird; 2 Anfragen gehen in 23 Sekunden ein, die erste Anfrage wird verarbeitet und die zweite Anfrage wird abgelehnt

xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4.2 Prüfburst

1) Wenn die Geschwindigkeitsbegrenzung 1qps beträgt, werden Anfragen, die die Begrenzung überschreiten, direkt abgelehnt. Um mit Burst-Verkehr fertig zu werden, sollten Anfragen zur Verarbeitung in die Warteschlange gestellt werden dürfen. Daher ist Burst=5 konfiguriert, was bedeutet, dass maximal 5 Anfragen zur Verarbeitung in die Warteschlange gestellt werden dürfen.

http{
 limit_req_zone $binary_remote_addr Zone=Test:10m Rate=1r/s;
 
 Server {
  hören Sie 80;
  Servername localhost;
  Standort / {
   limit_req Zone=Testburst=5;
   Stamm-HTML;
   Index Index.html Index.htm;
  }
}

2) Verwenden Sie ab, um 10 Anfragen gleichzeitig zu initiieren, ab -n 10 -c 10 http://xxxxx;

3) Überprüfen Sie das Serverzugriffsprotokoll. Dem Protokoll zufolge wird die erste Anforderung verarbeitet, die Anforderungen 2 bis 5 werden abgelehnt und die Anforderungen 6 bis 10 werden verarbeitet. Warum ist dies das Ergebnis?

Überprüfen Sie ngx_http_log_module und registrieren Sie den Handler für die Phase NGX_HTTP_LOG_PHASE (die letzte Phase der HTTP-Anforderungsverarbeitung).

Daher sollte die tatsächliche Situation folgendermaßen aussehen: 10 Anfragen kommen gleichzeitig an, die erste Anfrage wird direkt verarbeitet, die zweite bis sechste Anfrage kommen an und werden in die Warteschlange gestellt und verarbeitet (eine Anfrage wird pro Sekunde verarbeitet); die siebte bis zehnte Anfrage werden direkt abgelehnt, sodass das Zugriffsprotokoll zuerst ausgedruckt wird;

Die zweite bis sechste Anfrage wird eine pro Sekunde verarbeitet und das Zugriffsprotokoll wird nach der Verarbeitung ausgedruckt. Das heißt, dass 49 bis 53 Sekunden lang eine Anfrage pro Sekunde verarbeitet wird.

xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:49 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:50 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:51 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:52 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:53 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"

4) Die Antwortzeit der AB-Statistiken wird unten angezeigt. Die minimale Antwortzeit beträgt 87 ms, die maximale Antwortzeit 5128 ms und die durchschnittliche Antwortzeit 1609 ms:

    Min. Mittelwert[+/-SD] Median Max.
Verbinden: 41 44 1,7 44 46
Verarbeitung: 46 1566 1916,6 1093 5084
Warten: 46 1565 1916,7 1092 5084
Gesamt: 87 1609 1916,2 1135 5128

4.3 Testen von Nodelay

1) 4.2 zeigt, dass nach der Konfiguration von Burst zwar Burst-Anfragen zur Verarbeitung in die Warteschlange gestellt werden, die Antwortzeit jedoch zu lang ist und der Client möglicherweise bereits eine Zeitüberschreitung aufweist. Fügen Sie daher die Nodelay-Konfiguration hinzu, damit Nginx wartende Anfragen dringend verarbeitet, um die Antwortzeit zu verkürzen:

http{
 limit_req_zone $binary_remote_addr Zone=Test:10m Rate=1r/s;
 
 Server {
  hören Sie 80;
  Servername localhost;
  Standort / {
   limit_req Zone=Test-Burst=5 Knotenlage;
   Stamm-HTML;
   Index Index.html Index.htm;
  }
}

2) Verwenden Sie ab, um 10 Anfragen gleichzeitig zu initiieren, ab -n 10 -c 10 http://xxxx/;

3) Überprüfen Sie das Serverzugriffsprotokoll. Die erste Anforderung wird direkt verarbeitet, die zweite bis sechste Anforderung wird zur Verarbeitung in die Warteschlange gestellt (Nodelay konfigurieren, Nginx-Notfallverarbeitung) und die siebte bis zehnte Anforderung wird abgelehnt

xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4) Die Antwortzeit der AB-Statistiken wird unten angezeigt. Die minimale Antwortzeit beträgt 85 ms, die maximale Antwortzeit 92 ms und die durchschnittliche Antwortzeit 88 ms:

    Min. Mittelwert[+/-SD] Median Max.
Verbinden: 42 43 0,5 43 43
Verarbeitung: 43 46 2,4 47 49
Warten: 42 45 2,5 46 49
Gesamt: 85 88 2,8 90 92

Zusammenfassen

In diesem Artikel werden zunächst die häufig verwendeten Algorithmen zur Strombegrenzung (Leaky-Bucket-Algorithmus und Token-Bucket-Algorithmus) analysiert und kurz der Prozess der Verarbeitung von HTTP-Anforderungen durch Nginx und die Implementierung zeitgesteuerter Nginx-Ereignisse vorgestellt. Anschließend werden die grundlegende Datenstruktur des Moduls ngx_http_limit_req_module und sein Prozess zur Strombegrenzung im Detail analysiert. Anhand von Beispielen werden den Lesern die Konfiguration und die Ergebnisse der Strombegrenzung durch Nginx näher gebracht. Das andere Modul ngx_http_limit_conn_module dient zur Begrenzung der Anzahl der Verbindungen. Es ist relativ einfach zu verstehen und wird hier nicht im Detail vorgestellt.

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.

Das könnte Sie auch interessieren:
  • Die aktuellen Einschränkungen von Nginx in einem Artikel verstehen (einfache Implementierung)
  • Detaillierte Erläuterung der Verbindungslimitkonfiguration von Nginx für IP-Adressen in einem Netzwerksegment
  • Implementierung der Strombegrenzungslösung von Nginx (drei Methoden)
  • So implementieren Sie eine verteilte Strombegrenzung mit Nginx
  • So implementieren Sie die Lese- und Schreibstrombegrenzung in Nginx
  • Analyse der Implementierung der Nginx Rush-Kaufstrombegrenzungskonfiguration

<<:  Ein Beispiel, wie JavaScript doppelte Netzwerkanforderungen verhindern kann

>>:  Node verwendet das Modul async_hooks zur Anforderungsverfolgung

Artikel empfehlen

js Canvas realisiert Slider-Verifizierung

In diesem Artikelbeispiel wird der spezifische Co...

Lösung für das Problem, dass der Docker-Container nicht gestoppt werden kann

Die Lösung lautet wie folgt: 1. Container löschen...

So ersetzen Sie alle Tags im HTML-Text

(?i) bedeutet, dass die Groß-/Kleinschreibung nich...

5 coole und praktische Einführung in HTML-Tags und -Attribute

Tatsächlich handelt es sich auch hier um einen Cl...

JS implementiert den Beispielcode der Dezimalkonvertierung in Hexadezimal

Vorwort Beim Schreiben von Code stoßen wir gelege...

MySQL 5.6.28 Installations- und Konfigurations-Tutorial unter Linux (Ubuntu)

mysql5.6.28 Installations- und Konfigurationsmeth...

HTML-Tabellen-Markup-Tutorial (14): Tabellenkopf

<br />In der HTML-Sprache können Sie der Tab...

Maven-Projekte schneller in Docker erstellen

Inhaltsverzeichnis I. Überblick 2. Konventionelle...

Detaillierte Erklärung der Methode getBoundingClientRect() in js

1. getBoundingClientRect() Analyse Die Methode ge...

Design-Sharing der Download-Seite des Pengyou.com-Mobilclients (Bild und Text)

Schauen wir uns zunächst einige einfache Daten an:...