Qu'est-ce que LuaJIT ? Pourquoi APISIX choisit-il LuaJIT ?

Tao Yang

April 14, 2023

Products

Êtes-vous prêt à faire passer votre jeu de passerelle API au niveau supérieur ? Alors, vous pourriez vouloir prêter attention à Apache APISIX, une passerelle API cloud-native qui fait sensation dans la communauté des développeurs. Ce qui est encore plus intéressant, c'est qu'Apache APISIX est principalement construit en utilisant LuaJIT, un langage léger et efficace qui n'est pas aussi connu que certains de ses homologues.

Dans cet article, nous allons examiner de plus près pourquoi Apache APISIX a choisi LuaJIT plutôt que des langages et technologies plus populaires, et comment les fonctionnalités et avantages uniques de LuaJIT peuvent vous aider à construire des passerelles API ultra-rapides capables de gérer même les charges de travail les plus exigeantes. Donc, si vous cherchez à booster votre passerelle API, continuez à lire !

Qu'est-ce que LuaJIT

Définition

En bref, LuaJIT est l'implémentation d'un compilateur juste-à-temps (JIT) pour le langage de programmation Lua. Pour une meilleure compréhension, les lecteurs qui ne connaissent pas LuaJIT peuvent le décomposer en deux parties : Lua et JIT.

Lua

Lua est un langage de programmation élégant et facile à apprendre qui propose une gestion automatique de la mémoire, une portée lexicale complète, des fermetures, des itérateurs, des coroutines, des appels de queue appropriés et une manipulation pratique des données à l'aide de tableaux associatifs. Pour en savoir plus sur la syntaxe de Lua, vous pouvez lire Getting Started With Lua pour plus d'informations.

Lua est conçu pour être facilement intégré avec C ou d'autres langages de programmation largement utilisés, permettant aux développeurs de tirer parti des forces de ces langages. Il fournit des fonctionnalités qui ne sont généralement pas des points forts des langages comme C, tels que des abstractions de haut niveau par rapport au matériel, des structures dynamiques et des tests simples. Son noyau de langage réduit et sa dépendance à la norme ANSI C le rendent hautement portable sur différentes plateformes. Par conséquent, Lua est non seulement un langage de script qui peut fonctionner comme un programme autonome, mais aussi un langage embarqué qui peut être intégré dans d'autres applications.

Apache APISIX est un excellent exemple d'utilisation à la fois de Lua et de C au niveau bas

Cependant, à cette époque, Lua avait encore deux problèmes courants des langages de script traditionnels : une faible efficacité et un code exposé. La technologie JIT introduite par LuaJIT peut résoudre efficacement ces deux problèmes.

JIT

JIT (Compilation Juste-À-Temps), est une forme de compilation dynamique. La compilation dynamique n'est pas la seule forme de compilation en informatique. Par exemple, le langage C largement utilisé emploie une forme différente de compilation, connue sous le nom de compilation statique.

Il est important de noter que bien que nous utilisions souvent le terme Compilation À l'Avance (AOT) pour décrire l'opposé de la compilation dynamique utilisée en C, les deux ne sont pas complètement équivalents. AOT ne décrit que le comportement de compilation d'un langage "haut niveau" en un langage "bas niveau" avant l'exécution du programme. Le langage cible de sa compilation n'est pas nécessairement du code machine spécifique à la machine hôte du programme, mais arbitrairement défini. Par exemple, compiler Java en C ou compiler JavaScript en V8 serait également considéré comme de l'AOT. Puisque toute compilation statique est techniquement exécutée à l'avance, dans ce contexte spécifique, AOT peut être vu comme une compilation statique opposée à JIT.

En mettant de côté ces termes complexes, en considérant la sortie de la compilation statique, vous pourriez découvrir que les problèmes rencontrés par le langage Lua peuvent également être résolus par la compilation statique. Cependant, cela entraînerait la perte de l'avantage que Lua offre en tant que langage de script : la flexibilité des mises à jour à chaud et une bonne compatibilité de plateforme. Par conséquent, actuellement, la plupart des langages de script, à l'exception de ceux ayant des exigences particulières, utilisent JIT pour essayer d'améliorer les performances du langage, comme JavaScript V8 sur la plateforme Chromium et Ruby utilisant YJIT.

JIT essaie de combiner les avantages et les inconvénients de l'interprétation dynamique de Lua et de la compilation statique de C. Pendant l'exécution du langage de script, JIT analyse continuellement les fragments de code en cours d'exécution, et les compile ou recompile pour améliorer l'efficacité d'exécution. À ce stade, l'hypothèse derrière JIT est que le gain de performance obtenu grâce à ce processus dépassera le coût de compilation ou recompilation du code. En théorie, puisqu'il peut être dynamiquement recompilé, JIT peut optimiser et accélérer pour l'architecture de plateforme spécifique sur laquelle le programme en cours d'exécution est basé, et dans certains cas, produire des vitesses d'exécution plus rapides qu'une compilation statique.

JIT est divisé en deux types : le traditionnel Method JIT et le Trace JIT actuellement utilisé par LuaJIT. Method JIT traduit chaque méthode en code machine, tandis que Trace JIT, plus avancé, suppose que "Pour le code qui n'est exécuté qu'une ou deux fois, l'exécution interprétée est plus rapide que l'exécution compilée JIT". Sur cette base, Trace JIT optimise le JIT traditionnel en identifiant les fragments de code fréquemment exécutés (c'est-à-dire le code sur le chemin chaud) comme code à tracer et en compilant cette partie du code en code machine pour exécution, comme illustré dans le diagramme ci-dessous.

Le principe de LuaJIT

LuaJIT

LuaJIT (version 2.x) améliore significativement les performances de JIT en intégrant un interpréteur haute vitesse écrit en langage assembleur et un générateur de code backend optimisé basé sur Static Single Assignment (SSA). En conséquence, LuaJIT est devenu l'une des implémentations de langage dynamique les plus rapides.

En outre, par rapport à la liaison fastidieuse de Lua et C dans Lua natif pour l'interaction avec C, LuaJIT implémente également FFI (Foreign Function Interface). Cette technologie nous permet d'appeler des fonctions C externes et d'utiliser des structures de données C directement à partir du code Lua sans connaître le nombre et le type de paramètres. Avec cette fonctionnalité, nous pouvons directement utiliser FFI pour implémenter les structures de données nécessaires au lieu du type natif Lua Table, améliorant ainsi les performances du programme dans des scénarios sensibles à la performance. Les techniques pour utiliser FFI pour améliorer les performances dépassent le cadre de cet article, et des informations plus approfondies peuvent être trouvées dans l'article Why Does lua-resty-core Perform Better?.

En résumé, LuaJIT a implémenté l'un des Trace JIT les plus rapides dans les langages de script à ce jour, en utilisant la syntaxe Lua. De plus, il fournit des fonctionnalités, telles que FFI, pour résoudre les problèmes de faible efficacité et de code exposé de Lua, faisant ainsi de Lua un langage de script et embarqué hautement flexible, haute performance et à très faible empreinte mémoire.

Comparaison avec WASM et d'autres langages

Comparé à Lua et LuaJIT, nous pourrions être plus familiers avec d'autres langages, tels que JavaScript (Node.js), Python, Golang, Java, etc. En comparant Lua/LuaJIT avec ces langages populaires, nous pouvons mieux comprendre les fonctionnalités et avantages uniques de LuaJIT. Voici quelques brèves comparaisons :

  • La syntaxe de Lua est conçue pour les non-ingénieurs logiciels. Similaire au langage R, Lua a également un index de tableau commençant à 1, ce qui est adapté aux personnes ordinaires.
  • Lua est très adapté comme langage embarqué. Lua lui-même a une machine virtuelle légère, et LuaJIT reste léger même après l'ajout de diverses fonctionnalités et optimisations. Par conséquent, la taille de LuaJIT n'augmente pas significativement lorsqu'il est intégré directement dans des programmes C, contrairement à de grands environnements d'exécution tels que Node.js et Python. Ainsi, Lua est, en fait, le choix le plus largement utilisé et mainstream parmi tous les langages embarqués.
  • Lua est également très adapté comme langage "colle". Comme JavaScript (Node.js) et Python, Lua peut également connecter très bien différentes bibliothèques et codes. Cependant, légèrement différent des autres langages, Lua a un couplage plus élevé avec l'écosystème sous-jacent, donc l'écosystème Lua peut ne pas être universel dans différents domaines.

WASM (Web Assembly) est une technologie émergente multiplateforme. Cette technologie, initialement conçue pour compléter plutôt que remplacer JavaScript, peut compiler d'autres langages en bytecode WASM et exécuter du code comme un bac à sable sécurisé, faisant que de plus en plus de programmes envisagent d'utiliser WASM comme plateforme embarquée ou "colle". Même ainsi, Lua/LuaJIT a encore de nombreux avantages par rapport à WASM émergent :

  • Les performances de WASM sont limitées et ne peuvent pas atteindre le niveau de l'assembleur. Dans des scénarios généraux, WASM est certainement meilleur que Lua en termes de performances, mais il y a encore un écart entre WASM et LuaJIT.
  • L'efficacité de transmission des données entre WASM et le programme hôte est relativement faible. D'autre part, LuaJIT peut effectuer une transmission de données efficace via FFI.

Pourquoi Apache APISIX a-t-il choisi LuaJIT ?

Bien que de nombreux avantages de LuaJIT aient été décrits ci-dessus, Lua n'est ni un langage populaire ni un choix populaire pour la plupart des développeurs. Alors, pourquoi Apache APISIX, une passerelle API cloud-native sous la fondation Apache, a-t-il choisi LuaJIT ?

En tant que passerelle API cloud-native, Apache APISIX a les caractéristiques d'être dynamique, en temps réel et haute performance, fournissant des fonctions riches de gestion du trafic telles que l'équilibrage de charge, l'amont dynamique, la mise en production canari, la dégradation de service, l'authentification, l'observabilité, etc. Nous pouvons utiliser Apache APISIX pour gérer le trafic traditionnel nord-sud, ainsi que le trafic est-ouest entre les services, et il peut également servir de contrôleur Ingress pour k8s.

Tout cela est construit sur la pile technologique NGINX et LuaJIT choisie par Apache APISIX.

Avantages de la combinaison de LuaJIT avec NGINX

NGINX est un serveur web haute performance bien connu qui sert de proxy HTTP, TCP/UDP et de proxy inverse.

Cependant, en pratique, nous trouvons ennuyeux que chaque fois que nous modifions le fichier de configuration NGINX, nous devions utiliser la commande nginx -s reload pour recharger la configuration NGINX.

De plus, l'utilisation fréquente de cette commande pour recharger la configuration peut entraîner une instabilité de la connexion et augmenter la probabilité de perte d'activité. Dans certains cas, le mécanisme de rechargement de configuration NGINX peut également entraîner une récupération trop longue des anciens processus, affectant les opérations commerciales normales. Pour une analyse plus complète de ce sujet, nous recommandons de lire l'article Why NGINX's reload is not a hot reload?. Nous n'explorerons pas ce sujet plus en détail ici.

L'un des objectifs d'Apache APISIX est de résoudre le problème de configuration dynamique de NGINX. La haute flexibilité, la haute performance et la très faible utilisation de mémoire de LuaJIT rendent cela possible. Prenant l'exemple le plus courant de la route, Apache APISIX ne configure qu'un seul emplacement comme point d'entrée principal dans le fichier de configuration NGINX, et la distribution ultérieure des routes est effectuée par le module de distribution de routes d'APISIX, permettant ainsi une configuration dynamique des routes.

Pour atteindre une haute performance, Apache APISIX utilise un algorithme de correspondance de routes basé sur un arbre de préfixes écrit en C, et sur cette base, il fournit une interface pour Lua en utilisant FFI fourni par LuaJIT. La flexibilité de Lua permet également au module de distribution de routes d'Apache APISIX de facilement supporter la correspondance des routes subordonnées du même préfixe à travers des expressions spécifiques et d'autres méthodes. Finalement, en remplaçant la fonction native de distribution de routes de NGINX, il atteint une fonctionnalité de configuration dynamique avec haute performance et flexibilité. Pour l'implémentation détaillée de cette fonctionnalité, vous pouvez vous référer à lua-resty-radixtree et route.lua.

En plus de la distribution de routes, APISIX peut également recharger des fonctions comme l'équilibrage, les vérifications de santé, la configuration des nœuds amont, les certificats serveur et les plugins qui étendent les capacités d'APISIX sans redémarrer le serveur.

De plus, en plus de développer des plugins et d'autres fonctionnalités en utilisant LuaJIT, Apache APISIX supporte également le développement de plugins en utilisant divers langages tels que Java, Go, Node, Python et WASM. Cela réduit considérablement le seuil de développement personnalisé d'Apache APISIX, résultant en un écosystème de plugins riche et une communauté open-source active.

Le principe et l'écosystème des plugins d'Apache APISIX

Conclusion

LuaJIT est une implémentation de Lua, un compilateur juste-à-temps.

En tant que passerelle API open-source dynamique, en temps réel et haute performance, Apache APISIX fournit des fonctions riches de gestion du trafic telles que l'équilibrage de charge, l'amont dynamique, la mise en production canari, le disjoncteur, l'authentification et l'observabilité, basées sur la haute performance et la haute flexibilité apportées par NGINX et LuaJIT.

Actuellement, Apache APISIX a publié une nouvelle version, 3.x, qui inclut plus d'intégrations avec des projets open-source et des fournisseurs de cloud, un support natif de gRPC, des options supplémentaires de développement de plugins et un support de maillage de services. Rejoignez la communauté Apache APISIX pour en savoir plus sur l'application de LuaJIT dans les passerelles API cloud-native.

Tags: