Warum müssen Sie Module für Nginx erstellen?

Nginx ist ein Webserver, der Dutzende von Geschäftsaufgaben löst, flexibel konfiguriert, skaliert und auf fast allen Betriebssystemen und Plattformen funktioniert. Eine Liste der Funktionen, Fähigkeiten und Probleme, die sofort gelöst werden müssen, kann in einer kleinen Broschüre beschrieben werden. Manchmal können einige Geschäftsaufgaben jedoch nur durch die Entwicklung eigener Module für nginx gelöst werden. Dies sind Module, die geschäftsorientiert sind und eine Geschäftslogik enthalten, und nicht nur eine verallgemeinerte Systemlösung.



Im Allgemeinen ist alles in Nginx Module, die einmal von jemandem geschrieben wurden. Daher ist das Schreiben von Modulen unter nginx nicht nur möglich, sondern auch notwendig. Wann und warum dies notwendig ist und warum, wird Wassili Soshnikow ( dedokOne ) am Beispiel mehrerer Fälle berichten.

Lassen Sie uns über die Gründe sprechen, die das Schreiben von Modulen in C fördern, über die Architektur und den Kern von Nginx, die Anatomie von HTTP-Modulen, über C-Module, NJS, Lua und nginx.conf. Dies ist nicht nur für diejenigen wichtig, die sich unter Nginx entwickeln, sondern auch für diejenigen, die Nginx-Configs, Lua oder eine andere Sprache in Nginx verwenden.

Hinweis: Der Artikel basiert auf einem Bericht von Vasily Soshnikov. Der Bericht wird ständig aktualisiert und aktualisiert. Die Informationen im Material sind recht technisch und um das Beste daraus zu machen, müssen die Leser Erfahrung im Umgang mit Nginx-Code auf einem durchschnittlichen Niveau und darüber haben.


Kurz über Nginx


Alles, was Sie mit nginx verwenden, sind Module . Jede Direktive in der Nginx-Konfiguration ist ein separates Modul, das von Kollegen aus der Nginx-Community sorgfältig geschrieben wurde.

Direktiven in nginx.conf sind auch Module , die ein bestimmtes Problem lösen. Daher sind in Nginx-Modulen alles. add_header, proxy_pass, eine beliebige Direktive - dies sind Module oder Kombinationen von Modulen, die nach bestimmten Regeln arbeiten.

Nginx ist ein Framework mit folgenden Funktionen: Netzwerk- und Datei-E / A, gemeinsamer Speicher, Konfiguration und Skripterstellung. Dies ist eine riesige Schicht von Bibliotheken auf niedriger Ebene, auf denen Sie alles tun können, um mit den Netzwerklaufwerken zu arbeiten.

Nginx ist schnell und stabil, aber komplex . Sie sollten solchen Code schreiben, um diese Eigenschaften von Nginx nicht zu verlieren. Instabiles Nginx in der Produktion sind unzufriedene Kunden, und alles, was daraus folgt.

Warum eigene Module erstellen?


Konvertieren Sie das HTTP-Protokoll in ein anderes Protokoll. Dies ist der Hauptgrund, der häufig zur Erstellung eines bestimmten Moduls motiviert.

Beispielsweise konvertiert das memcached_pass-Modul HTTP in ein anderes Protokoll, und Sie können mit anderen externen Systemen arbeiten. Mit dem Modul proxy_pass können Sie auch von HTTP (s) zu HTTP (s) konvertieren. Ein weiteres gutes Beispiel ist fastcgi_pass.

Dies sind alles Anweisungen des Formulars: "Gehen Sie zu so und so einem Backend, wo nicht HTTP (aber im Fall von proxy_pass HTTP)."

Dynamisches Einfügen von Inhalten: AdBlock-Bypass, Anzeigeneinfügung. Zum Beispiel haben wir ein Backend und es ist notwendig, den Inhalt zu ändern, der daraus stammt. Zum Beispiel AdBlock, das den Anzeigeneinfügungscode analysiert, und wir müssen uns damit befassen - um ihn auf die eine oder andere Weise zu optimieren.

Eine andere Sache, die Sie häufig tun müssen, um Inhalte einzubetten, ist das Problem mit dem HLS-Caching. Wenn Parameter in HLS zwischengespeichert werden, können zwei Benutzer dieselbe Sitzung oder dieselben Parameter erhalten. Von dort aus schneiden oder fügen Sie einige Parameter hinzu, wenn Sie etwas verfolgen müssen.

Clickstream-Datenerfassung von Internet- / Mobilzählern. Ein beliebter Fall in meiner Praxis. Meistens geschieht dies auf nginx, aber nicht auf access.log, sondern etwas intelligenter.

Konvertieren aller Arten von Inhalten. Mit dem rtmp-Modul für können Sie beispielsweise nicht nur mit rtmp, sondern auch mit HLS arbeiten. Dieses Modul kann viel mit Videoinhalten tun.

Allgemeiner Autorisierungspunkt: SEP oder Api Gateway. Dies ist der Fall, wenn nginx als Teil der Infrastruktur arbeitet: Autorisiert, sammelt Metriken, sendet Daten an die Überwachung und ClickStream. Nginx fungiert hier als Infrastruktur-Hub - ein einziger Einstiegspunkt für Backends.

Anreicherung von Anfragen für deren spätere Rückverfolgung. Moderne Systeme sind sehr komplex, mit verschiedenen Arten von Backends, die unterschiedliche Teams bilden. In der Regel sind sie schwer zu debütieren, manchmal ist es sogar schwierig zu verstehen, woher die Anfrage kam und wohin sie ging. Um das Debuggen zu vereinfachen, verwenden einige große Unternehmen eine knifflige Technik: Sie fügen Anforderungen bestimmte Daten hinzu. Der Benutzer wird sie nicht sehen, aber anhand dieser Daten ist es einfach, den Anforderungspfad innerhalb des Systems zu verfolgen. Dies wird als Trace bezeichnet .

S3-Proxy. In diesem Jahr sehe ich oft Leute, die mit s3 über s3 arbeiten. Dies ist jedoch bei C-Modulen nicht erforderlich, da die Infrastruktur auch in Nginx ausreicht. Um einige dieser Probleme zu lösen, können Sie Lua verwenden. Auf NJS wird etwas gelöst. Aber manchmal ist es notwendig, Module in C zu schreiben.

Wann ist die Zeit, Module zu erstellen?


Es gibt zwei Kriterien, um zu verstehen, dass die Zeit gekommen ist.

Verallgemeinerung der Funktionalität. Wenn Sie verstehen, dass jemand anderes Ihr Produkt benötigt, tragen Sie es zu Open Source bei, erstellen allgemeine Funktionen, veröffentlichen es und lassen es verwenden.

Geschäftsprobleme lösen. Wenn ein Unternehmen solche Anforderungen stellt, die nur durch das Schreiben eines eigenen Moduls für nginx erfüllt werden können. Beispiel: Dynamisches Einfügen / Ändern von Inhalten, ClickStream-Sammlung kann auf Lua durchgeführt werden, funktioniert aber höchstwahrscheinlich nicht normal.

Nginx-Architektur


Ich habe lange Zeit Nginx-Code geschrieben. Neun meiner Module drehen sich in der Produktion, eines davon in Open Source und für viele in der Produktion. Daher habe ich Erfahrung und Verständnis.
Nginx ist eine Nistpuppe, in der alles um den Kern herum aufgebaut ist.
Also verstehe ich Nginx.
Kern sind Wrapper über Epoll.
Epoll ist eine Methode, mit der Sie asynchron mit allen Deskriptordateien arbeiten können, nicht nur mit Sockets, da ein Deskriptor nicht nur ein Socket ist.

Über dem Kern befinden sich Upstreams, HTTP und Scripting. Mit Scripting meine ich nginx.conf, nicht NJS. Neben Upstreams, HTTP und Scripting sind bereits HTTP-Module erstellt, über die wir sprechen werden.



Ein klassisches Beispiel für Upstreams und HTTP sind Upstream-Server - Anweisungen innerhalb der Konfiguration. Ein Beispiel für Module für HTTP ist add_header. Ein Beispiel für die Skripterstellung ist die Konfigurationsdatei selbst. Die Datei enthält die Module, aus denen nginx besteht. Sie wird irgendwie interpretiert und ermöglicht es Ihnen, etwas als Administrator oder als Benutzer zu tun.

Wir werden den Kern nicht betrachten und uns sehr kurz mit Upstreams befassen, da es sich um ein separates Universum innerhalb von Nginx handelt. Die Geschichte über sie verdient mehrere Artikel.

Anatomie von HTTP-Modulen


Auch wenn Sie keinen C-Code in nginx schreiben, sondern verwenden, beachten Sie die Hauptregel.
In Nginx gehorcht alles dem Muster der Verantwortungskette - COR.
Ich weiß nicht, wie ich das ins Russische übersetzen soll, aber ich werde die Logik beschreiben. Ihre Anfrage durchläuft eine Galaxie konfigurierter Kettenmodule, beginnend mit dem Standort. Jedes dieser Module gibt ein Ergebnis zurück. Wenn das Ergebnis schlecht ist, wird die Kette unterbrochen.



Vergessen Sie beim Entwickeln von Modulen oder beim Verwenden einer Direktive in NJS und Lua nicht, dass Ihr Code die Ausführung dieser Kette zum Absturz bringen kann.

Die nächste Analogie zu Chain of Responsibility ist eine Zeile mit Bash-Code:

grep -RI pool nginx | awk -F":" '{print $1}' | sort -u | wc -l 

Im Code ist alles ganz einfach: Wenn AWK in die Mitte der Zeile fällt, werden sort und die folgenden Befehle nicht ausgeführt. Das Nginx-Modul funktioniert ähnlich, aber die Wahrheit ist in Nginx und Sie können dies umgehen - starten Sie den Code neu. Sie sollten jedoch darauf vorbereitet sein, abzustürzen und auszuführen, genau wie Ihre Module, die Sie in der Konfiguration verwenden, aber nicht die Tatsache, dass dies so ist.

Arten von HTTP-Modulen


HTTP und Nginx sind eine Reihe verschiedener PHASEN.

  • Phasenbehandlung - PHASE-Handler .
  • Filter - Body / Header-Filter . Diese Filterung erfolgt entweder in Kopfzeilen oder in Anforderungskörpern.
  • Proxies . Typische Proxy-Module sind proxy_pass, fastcgi_pass, memcached_pass.
  • Module für den spezifischen Lastausgleich - Lastausgleicher . Dies ist die am wenigsten verdrehte Art von Modulen, sie werden nur wenig entwickelt. Ein Beispiel ist das Ketama CHash-Modul, mit dem Sie konsistentes Hashing in Nginx durchführen können, um Anforderungen an Backends zu verteilen.

Ich werde über jeden dieser Typen und ihren Zweck berichten.

Phasenhandler


Stellen Sie sich vor, wir haben mehrere Phasen, beginnend mit der Zugriffsphase. In jeder Phase gibt es mehrere Module. Beispielsweise ist die ACCESS-Phase in eine Verbindung, eine Anforderung an nginx und die Überprüfung der Benutzerberechtigung unterteilt. Jedes Modul ist eine Zelle in der Kette. Es kann unendlich viele solcher Module in Phase geben.



Der letzte, letzte Handler ist die INHALTSphase, in der Inhalte auf Anfrage bereitgestellt werden.
Der Weg ist immer folgender: Anfrage - eine Kette von Handlern - Inhalt ausgeben.
Phasen, die Entwicklern von Modulen aus den NGINX-Quellen zur Verfügung stehen :

 typedef enum { NGX_HTTP_POST_READ_PHASE = 0, NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_FIND_CONFIG_PHASE, NGX_HTTP_REWRITE_PHASE, NGX_HTTP_POST_REWRITE_PHASE, NGX_HTTP_PREACCESS_PHASE, NGX_HTTP_ACESS_PHASE, NGX_HTTP_POST_ACESS_PHASE, NGX_HTTP_PRECONTENT_PHASE, NGX_HTTP_CONTENT_PHASE, NGX_HTTP_LOG_PHASE, } ngx_http_phases; 

Phasen können überschrieben werden, fügen Sie Ihren eigenen Handler hinzu. Nicht alle von ihnen werden im wirklichen Leben benötigt, wenn Sie nicht der Entwickler von Nginx Core sind. Daher werde ich nicht über jede Phase sprechen, sondern nur über die wichtigsten, die ich verwendet habe.

Das wichtigste ist ACCESS_PHASE. Es ist besonders nützlich, Ihre Berechtigung zu nginx hinzuzufügen, um die Ausführung der Anforderung in Bezug auf den Zugriff zu überprüfen.

Die nächsten wichtigen Phasen, die ich oft ausnutze, sind die Phasen des Vorinhalts und des Inhalts. Mit PRECONTENT_PHASE können Sie Metriken zu Inhalten erfassen, die als Antwort an den Client gesendet werden sollen. Mit CONTENT_PHASE können Sie Ihren eigenen einzigartigen Inhalt basierend auf etwas generieren.

Die letzte Phase, die ich oft benutze, ist die Protokollierungsphase LOG_PHASE. Im Übrigen funktioniert darin die Direktive ACCESS_LOG. Die Protokollierungsphase weist die wildesten Einschränkungen auf, die mich verrückt machen: Sie können keine Unteranforderung verwenden und im Allgemeinen können Sie keine Anforderung verwenden. Sie haben den Inhalt bereits dem Benutzer überlassen, und Handler, Posthandler und Unteranforderungen werden nicht ausgeführt.

Ich werde erklären, warum es nervt. Angenommen, Sie möchten Nginx und Kafka in der Protokollierungsphase überqueren. In dieser Phase ist bereits alles abgeschlossen: Es gibt eine berechnete Größe des Inhalts, des Status und aller Daten, aber Sie können keine Unteranforderung durchführen. Sie arbeiten dort nicht. Sie müssen in der Protokollierungsphase auf nackte Sockets schreiben, um Daten an Kafka zu senden.

Body / Header-Filter


Es gibt zwei Arten von Filtern: Körperfilter und Kopfzeilenfilter.

Ein Beispiel für einen Body-Filter ist das gzip-Filtermodul. Warum werden Körperfilter benötigt? Stellen Sie sich vor, Sie haben einen bestimmten proxy_pass und möchten den Inhalt irgendwie transformieren oder analysieren. In diesem Fall sollten Sie den Body-Filter verwenden.

Es funktioniert so: Viele Brocken kommen zu Ihnen, Sie machen etwas mit ihnen, schauen sich den Inhalt an, aggregieren usw. Der Filter weist jedoch auch erhebliche Einschränkungen auf. Wenn Sie beispielsweise den Text ändern möchten, um den Antworttext einzufügen oder auszuschneiden, denken Sie daran, dass HTTP-Attribute, z. B. ein Inhaltsfeed, ersetzt werden. Dies kann zu seltsamen Effekten führen, wenn Sie keine Einschränkungen vorsehen und Ihren Code korrekt wiedergeben.

Ein Beispiel für einen Header-Filter ist der add_header, den jeder verwendet hat. Der Algorithmus funktioniert wie im Body-Filter. Für den Client wird eine Antwort vorbereitet, und mit dem Filter add_header können Sie dort etwas tun: Header hinzufügen, Header löschen, Header ersetzen, Unteranforderung senden.

Übrigens können Sie im Body-Filter und im Header-Filter Unteranforderungen sogar interne Identifikationen an einen zusätzlichen Ort senden.

Proxy


Dies ist die komplexeste und umstrittenste Art von Modulen, mit denen Sie Anforderungen an externe Systeme weiterleiten können, z. B. HTTP in ein anderes Protokoll konvertieren können . Beispiele: proxy_pass, redis_pass, tnt_pass.

Proxy ist eine Schnittstelle, die von Nginx-Kernentwicklern vorgeschlagen wurde, um das Schreiben von Proxy-Modulen zu vereinfachen. Wenn dies auf klassische Weise erfolgt, werden für einen solchen Proxy PHASES-Handler, Filter, Balancer ausgeführt. Wenn sich das Protokoll, in das Sie HTTP konvertieren möchten, jedoch irgendwie von den Klassikern unterscheidet, treten große Probleme auf. Die von nginx bereitgestellte Proxy-API ist einfach nicht geeignet - Sie müssen dieses Proxy-Modul von Grund auf neu erfinden.

Ein gutes Beispiel für ein solches Modul ist postgres_pass. Es ermöglicht nginx die Kommunikation mit PostgreSQL. Das Modul verwendet die in nginx entwickelte Schnittstelle überhaupt nicht - es hat einen eigenen Pfad.
Denken Sie an Proxy, aber schreiben Sie vorzugsweise nicht. Um einen Proxy zu schreiben, müssen Sie alle Nginx auswendig lernen - es ist sehr lang und schwierig.

Load Balancer


Die Aufgabe von Load Balancern ist sehr einfach - im Round-Robin-Modus zu arbeiten. Stellen Sie sich vor, Sie haben einen Upstream-Abschnitt, einige Server, Sie geben Gewichte und Ausgleichsmethoden an. Dies ist ein typischer Load Balancer.

Dieser Modus ist nicht immer geeignet. Daher wurde das Ketama CHash-Modul entwickelt, bei dem es bedingt möglich ist, eine konsistente Hash-Anfrage an einen Server zu erhalten. Manchmal ist es bequem. Nginx Lua bietet balancer_by_lua an. Auf Lua können Sie generell jeden Balancer schreiben.

C-Module


Als nächstes kommt meine absolut subjektive Meinung zur Entwicklung von C-Modulen. Zunächst - meine subjektiven Regeln.

Das Modul beginnt mit den Anweisungen nginx.conf. Auch wenn Sie ein C-Modul herstellen, das nur von Ihrem Unternehmen betrieben wird, denken Sie immer an Richtlinien. Beginnen Sie mit dem Entwerfen des Moduls mit ihnen, da der Systemadministrator auf diese Weise mit ihm kommuniziert. Dies ist wichtig - koordinieren Sie alle Nuancen mit ihm oder mit der Person, die Ihr C-Modul bedienen wird. NGINX ist ein bekanntes Produkt. Seine Richtlinien befolgen bestimmte Gesetze, die Systemadministratoren kennen. Denken Sie deshalb immer darüber nach.

Verwenden Sie den Nginx-Codestil. Stellen Sie sich vor, Ihr Modul wird von einer anderen Person unterstützt. Wenn er bereits mit Nginx und seinem Codestil vertraut ist, ist es für ihn viel einfacher, Ihren Code zu lesen und zu verstehen.

Kürzlich hat mich ein guter Freund aus Deutschland gebeten, ihm bei der Behebung eines Fehlers in seinem Nginx-Code zu helfen. Ich weiß nicht, für welchen Codestil er es geschrieben hat, aber ich konnte den Code nicht einmal normal lesen.

Verwenden Sie den richtigen Speicherpool. Denken Sie immer daran, auch wenn Sie viel Erfahrung mit Nginx haben. Ein typischer Fehler eines unerfahrenen C-Modul-Entwicklers für Nginx besteht darin, den falschen Pool zu erhalten.

Ein kleiner Hintergrund: Nginx verwendet im Allgemeinen die Ideologie schwacher Allokatoren. Sie können dort malloc verwenden, aber nicht empfohlen. Es hat seine eigenen Platten, seinen eigenen Speicherzuweiser, Sie müssen es verwenden. Dementsprechend hat jedes Objekt eine Verknüpfung zu seinem Pool, und dieser Pool muss verwendet werden. Ein typischer Anfängerfehler besteht darin, eine Poolverbindung im Headerfilter zu verwenden, keine Poolanforderung. Dies bedeutet, dass bei einer Keep-Alive-Verbindung der Pool anschwillt, bis nicht mehr genügend Speicher vorhanden ist oder andere Nebenwirkungen auftreten. Daher ist es wichtig.

Darüber hinaus sind solche Fehler äußerst schwer zu debütieren. Valgrind ("syshniks" wird verstehen) funktioniert nicht mit der Plattenzuordnung - es wird ein seltsames Bild zeigen.

Verwenden Sie keine blockierenden E / A. Ein typischer Fehler derjenigen, die etwas Externes schneller anwenden möchten, ist die Verwendung von blockierenden E / A und blockierenden Sockets. Sie können dies in Nginx niemals tun - es gibt viele Prozesse darin, aber jeder Prozess verwendet einen Thread.

Sie können Multithreading durchführen, dies macht es jedoch in der Regel nur noch schlimmer. Wenn Sie in einer solchen Architektur blockierende E / A verwenden, warten alle auf dieses blockierende Teil.

Ich werde entziffern, was ich oben gesagt habe.

Das Modul beginnt mit den Anweisungen nginx.conf

Entscheiden Sie, in welchen Arrays Ihre Direktive gespeichert werden soll: Main, Server, HTTP, Location, Location if.
Versuchen Sie, den Standort zu vermeiden, wenn dies in der Regel zu einer sehr merkwürdigen Verwendung der Nginx-Konfiguration führt.

Alle Direktiven in Nginx leben in unterschiedlichen Kontexten und in unterschiedlichen Bereichen. Die Direktive add_header kann auf HTTP-Ebene, auf Standortebene und auf Standortebene arbeiten. Dies wird normalerweise in der Dokumentation beschrieben.
Verstehen Sie, auf welchen Ebenen Ihre Direktive arbeiten kann und wo die Direktive ausgeführt wird: PHASE Handler, Body / Header-Filter.
Dies ist wichtig, da in nginx die Konfiguration eingefroren ist. Wenn Sie add_header irgendwo darüber schreiben, wird dieser Wert standardmäßig im unteren add_header geglättet, den Sie bereits an Ort und Stelle haben. Dementsprechend fügen Sie zwei Überschriften hinzu. Dies gilt für jede Richtlinie.

Wenn Sie einen Host-Port angeben, dann umgekehrt - Socket-Pool. Dies sollte einmal angezeigt werden.

Im Allgemeinen würde ich jegliches Zusammenführen verbieten - Sie brauchen es einfach nicht. Daher sollten Sie immer klar bestimmen, in welchen Nginx-Arrays aus der Konfiguration Ihre Direktive oder Gruppe von Direktiven lebt.

Gutes Beispiel:

 location /my_location/ { add_header “My-Header” “my value”; } 

Hier wird add_header einfach zum Speicherort hinzugefügt. Der gleiche add_header könnte sich irgendwo oben befinden und alles würde einfach verzerrt sein. Dies ist ein dokumentiertes und verständliches Verhalten.
Überlegen Sie, was die Umsetzung der Richtlinie behindern könnte.
Stellen Sie sich vor, Sie entwickeln einen Körperfilter. Wie oben erwähnt, ordnet nginx Ihr Modul nur einer gemeinsamen Kette zu, und Sie können nicht garantieren, dass das gzip-Modul beim Kompilieren nicht in die Kette vor Ihrem Body-Filter gelangt ist. In diesem Fall werden die Daten für das gzip an Ihr Modul gesendet, wenn jemand das gzip-Modul einschaltet. Dies droht, dass Sie mit dem Inhalt einfach nichts anfangen können. Sie können es beispielsweise erneut gzipen, dies ist jedoch aus Sicht der CPU ein Spott.

Für alle Phasenabwickler gelten die gleichen Regeln - es gibt keine Garantie dafür, wer vorher und wer danach angerufen wird. Respektieren Sie daher denjenigen, der angerufen wird, und denken Sie daran, dass ein GZIP oder etwas anderes unerwartet zu Ihnen fliegen kann.

Nginx-Codestil


Denken Sie beim Erstellen des Produkts daran, dass es von jemandem unterstützt wird. Vergessen Sie nicht den Code-Stil Nginx.
Machen Sie sich vor dem Schreiben Ihres Nginx-Moduls mit der Quelle vertraut: der einen und der zweiten .

Wenn Sie in Zukunft mit der Entwicklung von Nginx-Modulen beginnen, sind Sie sich der Nginx-Quellen bewusst. Sie werden sie lieben, weil es keine Dokumentation gibt . Sie lernen die Struktur des Nginx-Verzeichnisses gut kennen und lernen, Grep, möglicherweise Sed, zu verwenden, wenn Sie einige Teile von Nginx auf Ihre Module übertragen müssen.

Speicherpool


Pools müssen korrekt verwendet werden. Zum Beispiel "r-> Verbindung-> Pool! = R-> Pool". In keinem Fall können Sie die Speicherpoolkonfiguration bei der Verarbeitung von Anforderungen verwenden. Sie schwillt an, bis nginx neu gestartet wird.

Verstehen Sie die Lebensdauer des Objekts. Angenommen, die Anforderungswiedergabe hat genau diese Pipeline-Lebensdauer. In diesem Pool können Sie viele Dinge platzieren und Platz schaffen. Verbindung kann theoretisch unbegrenzt leben - es ist besser, etwas wirklich Wichtiges darin zu platzieren.

Versuchen Sie, keine externen Allokatoren zu verwenden, z. B. malloc / free . Dies hat negative Auswirkungen auf die Speicherfragmentierung. Wenn Sie mit großen Datenmengen arbeiten und viel Malloc verwenden, verlangsamt sich dieser Nginx ziemlich gut.

Für Fans von Valgrind gibt es einen Hack , mit dem Sie Nginx-Pools mit Valgrind debuggen können. Dies ist wichtig, wenn Sie viel C-Code auf nginx haben, da selbst ein erfahrener Entwickler in der Arbeit mit Speicher einen Fehler machen kann.

E / A blockieren

Hier ist alles einfach - verwenden Sie keine blockierenden E / A.
Andernfalls gibt es zumindest Probleme mit Keep-Alive-Verbindungen, aber maximal funktioniert alles sehr lange.

Ich kenne den Fall, dass eine Person Quora in Nginx im Blockierungsmodus verwendet hat (fragen Sie nicht warum). Dies führte dazu, dass Keep-Alive-Verbindungen ihre Aktivitäten aufgaben und ständig eine Zeitüberschreitung aufwiesen. Es ist besser, dies nicht zu tun - alles wird lange Zeit ineffizient funktionieren und Sie müssen sofort eine Million Zeitüberschreitungen verdrehen, da Nginx bei vielen Dingen eine Zeitüberschreitung startet.

Es gibt jedoch eine Alternative zu C-Modulen - NJS und Lua.

Wenn Sie keine C-Module entwickeln müssen


Dieses Jahr hatte ich meine ersten Erfahrungen mit NJS, ich bekam einen subjektiven Eindruck davon und ich erkannte sogar, was dort fehlte, so dass alles in Ordnung war. Ich möchte auch über meine Erfahrungen bei der Arbeit an Lua unter Nginx sprechen und darüber hinaus die Probleme teilen, die in Lua vorhanden sind.

Lua / LuaJit Essentials


Nginx verwendet nicht Lua, sondern LuaJit. Dies ist jedoch nicht Lua, da Lua bereits zwei Versionen weiterentwickelt hat und LuaJit irgendwo in der Vergangenheit feststeckt. Der Autor entwickelt LuaJit praktisch nicht - er lebt oft in Gabeln. Die aktuellste Gabel ist LuaJit2 . Dies fügt seltsame Situationen in derselben OpenResty hinzu.

Garbage Collector braucht Aufmerksamkeit . LuaJit kann dieses Problem nicht lösen. Überlegen Sie sich einfach einige Problemumgehungen. Bei einer enormen Last, bei der auf dem Client eine Menge Keep-Alive-Garbage Collector mit Fehlern in der Tabelle und 500 Fehlern sichtbar ist. Es gibt viele Möglichkeiten, mit dem Garbage Collector in Lua umzugehen. Ich werde mich hier nicht auf diese konzentrieren. Im Internet gibt es dazu viele Informationen.

Die Implementierung von Zeichenfolgen führt zu Leistungsproblemen . Dies ist nur das Böse von LuaJit, und in Lua wurde es repariert. Die Implementierung von Strings in LuaJit widerspricht einfach jeder Logik. Linien werden auf wildeste Weise langsamer, was mit der internen Implementierung verbunden ist.

Unfähigkeit, viele vorgefertigte Bibliotheken zu verwenden . Lua blockiert anfänglich, daher verwenden die meisten Bibliotheken auf Lua und LuaJit blockierende E / A. Aufgrund der Tatsache, dass Nginx nicht blockiert, ist es unmöglich, vorgefertigte Bibliotheken in Nginx zu verwenden, die blockierende E / A verwenden. Dies wird Nginx verlangsamen.

Die Gründe für die Verwendung von LuaJit sind identisch mit den Gründen für die Verwendung von Modulen:

  • Prototyping komplexer Module;
  • HMAC, SHA-Berechnungen für Berechtigungen;
  • Balancer ;
  • kleine Anwendungen: Header-Handler, Regeln für Weiterleitungen;
  • Berechnungsvariablen für nginx.conf.

Wo ist es besser, LuaJit nicht zu verwenden?
Die Hauptregel: Verarbeiten Sie keinen großen Körper auf Lua - das funktioniert nicht.
Handler für Inhalte auf Lua funktionieren ebenfalls nicht . Versuchen Sie, die Logik auf wenige if zu minimieren. Ein einfacher Balancer wird funktionieren, aber eine Seitenleiste auf Lua wird sehr schlecht funktionieren.

Shared Memory oder Garbage Collector werden kommen. Verwenden Sie Shared Memory nicht mit Lua - Garbage Collector wird schnell und mit Sicherheit das gesamte Gehirn für die Produktion ausschalten.

Verwenden Sie keine Coroutinen mit vielen Keep-Alive-Verbindungen. Coroutinen erzeugen noch mehr Müll im LuaJit Garbage Collector, was schlecht ist.

Wenn Sie LuaJit bereits verwenden, denken Sie daran:

  • über die Speicherüberwachung;
  • zur Überwachung und Optimierung der Arbeit von Garbage Collector;
  • darüber, wie Garbage Collector funktioniert, wenn Sie eine komplizierte Anwendung für LuaJit geschrieben haben, weil Sie etwas Neues hinzufügen müssen.

Njs


Als ich bei NGINX Conf war, haben sie mich überzeugt, dass es cool wäre, keinen Code in C zu schreiben. Ich dachte, ich müsste es versuchen, und das habe ich bekommen.

Autorisierung Es funktioniert, der Code ist einfach, es hat keinen Einfluss auf die Geschwindigkeit - alles ist großartig. Mein kleiner Prototyp, mit dem ich angefangen habe, besteht aus 10 Codezeilen. Aber diese 10 Zeilen autorisieren mit s3.

Berechnen von Variablen für nginx.conf. Viele Variablen können mit NJS berechnet werden. In Nginx ist das cool. Es gibt eine solche Funktion in Lua, aber es gibt einen Garbage Collector, also ist es nicht so cool.

Allerdings ist nicht alles so gut. Um wirklich coole Dinge auf NJS zu machen, vermisst er ein paar Dinge.

Gemeinsamer Speicher . Ich habe Shared Memory gepatcht. Dies ist meine eigene Gabel. Jetzt reicht es aus.

Filter, die mehr Phasen unterstützen . In NJS gibt es nur die Inhaltsphase und die Variablen, und der Header-Filter fehlt sehr. Sie müssen Krücken schreiben, um viele Überschriften hinzuzufügen. Es gibt nicht genügend Körperfilter für komplexe Logik oder die Arbeit mit Inhalten.

Informationen zur Überwachung und Profilierung . Ich weiß jetzt wie, aber ich musste die Quelle studieren. Es gibt nicht genügend Informationen oder Tools zur richtigen Profilerstellung. Wenn ja, ist es versteckt, wo es nicht zu finden ist. Gleichzeitig gibt es nicht genügend Informationen darüber, wo ich NJS verwenden kann und wo nicht?

C-Module . Ich hatte den Wunsch, NJS zu erweitern.

Nachwort


Warum eigene Module erstellen? Allgemeine und geschäftliche Probleme lösen.

Wann muss ich Module in C implementieren? Wenn es keine anderen Optionen gibt. Zum Beispiel eine hohe Last, das Einfügen von Inhalten oder grundlegende Einsparungen bei der Hardware. Dann muss dies garantiert in C erfolgen. In den meisten Fällen ist Lua oder NJS geeignet. Aber Sie müssen immer vorausdenken.

Und auf Lua? Wenn Sie nicht in C schreiben können, müssen Sie beispielsweise den Anforderungshauptteil nicht mit großem RPS konvertieren. Ihre Kundenzahl wächst, irgendwann werden Sie aufhören, damit umzugehen - denken Sie darüber nach.

NJS? Wenn LuaJit die Nase voll hat von seinem Garbage Collector und seinen Strings. Beispielsweise hat die Autorisierung viele Garbage-Objekte auf Lua generiert, dies war jedoch nicht kritisch. Dies spiegelte sich jedoch in der Überwachung und Belästigung wider. Jetzt erscheint es nicht mehr in meiner Überwachung und alles ist gut geworden.

Bei HighLoad ++ 2019 wird Vasily Soshnikov das Thema Nginx-Module fortsetzen und mehr über NJS sprechen , wobei der Vergleich mit LuaJit und C nicht zu vergessen ist.

Sehen Sie sich die vollständige Liste der Berichte auf der Website an und sehen Sie sich am 7. und 8. November auf der größten Konferenz für Entwickler hoch belasteter Systeme. Folgen Sie unseren neuen Ideen im Newsletter und im Telegrammkanal .

Source: https://habr.com/ru/post/de471684/


All Articles