---
title: "I/O não bloqueante - A chave para melhorar o desempenho do OpenResty - API7.ai"
description: "Evite usar operações de I/O bloqueantes no OpenResty, pois elas afetam o desempenho. Algumas operações frequentemente mal utilizadas incluem: execução de comandos externos, manipulação de I/O em disco e o uso de lusockets."
url: "https://api7.ai/pt/learning-center/openresty/blocking-io"
---

# I/O não bloqueante - A chave para melhorar o desempenho do OpenResty

API7.ai

December 2, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

No capítulo de Otimização de Desempenho, vou guiá-lo por todos os aspectos da otimização de desempenho no OpenResty e resumir os pontos mencionados nos capítulos anteriores em um guia abrangente de codificação OpenResty, para que você possa escrever código OpenResty de melhor qualidade.

Melhorar o desempenho não é fácil. Devemos considerar a otimização da arquitetura do sistema, otimização do banco de dados, otimização de código, testes de desempenho, análise de gráficos de chamas e outras etapas. Mas é fácil reduzir o desempenho, e como o título do artigo de hoje sugere, você pode reduzir o desempenho em 10 vezes ou mais apenas adicionando algumas linhas de código. Se você está usando o OpenResty para escrever seu código, mas o desempenho não melhorou, provavelmente é devido a I/O bloqueante.

Portanto, antes de entrarmos nos detalhes da otimização de desempenho, vamos olhar para um princípio importante na programação OpenResty: **I/O não bloqueante primeiro.**

Desde crianças, nossos pais e professores nos ensinaram a não brincar com fogo e não tocar em tomadas, que são comportamentos perigosos. O mesmo tipo de comportamento perigoso existe no OpenResty. Se você tiver que usar operações de I/O bloqueante em seu código, isso causará uma queda drástica no desempenho, e o propósito original de usar o OpenResty para construir um servidor de alto desempenho será derrotado.

## Por que não podemos usar operações de I/O bloqueante?

Entender quais comportamentos são perigosos e evitá-los é o primeiro passo na otimização de desempenho. Vamos começar revisando por que operações de I/O bloqueante podem afetar o desempenho do OpenResty.

O OpenResty pode manter um alto desempenho simplesmente porque empresta o tratamento de eventos do NGINX e as corrotinas do Lua, então:

- Quando você encontra uma operação como I/O de rede que exige que você espere por um retorno antes de continuar, você chama a corrotina Lua `yield` para se suspender e então registra um callback no NGINX.
- Após a operação de I/O ser concluída (ou ocorrer um timeout ou erro), o NGINX chama `resume` para acordar a corrotina Lua.

Esse processo garante que o OpenResty possa sempre usar os recursos da CPU de forma eficiente para processar todas as solicitações.

Nesse fluxo de processamento, o LuaJIT não entrega o controle para o loop de eventos do NGINX se não usar um método de I/O não bloqueante como `cosocket`, mas em vez disso usa uma função de I/O bloqueante para lidar com I/O. Isso resulta em outras solicitações esperando na fila para que o evento de I/O bloqueante termine de ser processado antes de receberem uma resposta.

Resumindo, na programação OpenResty, devemos ter especial cuidado com chamadas de função que podem bloquear I/O; caso contrário, uma única linha de código de I/O bloqueante pode derrubar o desempenho de todo o serviço.

Abaixo, vou apresentar alguns problemas comuns, algumas funções de I/O bloqueante frequentemente mal utilizadas; vamos também experimentar como usar a maneira mais fácil de "estragar" e rapidamente fazer o desempenho do seu serviço cair 10 vezes.

## Executar comandos externos

Em muitos cenários, os desenvolvedores não usam o OpenResty apenas como um servidor web, mas o dotam de mais lógica de negócios. Nesse caso, pode ser necessário chamar comandos e ferramentas externas para ajudar a completar algumas operações.

Por exemplo, para matar um processo.

```
os.execute("kill -HUP " .. pid)
```

Ou para operações mais demoradas, como copiar arquivos, usar OpenSSL para gerar chaves, etc.

```
os.execute(" cp test.exe /tmp ")

os.execute(" openssl genrsa -des3 -out private.pem 2048 ")
```

Na superfície, `os.execute` é uma função embutida no Lua, e no mundo Lua, é de fato a maneira de chamar comandos externos. No entanto, é importante lembrar que Lua é uma linguagem de programação embutida e terá usos recomendados diferentes em outros contextos.

No ambiente do OpenResty, `os.execute` bloqueia a solicitação atual. Então, se o tempo de execução desse comando for particularmente curto, o impacto não será muito grande. Mas se o comando levar centenas de milissegundos ou até segundos para ser executado, haverá uma queda drástica no desempenho.

Entendemos o problema, então como devemos resolvê-lo? Geralmente, há duas soluções.

### 1. Se houver uma biblioteca `FFI` disponível, então damos preferência à maneira FFI para chamá-la

Por exemplo, se usamos a linha de comando OpenSSL para gerar a chave acima, podemos mudar para usar `FFI` para chamar a função C do OpenSSL para contornar isso.

Para matar um processo, você pode usar `lua-resty-signal`, uma biblioteca que vem com o OpenResty, para resolvê-lo de forma não bloqueante. A implementação do código é a seguinte. Claro, aqui, `lua-resty-signal` também é resolvido usando `FFI` para chamar funções do sistema.

```
local resty_signal = require "resty.signal"
local pid = 12345
local ok, err = resty_signal.kill(pid, "KILL")
```

Além disso, o site oficial do LuaJIT tem uma [página específica](https://luajit.org/) que introduz várias bibliotecas de ligação FFI em diferentes categorias. Por exemplo, ao lidar com imagens, criptografia e descriptografia de operações intensivas em CPU, você pode ir lá primeiro para ver se há bibliotecas que foram encapsuladas e podem ser usadas diretamente.

### 2. Use a biblioteca `lua-resty-shell` baseada em `ngx.pipe`

Como descrito anteriormente, você pode executar seus comandos em `shell.run`, uma operação de I/O não bloqueante.

```
$ resty -e 'local shell = require "resty.shell"
local ok, stdout, stderr, reason, status =
    shell.run([[echo "hello, world"]])
    ngx.say(stdout) '
```

## I/O de disco

Vamos olhar para o cenário de lidar com I/O de disco. Em uma aplicação do lado do servidor, é uma operação comum ler um arquivo de configuração local, como o seguinte código.

```
local path = "/conf/apisix.conf"
local file = io.open(path, "rb")
local content = file:read("*a")
file:close()
```

Esse código usa `io.open` para obter o conteúdo de um determinado arquivo. No entanto, embora seja uma operação de I/O bloqueante, não se esqueça de que as coisas devem ser consideradas em um cenário real. Então, se você chamá-lo em `init` e `init worker`, é uma ação única que não afeta nenhuma solicitação do cliente e é perfeitamente aceitável.

Claro, torna-se inaceitável se cada solicitação do usuário disparar uma leitura ou escrita no disco. Nesse ponto, você precisa considerar seriamente a solução.

Primeiramente, podemos usar o `lua-io-nginx-module`, um módulo C de terceiros. Ele fornece uma API Lua de I/O não bloqueante para o OpenResty, mas você não pode usá-lo como gosta com `cosocket`. Porque o consumo de I/O de disco não desaparece sem motivo, é apenas uma maneira diferente de fazer as coisas.

Essa abordagem funciona porque o `lua-io-nginx-module` aproveita o pool de threads do NGINX para mover operações de I/O de disco da thread principal para outra thread para processá-las, de modo que a thread principal não seja bloqueada por operações de I/O de disco.

Você precisa recompilar o NGINX ao usar essa biblioteca, pois é um módulo C. Ele é usado da mesma maneira que a biblioteca I/O do Lua.

```
local ngx_io = require "ngx.io"
local path = "/conf/apisix.conf"
local file, err = ngx_io.open(path, "rb")
local data, err = file: read("*a")
file:close()
```

Em segundo lugar, tente um ajuste arquitetônico. Podemos mudar nossa abordagem para esse tipo de I/O de disco e parar de ler e escrever em discos locais?

Deixe-me dar um exemplo para que você possa aprender por analogia. Anos atrás, eu estava trabalhando em um projeto que exigia registro em disco local para fins estatísticos e de solução de problemas.

Na época, os desenvolvedores usavam `ngx.log` para escrever esses logs, como o seguinte.

```
ngx.log(ngx.WARN, "info")
```

Essa linha de código chama a API Lua fornecida pelo OpenResty, e parece que não há problemas. A desvantagem, no entanto, é que você não pode chamá-la com muita frequência. Primeiro, `ngx.log` em si é uma chamada de função custosa; segundo, mesmo com um buffer, grandes e frequentes escritas em disco podem impactar seriamente o desempenho.

Então, como resolvemos isso? Vamos voltar à necessidade original - estatísticas, solução de problemas, e escrever logs no disco local teria sido apenas um dos meios para atingir o objetivo.

Então você também pode enviar logs para um servidor de logs remoto para usar `cosocket` para fazer comunicação de rede não bloqueante; ou seja, jogar o I/O de disco bloqueante para o serviço de logs para evitar bloquear o serviço externo. Você pode usar `lua-resty-logger-socket` para fazer isso.

```
local logger = require "resty.logger.socket"
if not logger.initted() then
    local ok, err = logger.init{
        host = 'xxx',
        port = 1234,
        flush_limit = 1234,
        drop_limit = 5678,
    }
local msg = "foo"
local bytes, err = logger.log(msg)
```

Como você deve ter notado, ambos os métodos acima são os mesmos: se o I/O bloqueante for inevitável, não bloqueie a thread principal do worker; jogue-o para outras threads ou serviços externos.

## `luasocket`

Finalmente, vamos falar sobre `luasocket`, uma biblioteca embutida do Lua facilmente usada por desenvolvedores e frequentemente confundida entre `luasocket` e o `cosocket` fornecido pelo OpenResty. `luasocket` também pode realizar funções de comunicação de rede. No entanto, ele não tem a vantagem de não bloquear. Como resultado, se você usar `luasocket`, o desempenho cai drasticamente.

No entanto, luasocket também tem seus cenários de uso únicos. Por exemplo, eu não sei se você se lembra que `cosocket` não está disponível em várias fases, e geralmente podemos contornar isso usando `ngx.timer`. Além disso, você pode usar `luasocket` para funções de `cosocket` em fases únicas como `init_by_lua*` e `init_worker_by_lua*`. Quanto mais familiarizado você estiver com as semelhanças e diferenças entre OpenResty e Lua, mais soluções interessantes como essas você encontrará.

Além disso, `lua-resty-socket` é um wrapper secundário para uma biblioteca de código aberto que torna luasocket e cosocket\` compatíveis. Esse conteúdo também merece estudo adicional. Se você ainda estiver interessado, preparei [materiais](https://github.com/thibaultcha/lua-resty-socket/) para você continuar aprendendo.

## Resumo

Em geral, no OpenResty, reconhecer os tipos de operações de I/O bloqueante e suas soluções é a base de uma boa otimização de desempenho. Então, você já encontrou operações de I/O bloqueante semelhantes no desenvolvimento real? Como você as encontra e resolve? Sinta-se à vontade para compartilhar sua experiência comigo nos comentários, e sinta-se à vontade para compartilhar este artigo.

Share article link

Share

Related Posts

[![Vantagens e Desvantagens de \`string\` no OpenResty](https://static.api7.ai/2022/12/09/6392eeb028c7f.png)](https://api7.ai/learning-center/openresty/advantages-and-disadvantages-of-string)

## [Vantagens e Desvantagens de \`string\` no OpenResty](https://api7.ai/learning-center/openresty/advantages-and-disadvantages-of-string)

[![FAQ do OpenResty | Como o OpenResty é usado na prática](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [FAQ do OpenResty | Como o OpenResty é usado na prática](https://api7.ai/learning-center/openresty/faq-5)

Table of Contents

- [Por que não podemos usar operações de I/O bloqueante?](#por-que-n%C3%A3o-podemos-usar-opera%C3%A7%C3%B5es-de-io-bloqueante)

- [Executar comandos externos](#executar-comandos-externos)

  - [1. Se houver uma biblioteca `FFI` disponível, então damos preferência à maneira FFI para chamá-la](#1-se-houver-uma-biblioteca-ffi-dispon%C3%ADvel-ent%C3%A3o-damos-prefer%C3%AAncia-%C3%A0-maneira-ffi-para-cham%C3%A1-la)
  - [2. Use a biblioteca `lua-resty-shell` baseada em `ngx.pipe`](#2-use-a-biblioteca-lua-resty-shell-baseada-em-ngxpipe)

- [I/O de disco](#io-de-disco)

- [`luasocket`](#luasocket)

- [Resumo](#resumo)
