O que é LuaJIT? Por que o APISIX escolhe LuaJIT?
Tao Yang
April 14, 2023
Você está pronto para levar seu jogo de gateway de API para o próximo nível? Então, você pode querer prestar atenção ao Apache APISIX, um gateway de API nativo da nuvem que tem causado impacto na comunidade de desenvolvedores. O que é ainda mais interessante é que o Apache APISIX é construído principalmente usando LuaJIT, uma linguagem leve e eficiente que não é tão conhecida quanto algumas de suas contrapartes.
Neste artigo, vamos dar uma olhada mais de perto no porquê o Apache APISIX escolheu LuaJIT em vez de linguagens e tecnologias mais populares, e como os recursos e vantagens únicos do LuaJIT podem ajudá-lo a construir gateways de API extremamente rápidos que podem lidar até mesmo com as cargas de trabalho mais exigentes. Então, se você está procurando turbinar seu gateway de API, continue lendo!
O que é LuaJIT
Definição
Simplificando, LuaJIT é a implementação de um compilador just-in-time (JIT) para a linguagem de programação Lua. Para melhor compreensão, leitores que não estão familiarizados com LuaJIT podem dividi-lo em duas partes: Lua e JIT.
Lua
Lua é uma linguagem de programação elegante e fácil de aprender que apresenta gerenciamento automático de memória, escopo léxico completo, closures, iteradores, corotinas, chamadas de cauda adequadas e manipulação prática de dados usando arrays associativos. Para mais leitura sobre a sintaxe do Lua, bem-vindo a ler Começando com Lua para mais informações.
Lua é projetado para ser facilmente integrado com C ou outras linguagens de programação amplamente utilizadas, permitindo que os desenvolvedores aproveitem os pontos fortes dessas linguagens. Ele fornece recursos que não são tipicamente pontos fortes de linguagens como C, como abstrações de alto nível em relação ao hardware, estruturas dinâmicas e testes simples. Seu pequeno núcleo de linguagem e dependência do padrão ANSI C tornam-no altamente portátil em diferentes plataformas. Como resultado, Lua não é apenas uma linguagem de script que pode ser executada como um programa autônomo, mas também uma linguagem embutida que pode ser integrada em outras aplicações.
No entanto, neste momento, Lua ainda tinha dois problemas comuns encontrados em linguagens de script tradicionais: baixa eficiência e código exposto. A tecnologia JIT introduzida pelo LuaJIT pode resolver efetivamente esses dois problemas.
JIT
JIT (Compilação Just-In-Time), é uma forma de compilação dinâmica. A compilação dinâmica não é a única forma de compilação na ciência da computação. Por exemplo, a linguagem C amplamente utilizada emprega uma forma diferente de compilação, conhecida como compilação estática.
É importante notar que, embora frequentemente usemos o termo Compilação Ahead-of-Time (AOT) para descrever o oposto da compilação dinâmica usada em C, os dois não são completamente equivalentes. AOT apenas descreve o comportamento de compilar uma linguagem "de alto nível" em uma linguagem "de baixo nível" antes de executar o programa. A linguagem alvo de sua compilação não é necessariamente código de máquina específico para a máquina hospedeira do programa, mas arbitrariamente definida. Por exemplo, compilar Java para C ou compilar JavaScript para V8 também seria considerado como AOT. Como toda compilação estática é tecnicamente executada antecipadamente, neste contexto específico, AOT pode ser visto como uma compilação estática oposta ao JIT.
Deixando de lado esses termos complexos, ao considerar a saída da compilação estática, você pode descobrir que os problemas enfrentados pela linguagem Lua também podem ser resolvidos pela compilação estática. No entanto, isso resultaria na perda da vantagem que o Lua oferece como uma linguagem de script: a flexibilidade de atualizações em tempo real e boa compatibilidade de plataforma. Portanto, atualmente, a maioria das linguagens de script, exceto aquelas com requisitos especiais, estão usando JIT para tentar melhorar o desempenho da linguagem, como o JavaScript V8 na plataforma Chromium e o Ruby usando YJIT.
JIT tenta combinar as vantagens e desvantagens da interpretação dinâmica do Lua e da compilação estática do C. Durante a execução da linguagem de script, o JIT analisa continuamente os fragmentos de código que estão sendo executados e os compila ou recompila para melhorar a eficiência de execução. Neste ponto, a suposição por trás do JIT é que o ganho de desempenho obtido a partir deste processo superará o custo de compilar ou recompilar o código. Em teoria, como pode ser recompilado dinamicamente, o JIT pode otimizar e acelerar para a arquitetura de plataforma específica na qual o programa em execução é baseado e, em alguns casos, produzir velocidades de execução mais rápidas do que uma compilação estática.
JIT é dividido em dois tipos: o tradicional Method JIT
e o Trace JIT
atualmente usado pelo LuaJIT. O Method JIT traduz cada método em código de máquina, enquanto o Trace JIT, que é mais avançado, assume que "Para código que é executado apenas uma ou duas vezes, a execução interpretada é mais rápida do que a execução compilada JIT". Com base nisso, o Trace JIT otimiza o JIT tradicional identificando fragmentos de código frequentemente executados (ou seja, código no caminho quente) como código que precisa ser rastreado e compilando essa parte do código em código de máquina para execução, como mostrado no diagrama abaixo.
LuaJIT
LuaJIT (versão 2.x) melhora significativamente o desempenho do JIT integrando um interpretador de alta velocidade escrito em linguagem Assembly e um backend gerador de código otimizado baseado em Atribuição Estática Única (SSA). Como resultado, LuaJIT tornou-se uma das implementações de linguagem dinâmica mais rápidas.
Além disso, em comparação com a vinculação complicada de Lua e C no Lua nativo para interação com C, LuaJIT também implementa FFI (Interface de Função Estrangeira). Essa tecnologia nos permite chamar funções C externas e usar estruturas de dados C diretamente do código Lua sem saber o número e o tipo de parâmetros. Com esse recurso, podemos usar diretamente o FFI para implementar as estruturas de dados necessárias em vez do tipo Table
nativo do Lua, melhorando ainda mais o desempenho do programa em cenários sensíveis ao desempenho. As técnicas para usar o FFI para melhorar o desempenho estão além do escopo deste artigo, e informações mais aprofundadas podem ser encontradas no artigo Por que o lua-resty-core tem um desempenho melhor?.
Em resumo, LuaJIT implementou um dos Trace JITs mais rápidos em linguagens de script até o momento, usando a sintaxe do Lua. Além disso, ele fornece recursos, como o FFI, para resolver os problemas de baixa eficiência e código exposto do Lua, tornando o Lua uma linguagem de script e embutida altamente flexível, de alto desempenho e com uso de memória ultrabaixo.
Comparação com WASM e Outras Linguagens
Em comparação com Lua e LuaJIT, podemos estar mais familiarizados com algumas outras linguagens, como JavaScript (Node.js), Python, Golang, Java, etc. Ao comparar Lua/LuaJIT com essas linguagens populares, podemos entender melhor os recursos e vantagens únicos do LuaJIT. Abaixo estão algumas comparações breves:
- A sintaxe do Lua é projetada para não engenheiros de software. Semelhante à linguagem R, Lua também tem um índice de array começando em 1, o que é adequado para pessoas comuns.
- Lua é muito adequado como uma linguagem embutida. O próprio Lua tem uma VM leve, e LuaJIT ainda é leve mesmo após adicionar vários recursos e otimizações. Como resultado, o tamanho do LuaJIT não aumenta significativamente quando integrado diretamente em programas C, ao contrário de grandes ambientes de execução como Node.js e Python. Assim, Lua é, de fato, a escolha mais amplamente utilizada e mainstream entre todas as linguagens embutidas.
- Lua também é muito adequado como uma linguagem "cola". Como JavaScript (Node.js) e Python, Lua também pode conectar diferentes bibliotecas e códigos muito bem. No entanto, ligeiramente diferente de outras linguagens, Lua tem um acoplamento maior com o ecossistema subjacente, então o ecossistema Lua pode não ser universal em diferentes campos.
WASM (Web Assembly) é uma tecnologia emergente de plataforma cruzada. Essa tecnologia, inicialmente projetada para complementar em vez de substituir o JavaScript, pode compilar outras linguagens em bytecode WASM e executar código como uma sandbox segura, fazendo com que cada vez mais programas considerem usar WASM como uma plataforma embutida ou "cola". Mesmo assim, Lua/LuaJIT ainda tem muitas vantagens em comparação com o emergente WASM:
- O desempenho do WASM é limitado e não pode atingir o nível de assembly. Em cenários gerais, o WASM certamente é melhor que o Lua em termos de desempenho, mas ainda há uma lacuna entre o WASM e o LuaJIT.
- A eficiência de transmissão de dados entre o WASM e o programa hospedeiro é relativamente baixa. Por outro lado, LuaJIT pode realizar transmissão de dados eficiente através do FFI.
Por que o Apache APISIX escolheu LuaJIT?
Embora muitas vantagens do LuaJIT tenham sido descritas acima, Lua não é uma linguagem popular nem uma escolha popular para a maioria dos desenvolvedores. Então, por que o Apache APISIX, um gateway de API nativo da nuvem sob a Fundação Apache, escolheu LuaJIT?
Como um gateway de API nativo da nuvem, o Apache APISIX tem as características de ser dinâmico, em tempo real e de alto desempenho, fornecendo funções ricas de gerenciamento de tráfego, como balanceamento de carga, upstream dinâmico, lançamento canário, degradação de serviço, autenticação de identidade, observabilidade, etc. Podemos usar o Apache APISIX para lidar com o tráfego tradicional norte-sul, bem como o tráfego leste-oeste entre serviços, e ele também pode servir como um controlador Ingress para k8s.
Tudo isso é construído na pilha de tecnologia NGINX e LuaJIT escolhida pelo Apache APISIX.
Vantagens de Combinar LuaJIT com NGINX
NGINX é um servidor web de alto desempenho bem conhecido que serve como um proxy HTTP, TCP/UDP e proxy reverso.
No entanto, na prática, achamos irritante que toda vez que modificamos o arquivo de configuração do NGINX, precisamos usar o comando nginx -s reload
para recarregar a configuração do NGINX.
Além disso, o uso frequente desse comando para recarregar a configuração pode causar instabilidade na conexão e aumentar a probabilidade de perda de negócios. Em alguns casos, o mecanismo de recarregamento de configuração do NGINX também pode fazer com que processos antigos demorem muito para serem recuperados, afetando as operações normais de negócios. Para uma análise mais abrangente desse tópico, recomendamos a leitura do artigo Por que o recarregamento do NGINX não é um recarregamento em tempo real?. Não exploraremos esse assunto em mais detalhes aqui.
Um dos propósitos do Apache APISIX é resolver o problema de configuração dinâmica do NGINX. A alta flexibilidade, alto desempenho e uso de memória ultrabaixo do LuaJIT tornam isso possível. Tomando a rota mais comum como exemplo, o Apache APISIX configura apenas um único local como o ponto de entrada principal no arquivo de configuração do NGINX, e a distribuição de rota subsequente é concluída pelo módulo de distribuição de rota do APISIX, alcançando assim uma configuração dinâmica de rotas.
Para alcançar alto desempenho, o Apache APISIX usa um algoritmo de correspondência de rota baseado em árvore de prefixo escrito em C, e, com base nisso, fornece uma interface para Lua usando o FFI fornecido pelo LuaJIT. A flexibilidade do Lua também permite que o módulo de distribuição de rota do Apache APISIX suporte facilmente a correspondência de rotas subordinadas do mesmo prefixo através de expressões específicas e outros métodos. Finalmente, ao substituir a função de distribuição de rota nativa do NGINX, ele alcança funcionalidade de configuração dinâmica com alto desempenho e flexibilidade. Para a implementação detalhada desse recurso, você pode consultar lua-resty-radixtree e route.lua.
Além do roteamento, o APISIX também pode recarregar funções como balanceamento, verificações de saúde, configuração de nós upstream, certificados de servidor e plugins que estendem as capacidades do APISIX sem reiniciar o servidor.
Além disso, além de desenvolver plugins e outros recursos usando LuaJIT, o Apache APISIX também suporta o desenvolvimento de plugins usando várias linguagens, como Java, Go, Node, Python e WASM. Isso reduz muito o limiar para desenvolvimento personalizado do Apache APISIX, resultando em um ecossistema rico de plugins e uma comunidade de código aberto ativa.
Conclusão
LuaJIT é uma implementação do Lua, um compilador just-in-time.
Como um gateway de API de código aberto dinâmico, em tempo real e de alto desempenho, Apache APISIX fornece funções ricas de gerenciamento de tráfego, como balanceamento de carga, upstream dinâmico, lançamento canário, disjuntor, autenticação de identidade e observabilidade, com base no alto desempenho e alta flexibilidade trazidos pelo NGINX e LuaJIT.
Atualmente, o Apache APISIX lançou uma nova versão, 3.x, que inclui mais integrações com projetos de código aberto e provedores de nuvem, suporte nativo a gRPC, opções adicionais de desenvolvimento de plugins e suporte a service mesh. Junte-se à comunidade Apache APISIX para saber mais sobre a aplicação do LuaJIT em gateways de API nativos da nuvem.