Was ist LuaJIT? Warum wählt APISIX LuaJIT?
Tao Yang
April 14, 2023
Sind Sie bereit, Ihr API-Gateway-Spiel auf die nächste Stufe zu heben? Dann sollten Sie vielleicht auf Apache APISIX achten, ein cloud-natives API-Gateway, das in der Entwicklergemeinschaft für Aufsehen sorgt. Noch interessanter ist, dass Apache APISIX hauptsächlich mit LuaJIT entwickelt wurde, einer leichtgewichtigen und effizienten Sprache, die nicht so bekannt ist wie einige ihrer Gegenstücke.
In diesem Artikel werfen wir einen genaueren Blick darauf, warum Apache APISIX LuaJIT gegenüber bekannteren Sprachen und Technologien bevorzugt hat und wie die einzigartigen Funktionen und Vorteile von LuaJIT Ihnen helfen können, blitzschnelle API-Gateways zu erstellen, die selbst die anspruchsvollsten Workloads bewältigen können. Wenn Sie also Ihr API-Gateway aufrüsten möchten, lesen Sie weiter!
Was ist LuaJIT
Definition
Einfach ausgedrückt ist LuaJIT die Implementierung eines Just-in-Time (JIT)-Compilers für die Programmiersprache Lua. Zum besseren Verständnis können Leser, die mit LuaJIT nicht vertraut sind, es in zwei Teile zerlegen: Lua und JIT.
Lua
Lua ist eine elegante und leicht zu erlernende Programmiersprache, die sich durch automatische Speicherverwaltung, vollständige lexikalische Gültigkeitsbereiche, Closures, Iteratoren, Coroutinen, korrekte Tail Calls und praktische Datenverarbeitung mit assoziativen Arrays auszeichnet. Weitere Informationen zur Lua-Syntax finden Sie im Artikel Getting Started With Lua.
Lua ist so konzipiert, dass es leicht in C oder andere weit verbreitete Programmiersprachen integriert werden kann, wodurch Entwickler die Stärken dieser Sprachen nutzen können. Es bietet Funktionen, die in Sprachen wie C normalerweise nicht stark vertreten sind, wie z.B. hardwarenahe Abstraktionen, dynamische Strukturen und einfache Tests. Sein kleiner Sprachkern und die Einhaltung des ANSI-C-Standards machen es hochgradig portabel auf verschiedenen Plattformen. Daher ist Lua nicht nur eine Skriptsprache, die als eigenständiges Programm ausgeführt werden kann, sondern auch eine eingebettete Sprache, die in andere Anwendungen integriert werden kann.
Zu diesem Zeitpunkt hatte Lua jedoch noch zwei häufige Probleme, die in traditionellen Skriptsprachen zu finden sind: geringe Effizienz und offengelegter Code. Die von LuaJIT eingeführte JIT-Technologie kann diese beiden Probleme effektiv lösen.
JIT
JIT (Just-In-Time Compilation) ist eine Form der dynamischen Kompilierung. Dynamische Kompilierung ist nicht die einzige Form der Kompilierung in der Informatik. Beispielsweise verwendet die weit verbreitete Sprache C eine andere Form der Kompilierung, die als statische Kompilierung bekannt ist.
Es ist wichtig zu beachten, dass wir zwar oft den Begriff Ahead-of-Time Compilation (AOT) verwenden, um das Gegenteil der in C verwendeten dynamischen Kompilierung zu beschreiben, die beiden jedoch nicht vollständig gleichwertig sind. AOT beschreibt nur das Verhalten, eine "höhere" Sprache in eine "niedrigere" Sprache zu kompilieren, bevor das Programm ausgeführt wird. Die Zielsprache der Kompilierung ist nicht unbedingt maschinenspezifischer Code, sondern beliebig definiert. Beispielsweise wäre das Kompilieren von Java nach C oder das Kompilieren von JavaScript nach V8 ebenfalls als AOT zu betrachten. Da alle statischen Kompilierungen technisch gesehen im Voraus ausgeführt werden, kann AOT in diesem spezifischen Kontext als statische Kompilierung im Gegensatz zu JIT betrachtet werden.
Wenn wir diese komplexen Begriffe beiseite lassen und die Ausgabe der statischen Kompilierung betrachten, stellen wir fest, dass die Probleme, mit denen die Sprache Lua konfrontiert ist, auch durch statische Kompilierung gelöst werden könnten. Dies würde jedoch den Vorteil verlieren, den Lua als Skriptsprache bietet: die Flexibilität von Hot Updates und eine gute Plattformkompatibilität. Daher verwenden derzeit die meisten Skriptsprachen, außer denen mit speziellen Anforderungen, JIT, um die Sprachleistung zu verbessern, wie z.B. V8's JavaScript auf der Chromium-Plattform und Ruby mit YJIT.
JIT versucht, die Vor- und Nachteile von Luas dynamischer Interpretation und Cs statischer Kompilierung zu kombinieren. Während der Ausführung der Skriptsprache analysiert JIT kontinuierlich die ausgeführten Codefragmente und kompiliert oder rekompiliert sie, um die Ausführungseffizienz zu verbessern. Dabei geht JIT davon aus, dass der durch diesen Prozess erzielte Leistungsgewinn die Kosten für das Kompilieren oder Rekompilieren des Codes überwiegt. Theoretisch kann JIT, da es dynamisch rekompiliert werden kann, für die spezifische Plattformarchitektur, auf der das laufende Programm basiert, optimieren und beschleunigen und in einigen Fällen eine schnellere Ausführungsgeschwindigkeit als eine statische Kompilierung erzielen.
JIT wird in zwei Typen unterteilt: den traditionellen Method JIT
und den Trace JIT
, der derzeit von LuaJIT verwendet wird. Method JIT übersetzt jede Methode in Maschinencode, während Trace JIT, der fortschrittlicher ist, davon ausgeht, dass "Für Code, der nur einmal oder zweimal ausgeführt wird, die interpretierte Ausführung schneller ist als die JIT-kompilierte Ausführung". Basierend darauf optimiert Trace JIT den traditionellen JIT, indem er häufig ausgeführte Codefragmente (d.h. Code auf dem Hot Path) als Code identifiziert, der verfolgt werden muss, und diesen Teil des Codes in Maschinencode kompiliert, wie im folgenden Diagramm dargestellt.
LuaJIT
LuaJIT (Version 2.x) verbessert die JIT-Leistung erheblich, indem es einen in Assembler geschriebenen Hochgeschwindigkeitsinterpreter und einen optimierten Codegenerator-Backend basierend auf Static Single Assignment (SSA) integriert. Dadurch ist LuaJIT eine der schnellsten Implementierungen einer dynamischen Sprache geworden.
Darüber hinaus implementiert LuaJIT im Vergleich zur umständlichen Bindung von Lua und C in nativen Lua für die C-Interaktion auch FFI (Foreign Function Interface). Diese Technologie ermöglicht es uns, externe C-Funktionen aufzurufen und C-Datenstrukturen direkt aus Lua-Code zu verwenden, ohne die Anzahl und den Typ der Parameter zu kennen. Mit dieser Funktion können wir direkt FFI verwenden, um die erforderlichen Datenstrukturen anstelle des nativen Lua Table
-Typs zu implementieren, was die Programmierleistung in leistungskritischen Szenarien weiter verbessert. Die Techniken zur Verbesserung der Leistung mit FFI gehen über den Rahmen dieses Artikels hinaus, und weitere vertiefende Informationen finden Sie im Artikel Why Does lua-resty-core Perform Better?.
Zusammenfassend hat LuaJIT einen der schnellsten Trace JITs in Skriptsprachen implementiert, der Lua-Syntax verwendet. Darüber hinaus bietet es Funktionen wie FFI, um die Probleme der geringen Effizienz und des offengelegten Codes von Lua zu lösen, wodurch Lua zu einer hochflexiblen, leistungsstarken und speichersparenden Skript- und eingebetteten Sprache wird.
Vergleich zu WASM und anderen Sprachen
Im Vergleich zu Lua und LuaJIT sind wir möglicherweise mit einigen anderen Sprachen vertrauter, wie z.B. JavaScript (Node.js), Python, Golang, Java usw. Durch den Vergleich von Lua/LuaJIT mit diesen beliebten Sprachen können wir die einzigartigen Funktionen und Vorteile von LuaJIT besser verstehen. Im Folgenden finden Sie einige kurze Vergleiche:
- Die Syntax von Lua ist für Nicht-Software-Ingenieure konzipiert. Ähnlich wie die R-Sprache beginnt auch Lua mit einem Array-Index von 1, was für normale Benutzer geeignet ist.
- Lua eignet sich sehr gut als eingebettete Sprache. Lua selbst hat eine leichtgewichtige VM, und LuaJIT bleibt auch nach dem Hinzufügen verschiedener Funktionen und Optimierungen leichtgewichtig. Daher erhöht sich die Größe von LuaJIT nicht signifikant, wenn es direkt in C-Programme integriert wird, im Gegensatz zu großen Laufzeitumgebungen wie Node.js und Python. Daher ist Lua tatsächlich die am weitesten verbreitete und mainstream Wahl unter allen eingebetteten Sprachen.
- Lua eignet sich auch sehr gut als "Klebstoff"-Sprache. Wie JavaScript (Node.js) und Python kann Lua auch verschiedene Bibliotheken und Codes sehr gut verbinden. Allerdings hat Lua im Vergleich zu anderen Sprachen eine höhere Kopplung mit dem zugrunde liegenden Ökosystem, sodass das Lua-Ökosystem in verschiedenen Bereichen möglicherweise nicht universell ist.
WASM (Web Assembly) ist eine aufstrebende plattformübergreifende Technologie. Diese Technologie, die ursprünglich entwickelt wurde, um JavaScript zu ergänzen und nicht zu ersetzen, kann andere Sprachen in WASM-Bytecode kompilieren und Code als sicheren Sandbox ausführen, wodurch immer mehr Programme erwägen, WASM als eingebettete oder "Klebstoff"-Plattform zu verwenden. Dennoch hat Lua/LuaJIT im Vergleich zu WASM viele Vorteile:
- Die Leistung von WASM ist begrenzt und kann nicht das Niveau von Assembler erreichen. In allgemeinen Szenarien ist WASM sicherlich besser als Lua in Bezug auf die Leistung, aber es gibt immer noch eine Lücke zwischen WASM und LuaJIT.
- Die Datentransfereffizienz zwischen WASM und dem Host-Programm ist relativ gering. Andererseits kann LuaJIT durch FFI einen effizienten Datentransfer durchführen.
Warum wählt Apache APISIX LuaJIT?
Obwohl oben viele Vorteile von LuaJIT beschrieben wurden, ist Lua weder eine populäre Sprache noch eine beliebte Wahl für die meisten Entwickler. Warum hat also Apache APISIX, ein cloud-natives API-Gateway unter der Apache Foundation, LuaJIT gewählt?
Als cloud-natives API-Gateway hat Apache APISIX die Eigenschaften, dynamisch, in Echtzeit und leistungsstark zu sein, und bietet umfangreiche Traffic-Management-Funktionen wie Lastenausgleich, dynamisches Upstream, Canary Release, Dienstdegradierung, Identitäts-Authentifizierung, Beobachtbarkeit usw. Wir können Apache APISIX verwenden, um traditionellen Nord-Süd-Traffic sowie Ost-West-Traffic zwischen Diensten zu verarbeiten, und es kann auch als Ingress-Controller für k8s dienen.
All dies basiert auf dem von Apache APISIX gewählten NGINX- und LuaJIT-Technologie-Stack.
Vorteile der Kombination von LuaJIT mit NGINX
NGINX ist ein bekannter Hochleistungs-Webserver, der als HTTP-, TCP/UDP-Proxy und Reverse-Proxy dient.
In der Praxis finden wir es jedoch ärgerlich, dass wir jedes Mal, wenn wir die NGINX-Konfigurationsdatei ändern, den Befehl nginx -s reload
verwenden müssen, um die NGINX-Konfiguration neu zu laden.
Darüber hinaus kann die häufige Verwendung dieses Befehls zum Neuladen der Konfiguration zu Verbindungsinstabilität führen und die Wahrscheinlichkeit von Geschäftsverlusten erhöhen. In einigen Fällen kann der NGINX-Konfigurationsneulademechanismus auch dazu führen, dass alte Prozesse zu lange benötigen, um zurückgewonnen zu werden, was den normalen Geschäftsbetrieb beeinträchtigt. Für eine umfassendere Analyse dieses Themas empfehlen wir den Artikel Why NGINX's reload is not a hot reload?. Wir werden dieses Thema hier nicht weiter vertiefen.
Einer der Zwecke von Apache APISIX ist es, das Problem der dynamischen Konfiguration von NGINX zu lösen. Die hohe Flexibilität, hohe Leistung und der extrem geringe Speicherverbrauch von LuaJIT machen dies möglich. Nehmen wir als Beispiel die häufigste Route: Apache APISIX konfiguriert nur einen einzigen Standort als Haupteinstiegspunkt in der NGINX-Konfigurationsdatei, und die anschließende Routenverteilung wird durch das Routenverteilungsmodul von APISIX abgeschlossen, wodurch eine dynamische Konfiguration der Routen erreicht wird.
Um hohe Leistung zu erzielen, verwendet Apache APISIX einen in C geschriebenen, präfixbaum-basierten Routenabgleichsalgorithmus und bietet darauf aufbauend eine Schnittstelle für Lua unter Verwendung von FFI, das von LuaJIT bereitgestellt wird. Die Flexibilität von Lua ermöglicht es dem Routenverteilungsmodul von Apache APISIX auch, leicht die Abgleichung von Unterrouten desselben Präfix durch spezifische Ausdrücke und andere Methoden zu unterstützen. Letztendlich wird durch den Ersatz der nativen Routenverteilungsfunktion von NGINX eine dynamische Konfigurationsfunktionalität mit hoher Leistung und Flexibilität erreicht. Für die detaillierte Implementierung dieser Funktion können Sie sich auf lua-resty-radixtree und route.lua beziehen.
Neben der Routenverteilung kann APISIX auch Funktionen wie Lastenausgleich, Gesundheitschecks, Upstream-Knotenkonfiguration, Serverzertifikate und Plugins, die die Fähigkeiten von APISIX erweitern, ohne Neustart des Servers neu laden.
Darüber hinaus unterstützt Apache APISIX neben der Entwicklung von Plugins und anderen Funktionen mit LuaJIT auch die Entwicklung von Plugins in verschiedenen Sprachen wie Java, Go, Node, Python und WASM. Dies verringert die Schwelle für die benutzerdefinierte Entwicklung von Apache APISIX erheblich, was zu einem reichen Plugin-Ökosystem und einer aktiven Open-Source-Community führt.
Fazit
LuaJIT ist eine Implementierung von Lua, einem Just-in-Time-Compiler.
Als dynamisches, echtzeitfähiges und leistungsstarkes Open-Source-API-Gateway bietet Apache APISIX umfangreiche Traffic-Management-Funktionen wie Lastenausgleich, dynamisches Upstream, Canary Release, Circuit Breaker, Identitätsauthentifizierung und Beobachtbarkeit, basierend auf der hohen Leistung und Flexibilität, die NGINX und LuaJIT bieten.
Derzeit hat Apache APISIX eine neue Version, 3.x, veröffentlicht, die mehr Integrationen mit Open-Source-Projekten und Cloud-Anbietern, native gRPC-Unterstützung, zusätzliche Plugin-Entwicklungsoptionen und Service-Mesh-Unterstützung umfasst. Treten Sie der Apache APISIX-Community bei, um mehr über die Anwendung von LuaJIT in cloud-nativen API-Gateways zu erfahren.