Jenseits des Web-Servers: Privilegierte Prozesse und Timer-Tasks
API7.ai
November 3, 2022
Im vorherigen Artikel haben wir die OpenResty-APIs, shared dict
und cosocket
vorgestellt, die alle Funktionen im Bereich von NGINX und Webservern implementieren und einen programmierbaren Webserver bieten, der kostengünstiger und einfacher zu warten ist.
Allerdings kann OpenResty noch mehr. Lassen Sie uns heute einige Funktionen in OpenResty vorstellen, die über den Webserver hinausgehen. Es handelt sich um Timer-Aufgaben, privilegierte Prozesse und nicht-blockierendes ngx.pipe
.
Timer-Aufgaben
In OpenResty müssen wir manchmal regelmäßig bestimmte Aufgaben im Hintergrund ausführen, wie z.B. das Synchronisieren von Daten oder das Bereinigen von Protokollen. Wie würden Sie das gestalten? Der einfachste Ansatz wäre, eine API-Schnittstelle bereitzustellen, die diese Aufgaben ausführt, und dann das System-crontab
zu verwenden, um in regelmäßigen Abständen curl
aufzurufen und diese Schnittstelle zu nutzen, um die Anforderung indirekt zu erfüllen.
Dies wäre jedoch nicht nur fragmentiert, sondern würde auch die Komplexität der Wartung erhöhen. Daher bietet OpenResty ngx.timer
, um diese Art von Anforderung zu lösen. Sie können sich ngx.timer
als einen von OpenResty simulierten Client-Request vorstellen, der die entsprechende Callback-Funktion auslöst.
Die Timer-Aufgaben in OpenResty können in die folgenden zwei Typen unterteilt werden:
ngx.timer.at
wird verwendet, um einmalige Timer-Aufgaben auszuführen.ngx.timer.every
wird verwendet, um periodische Timer-Aufgaben auszuführen.
Erinnern Sie sich an die nachdenkliche Frage, die ich am Ende des letzten Artikels gestellt habe? Die Frage war, wie man die Einschränkung umgehen kann, dass cosocket
nicht in init_worker_by_lua
verwendet werden kann, und die Antwort lautet ngx.timer
.
Der folgende Code startet eine Timer-Aufgabe mit einer Verzögerung von 0
. Er startet die Callback-Funktion handler
, und in dieser Funktion wird cosocket
verwendet, um auf eine Website zuzugreifen.
init_worker_by_lua_block {
local function handler()
local sock = ngx.socket.tcp()
local ok, err = sock:connect(“api7.ai", 80)
end
local ok, err = ngx.timer.at(0, handler)
}
Auf diese Weise umgehen wir die Einschränkung, dass cosocket
in dieser Phase nicht verwendet werden kann.
Zurück zu der Benutzeranforderung, die wir zu Beginn dieses Abschnitts erwähnt haben: ngx.timer.at
erfüllt nicht die Anforderung, periodisch ausgeführt zu werden; im obigen Codebeispiel handelt es sich um eine einmalige Aufgabe.
Wie können wir dies also periodisch tun? Sie scheinen zwei Optionen basierend auf der ngx.timer.at
-API zu haben:
- Sie können die periodische Aufgabe selbst implementieren, indem Sie eine
while true
-Endlosschleife in der Callback-Funktion verwenden, die nach der Ausführung der Aufgabe eine Weilesleep
t. - Sie können auch am Ende der Callback-Funktion einen neuen Timer erstellen.
Bevor Sie jedoch eine Wahl treffen, müssen wir eines klären: Der Timer ist im Wesentlichen ein Request, obwohl dieser Request nicht vom Client initiiert wurde. Für den Request muss er nach Abschluss seiner Aufgabe beendet werden und kann nicht dauerhaft aktiv bleiben. Andernfalls kann dies leicht zu verschiedenen Arten von Ressourcenlecks führen.
Daher ist die erste Lösung, periodische Aufgaben mit while true
zu implementieren, unzuverlässig. Die zweite Lösung ist machbar, erstellt jedoch rekursiv Timer, was nicht leicht zu verstehen ist.
Gibt es also eine bessere Lösung? Die neue ngx.timer.every
-API in OpenResty wurde speziell entwickelt, um dieses Problem zu lösen, und ist eine Lösung, die näher an crontab
liegt.
Der Nachteil ist, dass Sie eine Timer-Aufgabe nach dem Start nie mehr abbrechen können. Schließlich ist ngx.timer.cancel
immer noch eine To-Do-Funktion.
An diesem Punkt stehen Sie vor einem Problem: Der Timer läuft im Hintergrund und kann nicht abgebrochen werden; wenn es viele Timer gibt, können die Systemressourcen leicht erschöpft sein.
Daher bietet OpenResty zwei Direktiven, lua_max_pending_timers
und lua_max_running_timers
, um sie zu begrenzen. Ersteres stellt die maximale Anzahl von Timern dar, die auf die Ausführung warten, und Letzteres die maximale Anzahl der derzeit laufenden Timer.
Sie können auch die Lua-API verwenden, um die Werte der derzeit wartenden und laufenden Timer-Aufgaben zu erhalten, wie in den folgenden beiden Beispielen gezeigt.
content_by_lua_block {
ngx.timer.at(3, function() end)
ngx.say(ngx.timer.pending_count())
}
Dieser Code gibt eine 1
aus, was bedeutet, dass eine geplante Aufgabe auf die Ausführung wartet.
content_by_lua_block {
ngx.timer.at(0.1, function() ngx.sleep(0.3) end)
ngx.sleep(0.2)
ngx.say(ngx.timer.running_count())
}
Dieser Code gibt eine 1
aus, was bedeutet, dass eine geplante Aufgabe ausgeführt wird.
Privilegierter Prozess
Als Nächstes betrachten wir den privilegierten Prozess. Wie wir alle wissen, ist NGINX in Master
-Prozess und Worker
-Prozesse unterteilt, wobei die Worker-Prozesse Benutzeranfragen bearbeiten. Wir können den Typ des Prozesses über die in lua-resty-core
bereitgestellte process.type
-API erhalten. Zum Beispiel können Sie resty
verwenden, um die folgende Funktion auszuführen.
$ resty -e 'local process = require "ngx.process"
ngx.say("process type:", process.type())'
Sie werden sehen, dass es ein single
-Ergebnis zurückgibt und nicht worker
, was bedeutet, dass resty
NGINX mit einem Worker
-Prozess startet, nicht mit einem Master
-Prozess. Dies ist wahr. In der resty
-Implementierung können Sie sehen, dass der Master
-Prozess mit einer Zeile wie dieser deaktiviert wird.
master_process off;
OpenResty erweitert NGINX durch das Hinzufügen eines privileged agent
. Der privilegierte Prozess hat die folgenden besonderen Merkmale:
-
Er überwacht keine Ports, was bedeutet, dass er keine Dienste nach außen bereitstellt.
-
Er hat die gleichen Privilegien wie der
Master
-Prozess, was in der Regel die Privilegien desroot
-Benutzers sind, was es ihm ermöglicht, viele Aufgaben auszuführen, die für denWorker
-Prozess unmöglich sind. -
Der privilegierte Prozess kann nur im
init_by_lua
-Kontext geöffnet werden. -
Außerdem macht der privilegierte Prozess nur Sinn, wenn er im
init_worker_by_lua
-Kontext läuft, da keine Anfragen ausgelöst werden und sie nicht in diecontent
-,access
- usw. Kontexte gelangen.
Schauen wir uns ein Beispiel für einen privilegierten Prozess an, der geöffnet wird.
init_by_lua_block {
local process = require "ngx.process"
local ok, err = process.enable_privileged_agent()
if not ok then
ngx.log(ngx.ERR, "enables privileged agent failed error:", err)
end
}
Nachdem der privilegierte Prozess mit diesem Code geöffnet und der OpenResty-Dienst gestartet wurde, können wir sehen, dass der privilegierte Prozess jetzt Teil des NGINX-Prozesses ist.
nginx: master process
nginx: worker process
nginx: privileged agent process
Wenn jedoch Privilegien nur einmal während der init_worker_by_lua
-Phase ausgeführt werden, was keine gute Idee ist, wie sollten wir dann den privilegierten Prozess auslösen?
Ja, die Antwort liegt in dem gerade vermittelten Wissen. Da er keine Ports überwacht, d.h. er kann nicht durch Terminalanfragen ausgelöst werden, ist die einzige Möglichkeit, ihn periodisch auszulösen, die Verwendung des gerade vorgestellten ngx.timer
:
init_worker_by_lua_block {
local process = require "ngx.process"
local function reload(premature)
local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")
if not f then
return
end
local pid = f:read()
f:close()
os.execute("kill -HUP " .. pid)
end
if process.type() == "privileged agent" then
local ok, err = ngx.timer.every(5, reload)
if not ok then
ngx.log(ngx.ERR, err)
end
end
}
Der obige Code implementiert die Fähigkeit, alle 5 Sekunden HUP
-Signale an den Master-Prozess zu senden. Natürlich können Sie darauf aufbauen, um noch spannendere Dinge zu tun, wie z.B. die Datenbank abzufragen, um zu sehen, ob es Aufgaben für den privilegierten Prozess gibt, und diese auszuführen. Da der privilegierte Prozess root
-Privilegien hat, ist dies offensichtlich ein bisschen wie ein "Hintertür"-Programm.
Nicht-blockierendes ngx.pipe
Schließlich betrachten wir das nicht-blockierende ngx.pipe
, das die Standardbibliothek von Lua verwendet, um einen externen Befehl auszuführen, der in dem gerade beschriebenen Codebeispiel ein Signal an den Master
-Prozess sendet.
os.execute("kill -HUP " .. pid)
Natürlich blockiert dieser Vorgang. Gibt es also eine nicht-blockierende Möglichkeit, externe Programme in OpenResty aufzurufen? Schließlich wissen Sie, dass Sie, wenn Sie OpenResty als vollständige Entwicklungsplattform und nicht als Webserver verwenden, dies benötigen. Aus diesem Grund wurde die lua-resty-shell
-Bibliothek erstellt, und ihre Verwendung zum Aufrufen der Befehlszeile ist nicht-blockierend:
$ resty -e 'local shell = require "resty.shell"
local ok, stdout, stderr, reason, status =
shell.run([[echo "hello, world"]])
ngx.say(stdout)
Dieser Code ist eine andere Art, hello world
zu schreiben, indem der Systembefehl echo
verwendet wird, um die Ausgabe zu erledigen. Ebenso können Sie resty.shell
als Alternative zum os.execute
-Aufruf in Lua verwenden.
Wir wissen, dass die zugrunde liegende Implementierung von lua-resty-shell
auf der ngx.pipe
-API in lua-resty-core
basiert, daher würde dieses Beispiel, das lua-resty-shell
verwendet, um hello world
auszugeben, mit ngx.pipe
so aussehen.
$ resty -e 'local ngx_pipe = require "ngx.pipe"
local proc = ngx_pipe.spawn({"echo", "hello world"})
local data, err = proc:stdout_read_line()
ngx.say(data)'
Das obige ist der zugrunde liegende Code der lua-resty-shell
-Implementierung. Sie können die ngx.pipe
-Dokumentation und Testfälle überprüfen, um mehr über die Verwendung zu erfahren. Daher werde ich hier nicht weiter darauf eingehen.
Zusammenfassung
Das war's. Wir haben den Hauptinhalt für heute abgeschlossen. Aus den oben genannten Funktionen können wir sehen, dass OpenResty auch versucht, sich in Richtung einer universellen Plattform zu bewegen, während es ein besserer NGINX wird, in der Hoffnung, dass Entwickler versuchen können, den Technologie-Stack zu vereinheitlichen und OpenResty zu verwenden, um ihre Entwicklungsanforderungen zu lösen. Dies ist ziemlich freundlich für den Betrieb und die Wartung, da die Wartungskosten niedriger sind, solange Sie ein OpenResty darauf bereitstellen.
Abschließend hinterlasse ich Ihnen eine nachdenkliche Frage. Da es möglicherweise mehrere NGINX-Worker
gibt, wird der Timer
einmal für jeden Worker
ausgeführt, was in den meisten Szenarien nicht akzeptabel ist. Wie können wir sicherstellen, dass der Timer
nur einmal ausgeführt wird?
Hinterlassen Sie gerne einen Kommentar mit Ihrer Lösung und teilen Sie diesen Artikel gerne mit Ihren Kollegen und Freunden, damit wir gemeinsam kommunizieren und uns verbessern können.