OpenResty FAQ | Permissão de Processo Privilegiado, Fase de Execução e mais

API7.ai

November 11, 2022

OpenResty (NGINX + Lua)

Este artigo contém seis perguntas frequentes:

1. Permissões de Processo Privilegiado

P: O que é um processo privilegiado? Como um usuário não privilegiado pode obter permissões de root? Você pode apresentar alguns cenários de uso do processo privilegiado?

R: As permissões do processo privilegiado são as mesmas do processo master. Se você iniciar o OpenResty como um usuário não privilegiado, o processo master herdará as permissões do usuário, o que significa que o "processo privilegiado" não terá direitos agora.

É fácil entender que não há privilégios de root quando um usuário normal inicia um processo.

Quanto aos cenários de uso do processo privilegiado, geralmente o utilizamos para tarefas que exigem altos privilégios, como limpar logs e reiniciar o OpenResty. No entanto, tenha cuidado para não usar o processo privilegiado para executar tarefas do processo worker devido a riscos de segurança.

Um desenvolvedor executa todas as tarefas de timer no processo privilegiado. Por que ele faz isso? Porque há apenas um processo privilegiado, e dessa forma, o timer não é iniciado repetidamente.

O desenvolvedor é "inteligente" porque alcançou o objetivo sem usar worker.id. No entanto, não se esqueça, é muito perigoso se a tarefa de timer depender da entrada do cliente.

2. Fases e Depuração

P: Após executar ngx.say('hello'), o OpenResty responderá ao cliente diretamente após executar a lógica restante na fase atual? Isso significa que ele não continuará a executar as fases posteriores.

R: Não. Podemos observar sua fase de execução:

image

Você pode testar ngx.say na fase content primeiro, depois usar ngx.log na fase log ou body filter para imprimir o log.

Em artigos anteriores, não mencionei especificamente o problema de depuração de código no OpenResty, sobre o qual os desenvolvedores podem se sentir confusos.

Não há recursos avançados para depuração de código no OpenResty, como pontos de interrupção (existem alguns plugins pagos, mas não os usei), e você só pode usar ngx.say e ngx.log para ver a saída. É assim que todos os desenvolvedores que conheço fazem sua depuração, incluindo os autores e contribuidores do OpenResty. Portanto, você precisa de casos de teste robustos e logs de depuração como garantia.

3. A Prática de ngx.exit

P: Em um dos artigos anteriores, havia uma descrição: O código de status HTTP do OpenResty tem uma constante especial ngx.OK. Após executar ngx.exit(ngx.OK), a solicitação sai da fase atual e passa para a próxima fase, em vez de retornar diretamente ao cliente.

Lembro que ngx.OK não deve ser considerado um código de status HTTP, seu valor é 0. Minha compreensão é:

  • Após executar ngx.exit(ngx.OK), ngx.exit(ngx.ERROR) ou ngx.exit(ngx.DECLINED), a solicitação sai da fase atual e passa para a próxima fase.
  • Quando ngx.exit(ngx.HTTP_*) recebe os vários códigos de status HTTP de ngx.HTTP_* como parâmetro, ele responderá diretamente ao cliente.

Não sei se minha compreensão está correta.

R: Em relação à sua primeira pergunta, ngx.ok não é um código de status HTTP, mas uma constante no OpenResty com valor 0.

Quanto à segunda pergunta, a documentação oficial de ngx.exit pode ser a resposta exata:

  1. Quando status >= 200 (ou seja, ngx.HTTP_OK e acima), ele interromperá a execução da solicitação atual e retornará o código de status para o nginx.

  2. Quando status == 0 (ou seja, ngx.OK), ele apenas sairá do manipulador de fase atual (ou do manipulador de conteúdo se a diretiva content_by_lua* for usada) e continuará a executar as fases posteriores (se houver) para a solicitação atual.

No entanto, a documentação não menciona como o OpenResty lida com ngx.exit(ngx.ERROR) e ngx.exit(ngx.DECLINED). Podemos fazer um teste da seguinte forma:

location /lua {
    rewrite_by_lua "ngx.exit(ngx.ERROR)";
    echo hello;
}

Ao acessar este location, você pode ver que o código de resposta HTTP está vazio, o corpo da resposta também está vazio, e ele não passa para a próxima fase de execução.

À medida que você se aprofunda no processo de aprendizado do OpenResty, em algum momento você descobrirá que nem a documentação nem os casos de teste podem responder às suas perguntas. Nesse momento, você precisa construir seus próprios casos de teste para verificar suas ideias. Você pode fazer isso manualmente ou adicionar os testes ao conjunto de casos de teste construído por test::nginx.

4. Variáveis e Condição de Corrida

P: Como mencionado anteriormente, o escopo da variável ngx.var está entre os módulos nginx C e lua-nginx-module.

  1. Não entendo muito bem isso. Do ponto de vista da solicitação, isso significa uma única solicitação em um processo worker?

  2. Minha compreensão é que quando manipulamos variáveis dentro de um módulo. Se houver uma operação de bloqueio entre duas operações, pode existir uma condição de corrida. Então, se não houver uma operação de bloqueio entre duas operações, e acontecer que o processo atual entre na fila de prontos quando o tempo de CPU acabar, é possível que exista uma condição de corrida?

R: Vamos analisar essas perguntas.

Primeiro, em relação à variável ngx.var, sua compreensão está correta. O ciclo de vida de ngx.var é o mesmo da solicitação, e ele desaparece quando a solicitação termina. Mas sua vantagem é que os dados podem ser passados em módulos C e código Lua, o que não é possível de várias outras maneiras.

Segundo, desde que haja uma operação de yield entre duas operações, pode haver uma condição de corrida, e não uma operação de bloqueio. Não há condição de corrida quando há uma operação de bloqueio. Em outras palavras, desde que você não entregue a iniciativa ao loop de eventos do NGINX, não haverá condição de corrida.

5. O shared dict Não Precisa de Bloqueio

P: Se vários workers armazenam dados simultaneamente, é necessário adicionar bloqueios?

Por exemplo:

resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
local lock= ngx.xxxx.lock
lock.lock()
 dogs:set("Jim", 8)
lock.unlock()
 local v = dogs:get("Jim")
 ngx.say(v)
 '

R: Você não precisa adicionar um bloqueio aqui porque, independentemente da operação get ou set, a operação do shared dict é atômica. O OpenResty já considerou esse tipo de processamento semelhante a bloqueio.

6. Operação de Tempo no OpenResty

P: Usando ngx.now() para obter o tempo, isso acontece na fase de restauração da função resume?

R: O NGINX é projetado com desempenho em primeiro lugar e armazena em cache o tempo. Podemos verificar isso pelo código-fonte de ngx.now:

static int
ngx_http_lua_ngx_now(lua_State *L)
{
    ngx_time_t              *tp;

    tp = ngx_timeofday();

    lua_pushnumber(L, (lua_Number) (tp->sec + tp->msec / 1000.0L));

    return 1;
}

Como você pode ver, por trás da função ngx.now() que obtém o tempo atual está a função ngx_timeofday do NGINX. A função ngx_timeofday é uma definição de macro:

#define ngx_timeofday()      (ngx_time_t *) ngx_cached_time

Aqui, o valor de ngx_cached_time será atualizado apenas na função ngx_time_update.

Então, a pergunta se transforma em "quando a função ngx_time_update é chamada?" Se você rastrear no código-fonte do NGINX, verá que as chamadas para ngx_time_update ocorrem no loop de eventos, então esse problema é resolvido.

Resumo

Você também deve ser capaz de descobrir através dessas perguntas, o benefício dos projetos de código aberto é que você pode seguir as pistas e procurar respostas no código-fonte, o que lhe dará a sensação de resolver o caso.

Finalmente, espero que através da comunicação e Q&A, eu possa ajudá-lo a transformar o que você aprende no que você obtém. Você também é bem-vindo a encaminhar este artigo, e vamos nos comunicar e melhorar juntos.