Três Bibliotecas Lua Resty Comumente Usadas no OpenResty

API7.ai

January 13, 2023

OpenResty (NGINX + Lua)

Aprender sobre linguagens de programação e plataformas geralmente é uma questão de entender as bibliotecas padrão e de terceiros, em vez da sintaxe em si. Depois de aprender sua API e técnicas de otimização de desempenho, precisamos aprender o uso de várias bibliotecas lua-resty para expandir nossas capacidades do OpenResty para mais cenários.

Onde encontrar a biblioteca lua-resty?

Comparado ao PHP, Python e JavaScript, as bibliotecas padrão e de terceiros do OpenResty ainda são relativamente escassas, e encontrar as bibliotecas lua-resty certas não é fácil. No entanto, aqui estão duas fontes recomendadas para ajudá-lo a encontrá-las mais rapidamente.

A primeira recomendação é o repositório awesome-resty mantido por Aapo. Este repositório organiza as bibliotecas relacionadas ao OpenResty por categoria e é abrangente, incluindo módulos C do NGINX, bibliotecas lua-resty, frameworks web, bibliotecas de roteamento, templates, frameworks de teste, etc. É sua primeira escolha para recursos do OpenResty.

Se você não encontrar a biblioteca certa no repositório de Aapo, também pode consultar o luarocks, topm ou GitHub. Pode haver algumas bibliotecas que não foram open-source por muito tempo e não receberam muita atenção.

Nos artigos anteriores, aprendemos sobre várias bibliotecas úteis, como lua-resty-mlcache, lua-resty-traffic, lua-resty-shell, etc. Hoje, no último artigo da seção de otimização de desempenho do OpenResty, conheceremos mais 3 bibliotecas periféricas únicas, todas contribuídas por desenvolvedores da comunidade.

Melhoria de desempenho do ngx.var

Primeiro, vamos ver um módulo C: lua-var-nginx-module. Como mencionei anteriormente, ngx.var é uma operação relativamente custosa em termos de desempenho. Assim, na prática, precisamos usar ngx.ctx como uma camada de cache.

Então, existe alguma maneira de resolver completamente o problema de desempenho do ngx.var?

Este módulo C faz algumas experiências nessa área, e os resultados são notáveis, com uma melhoria de desempenho de 5x em relação ao ngx.var. Ele usa a abordagem FFI, então você precisa compilar o OpenResty com a seguinte opção de compilação primeiro.

./configure --prefix=/opt/openresty \
         --add-module=/path/to/lua-var-nginx-module

Em seguida, use luarocks para instalar a biblioteca lua da seguinte forma:

luarocks install lua-resty-ngxvar

O método chamado aqui também é muito simples, exigindo apenas uma linha da função fetch. Funciona igual ao ngx.var.remote_addr original para obter o endereço IP do cliente.

content_by_lua_block {
    local var = require("resty.ngxvar")
    ngx.say(var.fetch("remote_addr"))
}

Depois de entender essas operações básicas, você pode ficar mais curioso sobre como este módulo alcança uma melhoria significativa de desempenho. Como sempre dizemos, "não há segredos diante do código-fonte". Então, vamos descobrir como buscar a variável remote_addr.

ngx_int_t
ngx_http_lua_var_ffi_remote_addr(ngx_http_request_t *r, ngx_str_t *remote_addr)
{
    remote_addr->len = r->connection->addr_text.len;
    remote_addr->data = r->connection->addr_text.data;

    return NGX_OK;
}

Depois de ler este código, você verá que esta abordagem Lua FFI é a mesma que a abordagem do lua-resty-core. Tem a vantagem óbvia de usar FFI para obter variáveis diretamente, contornando a lógica de busca original do ngx.var. Sua desvantagem é óbvia: adicionar funções C e chamadas FFI para cada variável que você deseja obter, o que consome tempo e energia.

Alguém pode perguntar: "Por que eu diria que isso consome tempo e energia? O código C acima não parece substancial?" Vamos dar uma olhada na origem dessas linhas de código, que vêm de src/http/ngx_http_variables.c no código do NGINX.

static ngx_int_t
ngx_http_variable_remote_addr(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
    v->len = r->connection->addr_text.len;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = r->connection->addr_text.data;

    return NGX_OK;
}

Depois de ver o código-fonte, o mistério é revelado! lua-var-nginx-module é um portador do código de variáveis do NGINX, com o empacotamento FFI na camada externa, e dessa forma, alcança a otimização de desempenho. Esta é uma boa ideia e uma boa direção para otimização.

Ao aprender uma biblioteca ou uma ferramenta, não devemos parar apenas no nível da operação, mas também perguntar por que fazemos isso e olhar o código-fonte. Claro, também encorajo fortemente você a contribuir com código para suportar mais variáveis do NGINX.

JSON Schema

Aqui, apresento uma biblioteca lua-resty: lua-rapidjson. É um wrapper em torno do rapidjson, a biblioteca JSON open-source da Tencent, conhecida por seu desempenho. Aqui, focamos na diferença entre ela e o cjson: suporte a JSON Schema.

JSON Schema é um padrão comum que nos permite descrever com precisão o formato dos parâmetros em uma interface e como eles devem ser validados. Aqui está um exemplo simples:

"stringArray": {
    "type": "array",
    "items": { "type": "string" },
    "minItems": 1,
    "uniqueItems": true
}

Este JSON descreve com precisão que o parâmetro stringArray é do tipo array de strings e que o array não pode estar vazio, nem os elementos do array podem ser duplicados.

lua-rapidjson nos permite usar JSON Schema no OpenResty, o que pode trazer grande conveniência para a validação de interfaces. Por exemplo, para a interface de limite de contagem descrita anteriormente, podemos usar o seguinte schema para descrever:

local schema = {
    type = "object",
    properties = {
        count = {type = "integer", minimum = 0},
        time_window = {type = "integer",  minimum = 0},
        key = {type = "string", enum = {"remote_addr", "server_addr"}},
        rejected_code = {type = "integer", minimum = 200, maximum = 600},
    },
    additionalProperties = false,
    required = {"count", "time_window", "key", "rejected_code"},
}

Você descobrirá que isso pode levar a dois benefícios muito óbvios:

  1. Para o front-end, o front-end pode reutilizar diretamente esta descrição de schema para o desenvolvimento de páginas front-end e validação de parâmetros sem precisar se preocupar com o back-end.
  2. Para o back-end, o back-end usa diretamente a função de validação de schema SchemaValidator do lua-rapidjson para determinar a legitimidade da interface, e não há necessidade de escrever código extra.

Comunicação entre Workers

Finalmente, gostaria de falar sobre a biblioteca lua-resty que permite a comunicação entre workers no OpenResty, onde não há um mecanismo de comunicação direta entre workers, o que causa muitos problemas. Vamos imaginar um cenário:

Um serviço OpenResty tem 24 processos worker, e quando o administrador atualiza uma configuração do sistema através das APIs REST HTTP, apenas um Worker recebe a atualização do administrador e escreve o resultado no banco de dados, atualizando o shared dict e o lru cache dentro de seu próprio Worker. Então, como os outros 23 workers podem ser notificados para atualizar esta configuração?

Um mecanismo de notificação entre múltiplos Workers é necessário para realizar a tarefa acima. No caso de o OpenResty não suportar isso, temos que salvar o dia com dados shared dict entre workers.

lua-resty-worker-events é uma implementação concreta dessa ideia. Ele mantém um número de versão em um shared dict, e quando uma nova mensagem é publicada, ele adiciona um ao número de versão e coloca o conteúdo da mensagem no dicionário com o número de versão como key.

event_id, err = _dict:incr(KEY_LAST_ID, 1)
success, err = _dict:add(KEY_DATA .. tostring(event_id), json)

Além disso, um loop de polling com um intervalo padrão de 1 segundo é criado em segundo plano usando ngx.timer para verificar constantemente as mudanças no número de versão:

local event_id, err = get_event_id()
if event_id == _last_event then
    return "done"
end

Dessa forma, assim que uma nova notificação de evento for encontrada para ser processada, o conteúdo da mensagem é recuperado do shared dict com base no número de versão:

while _last_event < event_id do
    count = count + 1
    _last_event = _last_event + 1
    data, err = _dict:get(KEY_DATA..tostring(_last_event))
end

No geral, embora lua-resty-worker-events tenha um atraso de um segundo, ele ainda implementa um mecanismo de notificação de eventos entre Workers.

No entanto, em alguns cenários em tempo real, como o envio de mensagens, a falta de comunicação direta entre processos Worker do OpenResty pode causar alguns problemas. Não há uma solução melhor para isso, mas se você tiver boas ideias, sinta-se à vontade para discutir no Github. Muitas funcionalidades do OpenResty são impulsionadas pela comunidade para construir um ciclo ecológico virtuoso.

Resumo

As três bibliotecas que apresentamos hoje são únicas e trazem mais possibilidades para aplicações OpenResty. Finalmente, um tópico interativo: você encontrou alguma biblioteca interessante em torno do OpenResty? Ou o que você descobriu ou se perguntou sobre as bibliotecas mencionadas hoje? Sinta-se à vontade para enviar este artigo para os usuários do OpenResty ao seu redor para trocar e progredir juntos.