¿Qué es LuaJIT? ¿Por qué APISIX elige LuaJIT?
Tao Yang
April 14, 2023
¿Estás listo para llevar tu juego de puerta de enlace de API al siguiente nivel? Entonces, quizás quieras prestar atención a Apache APISIX, una puerta de enlace de API nativa de la nube que ha estado causando revuelo en la comunidad de desarrolladores. Lo que es aún más interesante es que Apache APISIX está construido principalmente utilizando LuaJIT, un lenguaje ligero y eficiente que no es tan conocido como algunos de sus homólogos.
En este artículo, analizaremos más de cerca por qué Apache APISIX eligió LuaJIT sobre lenguajes y tecnologías más populares, y cómo las características y ventajas únicas de LuaJIT pueden ayudarte a construir puertas de enlace de API extremadamente rápidas que pueden manejar incluso las cargas de trabajo más exigentes. Así que, si estás buscando potenciar tu puerta de enlace de API, ¡sigue leyendo!
¿Qué es LuaJIT?
Definición
En pocas palabras, LuaJIT es la implementación de un compilador just-in-time (JIT) para el lenguaje de programación Lua. Para una mejor comprensión, los lectores que no estén familiarizados con LuaJIT pueden desglosarlo en dos partes: Lua y JIT.
Lua
Lua es un lenguaje de programación elegante y fácil de aprender que cuenta con gestión automática de memoria, alcance léxico completo, clausuras, iteradores, corutinas, llamadas de cola adecuadas y manejo práctico de datos utilizando arreglos asociativos. Para obtener más información sobre la sintaxis de Lua, te invitamos a leer Getting Started With Lua.
Lua está diseñado para integrarse fácilmente con C u otros lenguajes de programación ampliamente utilizados, permitiendo a los desarrolladores aprovechar las fortalezas de esos lenguajes. Proporciona características que no son típicamente puntos fuertes de lenguajes como C, como abstracciones de alto nivel relativas al hardware, estructuras dinámicas y pruebas simples. Su pequeño núcleo de lenguaje y su dependencia del estándar ANSI C lo hacen altamente portable en diferentes plataformas. Como resultado, Lua no solo es un lenguaje de scripting que puede ejecutarse como un programa independiente, sino también un lenguaje embebido que puede integrarse en otras aplicaciones.
Sin embargo, en este momento, Lua todavía tenía dos problemas comunes encontrados en los lenguajes de scripting tradicionales: baja eficiencia y código expuesto. La tecnología JIT introducida por LuaJIT puede resolver efectivamente estos dos problemas.
JIT
JIT (Compilación Just-In-Time), es una forma de compilación dinámica. La compilación dinámica no es la única forma de compilación en la informática. Por ejemplo, el lenguaje C ampliamente utilizado emplea una forma diferente de compilación, conocida como compilación estática.
Es importante tener en cuenta que, aunque a menudo usamos el término Compilación Ahead-of-Time (AOT) para describir lo opuesto a la compilación dinámica utilizada en C, los dos no son completamente equivalentes. AOT solo describe el comportamiento de compilar un lenguaje de "alto nivel" a un lenguaje de "bajo nivel" antes de ejecutar el programa. El lenguaje objetivo de su compilación no es necesariamente código de máquina específico para la máquina anfitriona del programa, sino arbitrariamente definido. Por ejemplo, compilar Java a C o compilar JavaScript a V8 también se consideraría como AOT. Dado que toda la compilación estática se ejecuta técnicamente antes de tiempo, en este contexto específico, AOT puede verse como una compilación estática opuesta a JIT.
Dejando de lado estos términos complejos, al considerar el resultado de la compilación estática, puedes descubrir que los problemas enfrentados por el lenguaje Lua también pueden resolverse mediante la compilación estática. Sin embargo, esto resultaría en la pérdida de la ventaja que Lua proporciona como lenguaje de scripting: la flexibilidad de las actualizaciones en caliente y la buena compatibilidad de plataforma. Por lo tanto, actualmente, la mayoría de los lenguajes de scripting, excepto aquellos con requisitos especiales, están utilizando JIT para intentar mejorar el rendimiento del lenguaje, como JavaScript de V8 en la plataforma Chromium y Ruby utilizando YJIT.
JIT intenta combinar las ventajas y desventajas de la interpretación dinámica de Lua y la compilación estática de C. Durante la ejecución del lenguaje de scripting, JIT analiza continuamente los fragmentos de código que se están ejecutando, y los compila o recompila para mejorar la eficiencia de ejecución. En este punto, la suposición detrás de JIT es que la ganancia de rendimiento obtenida de este proceso superará el costo de compilar o recompilar el código. En teoría, dado que puede recompilarse dinámicamente, JIT puede optimizar y acelerar para la arquitectura de plataforma específica en la que se basa el programa en ejecución, y en algunos casos, producir velocidades de ejecución más rápidas que una compilación estática.
JIT se divide en dos tipos: el tradicional Method JIT
y el Trace JIT
actualmente utilizado por LuaJIT. Method JIT traduce cada método a código de máquina, mientras que Trace JIT, que es más avanzado, asume que "Para el código que solo se ejecuta una o dos veces, la ejecución interpretada es más rápida que la ejecución compilada JIT". Basándose en esto, Trace JIT optimiza el JIT tradicional identificando fragmentos de código ejecutados frecuentemente (es decir, código en el camino caliente) como código que necesita ser rastreado y compilando esta porción del código en código de máquina para su ejecución, como se muestra en el diagrama a continuación.
LuaJIT
LuaJIT (versión 2.x) mejora significativamente el rendimiento de JIT al integrar un intérprete de alta velocidad escrito en lenguaje ensamblador y un generador de código optimizado basado en Asignación Estática Única (SSA). Como resultado, LuaJIT se ha convertido en una de las implementaciones de lenguaje dinámico más rápidas.
Además, en comparación con la engorrosa vinculación de Lua y C en Lua nativa para la interacción con C, LuaJIT también implementa FFI (Interfaz de Función Extranjera). Esta tecnología nos permite llamar a funciones externas de C y utilizar estructuras de datos de C directamente desde el código Lua sin conocer el número y tipo de parámetros. Con esta característica, podemos utilizar directamente FFI para implementar las estructuras de datos requeridas en lugar del tipo Table
nativo de Lua, mejorando aún más el rendimiento del programa en escenarios sensibles al rendimiento. Las técnicas para utilizar FFI para mejorar el rendimiento están fuera del alcance de este artículo, y se puede encontrar información más detallada en el artículo Why Does lua-resty-core Perform Better?.
En resumen, LuaJIT ha implementado uno de los Trace JIT más rápidos en lenguajes de scripting hasta la fecha, utilizando la sintaxis de Lua. Además, proporciona características, como FFI, para abordar los problemas de baja eficiencia y código expuesto de Lua, convirtiendo así a Lua en un lenguaje de scripting y embebido altamente flexible, de alto rendimiento y con un consumo de memoria ultra bajo.
Comparación con WASM y otros lenguajes
En comparación con Lua y LuaJIT, podríamos estar más familiarizados con algunos otros lenguajes, como JavaScript (Node.js), Python, Golang, Java, etc. Al comparar Lua/LuaJIT con estos lenguajes populares, podemos obtener una mejor comprensión de las características y ventajas únicas de LuaJIT. A continuación, se presentan algunas comparaciones breves:
- La sintaxis de Lua está diseñada para ingenieros no de software. Similar al lenguaje R, Lua también tiene un índice de arreglo que comienza en 1, lo cual es adecuado para personas comunes.
- Lua es muy adecuado como lenguaje embebido. Lua en sí tiene una VM ligera, y LuaJIT sigue siendo ligero incluso después de agregar varias características y optimizaciones. Como resultado, el tamaño de LuaJIT no aumenta significativamente cuando se integra directamente en programas C, a diferencia de entornos de ejecución grandes como Node.js y Python. Por lo tanto, Lua es, de hecho, la opción más ampliamente utilizada y principal entre todos los lenguajes embebidos.
- Lua también es muy adecuado como un lenguaje "pegamento". Al igual que JavaScript (Node.js) y Python, Lua también puede conectar diferentes bibliotecas y códigos muy bien. Sin embargo, ligeramente diferente de otros lenguajes, Lua tiene un mayor acoplamiento con el ecosistema subyacente, por lo que el ecosistema de Lua puede no ser universal en diferentes campos.
WASM (Web Assembly) es una tecnología emergente multiplataforma. Esta tecnología, inicialmente diseñada para complementar en lugar de reemplazar a JavaScript, puede compilar otros lenguajes en bytecode WASM y ejecutar código como un sandbox seguro, haciendo que cada vez más programas consideren usar WASM como una plataforma embebida o "pegamento". Aun así, Lua/LuaJIT todavía tiene muchas ventajas en comparación con el emergente WASM:
- El rendimiento de WASM es limitado y no puede alcanzar el nivel de ensamblador. En escenarios generales, WASM ciertamente es mejor que Lua en términos de rendimiento, pero todavía hay una brecha entre WASM y LuaJIT.
- La eficiencia de transmisión de datos entre WASM y el programa anfitrión es relativamente baja. Por otro lado, LuaJIT puede realizar una transmisión de datos eficiente a través de FFI.
¿Por qué Apache APISIX eligió LuaJIT?
Aunque se han descrito muchas ventajas de LuaJIT anteriormente, Lua no es un lenguaje popular ni una elección popular para la mayoría de los desarrolladores. Entonces, ¿por qué Apache APISIX, una puerta de enlace de API nativa de la nube bajo la Fundación Apache, eligió LuaJIT?
Como una puerta de enlace de API nativa de la nube, Apache APISIX tiene las características de ser dinámica, en tiempo real y de alto rendimiento, proporcionando funciones ricas de gestión de tráfico como balanceo de carga, upstream dinámico, lanzamiento canario, degradación de servicio, autenticación de identidad, observabilidad, etc. Podemos usar Apache APISIX para manejar el tráfico tradicional norte-sur, así como el tráfico este-oeste entre servicios, y también puede servir como un controlador Ingress para k8s.
Todo esto está construido sobre la pila tecnológica de NGINX y LuaJIT elegida por Apache APISIX.
Ventajas de combinar LuaJIT con NGINX
NGINX es un servidor web de alto rendimiento bien conocido que sirve como proxy HTTP, TCP/UDP y proxy inverso.
Sin embargo, en la práctica, encontramos molesto que cada vez que modificamos el archivo de configuración de NGINX, necesitamos usar el comando nginx -s reload
para recargar la configuración de NGINX.
Además, el uso frecuente de este comando para recargar la configuración puede causar inestabilidad en las conexiones y aumentar la probabilidad de pérdida de negocio. En algunos casos, el mecanismo de recarga de configuración de NGINX también puede hacer que los procesos antiguos tarden demasiado en ser reclamados, afectando las operaciones normales del negocio. Para un análisis más completo de este tema, recomendamos leer el artículo Why NGINX's reload is not a hot reload?. No exploraremos este tema en más detalle aquí.
Uno de los propósitos de Apache APISIX es resolver el problema de configuración dinámica de NGINX. La alta flexibilidad, el alto rendimiento y el uso ultra bajo de memoria de LuaJIT hacen que esto sea posible. Tomando la ruta más común como ejemplo, Apache APISIX solo configura una única ubicación como punto de entrada principal en el archivo de configuración de NGINX, y la distribución de rutas posterior se completa mediante el módulo de distribución de rutas de APISIX, logrando así una configuración dinámica de rutas.
Para lograr un alto rendimiento, Apache APISIX utiliza un algoritmo de coincidencia de rutas basado en árboles de prefijos escrito en C, y sobre esto, proporciona una interfaz para Lua utilizando FFI proporcionado por LuaJIT. La flexibilidad de Lua también permite que el módulo de distribución de rutas de Apache APISIX admita fácilmente la coincidencia de rutas subordinadas del mismo prefijo a través de expresiones específicas y otros métodos. Finalmente, al reemplazar la función de distribución de rutas nativa de NGINX, logra una funcionalidad de configuración dinámica con alto rendimiento y flexibilidad. Para la implementación detallada de esta característica, puedes consultar lua-resty-radixtree y route.lua.
Además de las rutas, APISIX también puede recargar funciones como balanceo, comprobaciones de salud, configuración de nodos upstream, certificados de servidor y plugins que extienden las capacidades de APISIX sin reiniciar el servidor.
Además, además de desarrollar plugins y otras características utilizando LuaJIT, Apache APISIX también admite el desarrollo de plugins utilizando varios lenguajes como Java, Go, Node, Python y WASM. Esto reduce en gran medida el umbral para el desarrollo personalizado de Apache APISIX, lo que resulta en un ecosistema de plugins rico y una comunidad de código abierto activa.
Conclusión
LuaJIT es una implementación de Lua, un compilador just-in-time.
Como una puerta de enlace de API de código abierto dinámica, en tiempo real y de alto rendimiento, Apache APISIX proporciona funciones ricas de gestión de tráfico como balanceo de carga, upstream dinámico, lanzamiento canario, corte de circuito, autenticación de identidad y observabilidad, basadas en el alto rendimiento y alta flexibilidad proporcionados por NGINX y LuaJIT.
Actualmente, Apache APISIX ha lanzado una nueva versión, 3.x, que incluye más integraciones con proyectos de código abierto y proveedores de nube, soporte nativo para gRPC, opciones adicionales de desarrollo de plugins y soporte para malla de servicios. Únete a la comunidad de Apache APISIX para aprender más sobre la aplicación de LuaJIT en puertas de enlace de API nativas de la nube.