Funcionalidade Matadora do OpenResty: Dinâmica
API7.ai
January 12, 2023
Até agora, estamos quase terminando o conteúdo relacionado ao desempenho do OpenResty. Dominar e aplicar de forma flexível essas técnicas de otimização pode melhorar muito o desempenho do nosso código. Hoje, na última parte da otimização de desempenho, vamos aprender sobre uma capacidade frequentemente subestimada no OpenResty: "dinâmico".
Vamos começar vendo o que é dinâmico e como ele se relaciona com o desempenho. Dinâmico, neste contexto, significa que os programas podem modificar parâmetros, configurações e até mesmo seu código em tempo de execução, sem precisar recarregar. Especificamente, no NGINX e no OpenResty, você pode alterar upstreams, certificados SSL e limites de taxa sem reiniciar o serviço, alcançando a dinamicidade. Quanto à relação entre dinâmico e desempenho, é claro que, se esses tipos de operações não puderem ser feitos dinamicamente, então recargas frequentes dos serviços do NGINX naturalmente resultarão em perda de desempenho.
No entanto, sabemos que a versão de código aberto do NGINX não suporta recursos dinâmicos, então você precisa alterar os certificados SSL do upstream modificando o arquivo de configuração e reiniciando o serviço para que eles entrem em vigor. O NGINX Plus (versão comercial do NGINX) fornece algumas capacidades dinâmicas, e você pode usar a API REST para atualizar, mas isso é uma melhoria menos radical, na melhor das hipóteses.
No OpenResty, essas amarras não existem, e a dinamicidade é o recurso matador do OpenResty. Você pode se perguntar por que o OpenResty, baseado no NGINX, pode suportar dinamicidade. A razão é simples: a lógica do NGINX é feita através de módulos em C, enquanto o OpenResty é feito através de Lua, uma linguagem de script. Uma das vantagens das linguagens de script é que elas podem ser alteradas dinamicamente em tempo de execução.
Carregar código dinamicamente
Aqui está como carregar dinamicamente código Lua no OpenResty.
resty -e 'local s = [[ngx.say("hello world")]]
local func, err = loadstring(s)
func()'
Podemos ver que, em apenas algumas linhas de código, podemos transformar uma string em uma função Lua e fazê-la funcionar. Vamos dar uma olhada mais de perto nessas linhas de código:
- Primeiro, declaramos uma string cujo conteúdo é um trecho de código Lua que imprime
hello world
; - Em seguida, usando a função
loadstring
em Lua, transformamos o objeto string no objeto funçãofunc
. - Finalmente, adicionamos parênteses ao nome da função para executar o
func
e imprimirhello world
.
Claro, também podemos estender funções mais interessantes e práticas com base nesse código. A seguir, vou te levar para experimentar.
Função 1: FaaS
Primeiro, temos o FaaS (Function-as-a-Service), que recentemente tem sido uma direção tecnológica muito popular. Vamos ver como implementá-lo no OpenResty. No código mencionado anteriormente, a string é um código Lua. Podemos também mudá-la para uma função Lua:
local s = [[
return function()
ngx.say("hello world")
end
]]
Como dissemos, as funções são cidadãos de primeira classe em Lua, e este código retorna uma função anônima. Ao executar essa função anônima, usamos pcall
para fornecer uma camada de proteção. pcall
executará a função em modo protegido e capturará a exceção. Se for normal, retornará true
e o resultado da execução. Se falhar, retornará false
e informações de erro, que é o seguinte código:
local func1, err = loadstring(s)
local ret, func = pcall(func1)
Naturalmente, se você combinar as duas partes acima, obterá um exemplo completo e operacional:
resty -e 'local s = [[
return function()
ngx.say("hello world")
end
]]
local func1 = loadstring(s)
local ret, func = pcall(func1)
func()'
Para ir um passo adiante, podemos mudar a string s
contendo funções para uma forma que os usuários possam especificar e adicionar as condições para sua execução. Este é o protótipo do FaaS. Aqui, forneço uma implementação completa. Se você estiver interessado em FaaS e quiser continuar sua pesquisa, siga o link para aprender mais.
Função 2: Computação de Borda
A dinamicidade do OpenResty pode ser usada para FaaS, tornando a dinamicidade da linguagem de script refinada ao nível da função, e desempenhando um papel dinâmico na computação de borda.
Por causa dessas vantagens, podemos estender os tentáculos do OpenResty dos campos de gateway de API, WAF (Web Application Firewall), servidor web e outros servidores para os nós de borda mais próximos dos usuários, como dispositivos IoT, nós de borda CDN, roteadores e assim por diante.
Isso não é apenas uma fantasia. O OpenResty tem sido amplamente utilizado nos campos mencionados acima. Tomando os nós de borda CDN como exemplo, o Cloudflare, o maior usuário do OpenResty, há muito tempo realiza o controle dinâmico dos nós de borda CDN com a ajuda das características dinâmicas do OpenResty.
A abordagem do Cloudflare é semelhante ao princípio de carregar código dinamicamente mencionado acima, que pode ser dividido aproximadamente nos seguintes passos:
- Primeiro, obter os arquivos de código alterados do cluster de banco de dados de valor-chave. O método pode ser a sondagem de temporizador em segundo plano ou o modo "publicar-assinar" para monitorar;
- Em seguida, substituir o arquivo antigo no disco local pelo arquivo de código atualizado e atualizar o cache carregado na memória usando os métodos
loadstring
epcall
;
Dessa forma, a próxima solicitação do cliente a ser processada passará pela lógica do código atualizado. Claro, a aplicação prática deve considerar mais detalhes do que os passos acima, como controle de versão e reversão, tratamento de exceções, interrupção de rede, reinicialização do nó de borda, etc., mas o processo geral permanece inalterado.
Se movermos a abordagem do Cloudflare dos nós de borda CDN para outros cenários de borda, podemos atribuir dinamicamente muito poder de computação aos dispositivos de nó de borda. Isso não apenas pode aproveitar ao máximo o poder de computação dos nós de borda, mas também permitir que os usuários obtenham respostas mais rápidas às solicitações, porque o nó de borda processará os dados originais e depois os resumirá para o servidor remoto, o que reduz muito a quantidade de transmissão de dados.
No entanto, para fazer um excelente trabalho em FaaS e computação de borda, a dinamicidade do OpenResty é apenas uma boa base. Você também precisa considerar a melhoria do ecossistema ao seu redor e a participação dos fabricantes, o que não é apenas uma categoria técnica.
Upstream Dinâmico
Agora, vamos trazer nossos pensamentos de volta ao OpenResty para ver como alcançar o upstream dinâmico. O lua-resty-core
fornece uma biblioteca de ngx.balancer
para configurar o upstream. Ela precisa ser colocada no estágio balancer
do OpenResty para ser executada:
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local host = "127.0.0.2"
local port = 8080
local ok, err = balancer.set_current_peer(host, port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(500)
end
}
A função set_current_peer
configura o endereço IP e a porta do upstream. No entanto, gostaríamos de salientar que o nome de domínio não é suportado aqui. Precisamos usar a biblioteca lua-resty-dns
para fazer uma camada de análise para o nome de domínio e IP.
No entanto, o ngx.balancer
é relativamente baixo. Embora possa ser usado para configurar o upstream, a realização do upstream dinâmico está longe de ser simples. Portanto, duas funções são necessárias na frente do ngx.balancer
:
- Primeiro, decidir se o algoritmo de seleção de upstream é
hash consistente
ouroundrobin
; - Segundo, o mecanismo de verificação de saúde do upstream, que precisa eliminar o upstream não saudável e reincorporá-lo quando o upstream não saudável se tornar saudável.
A biblioteca oficial do OpenResty lua-resty-balancer
contém dois tipos de algoritmos: resty.chash
e resty.roundrobin
para completar a primeira função, e tem lua-resty-upstream-healthcheck
para tentar completar a segunda função.
No entanto, ainda há dois problemas.
O primeiro ponto é a falta de implementação completa da última milha. Transformar ngx.balancer
, lua-resty-balancer
e lua-resty-upstream-healthcheck
para combinar as funções de upstream dinâmico, mas ainda precisa de algum trabalho, o que impede a maioria dos desenvolvedores.
Segundo, a implementação do lua-resty-upstream-healthcheck
não está completa. Há apenas verificação de saúde passiva, mas não verificação de saúde ativa.
Aqui, as solicitações dos clientes acionam a verificação de saúde passiva e, em seguida, analisam o valor de retorno do upstream como uma condição para determinar se a saúde está boa. Se não houver solicitação do cliente, não se sabe se o upstream está saudável. As verificações de saúde ativas podem remediar essa deficiência. Elas usam ngx.timer
para sondar periodicamente a interface upstream especificada para detectar o status de saúde.
Portanto, na prática real, geralmente recomendamos usar o lua-resty-healthcheck
para completar as verificações de saúde do upstream. Sua vantagem é que inclui verificações de saúde ativas e passivas, e foi verificada em vários projetos com maior confiabilidade.
Além disso, o emergente gateway de API de microsserviços Apache APISIX fez uma implementação completa do upstream dinâmico com base no lua-resty-upstream-healthcheck
. Podemos consultar sua implementação. Há apenas 400 linhas de código no total. Você pode facilmente destacá-lo e colocá-lo em seu projeto para uso.
Resumo
Com relação à dinamicidade do OpenResty, em quais áreas e cenários você pode aproveitá-la? Você também pode expandir os conteúdos de cada parte introduzida neste capítulo para uma análise mais detalhada e aprofundada.
Você é bem-vindo a compartilhar este artigo e aprender e progredir com mais pessoas.