redis: driver de cliente Lua redis para nginx-module-lua baseado na API cosocket
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Em seguida, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-redis
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-redis
Para usar esta biblioteca Lua com o NGINX, certifique-se de que o nginx-module-lua está instalado.
Este documento descreve lua-resty-redis v0.33 lançado em 09 de julho de 2025.
Esta biblioteca Lua é um driver de cliente Redis para o módulo ngx_lua do nginx:
https://github.com/openresty/lua-nginx-module/#readme
Esta biblioteca Lua aproveita a API cosocket do ngx_lua, que garante um comportamento 100% não bloqueante.
Observe que é necessário ter pelo menos ngx_lua 0.5.14 ou OpenResty 1.2.1.14.
Sinopse
# você não precisa da linha a seguir se estiver usando
# o pacote OpenResty:
server {
location /test {
# é necessário especificar o resolvedor para resolver o nome do host
resolver 8.8.8.8;
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 seg
-- ou conectar a um arquivo de socket de domínio unix escutado
-- por um servidor redis:
-- local ok, err = red:connect("unix:/path/to/redis.sock")
-- conectar via endereço IP diretamente
local ok, err = red:connect("127.0.0.1", 6379)
-- ou conectar via nome de host, precisa especificar o resolvedor como acima
local ok, err = red:connect("redis.openresty.com", 6379)
if not ok then
ngx.say("falha ao conectar: ", err)
return
end
ok, err = red:set("dog", "um animal")
if not ok then
ngx.say("falha ao definir dog: ", err)
return
end
ngx.say("resultado da definição: ", ok)
local res, err = red:get("dog")
if not res then
ngx.say("falha ao obter dog: ", err)
return
end
if res == ngx.null then
ngx.say("dog não encontrado.")
return
end
ngx.say("dog: ", res)
red:init_pipeline()
red:set("cat", "Marry")
red:set("horse", "Bob")
red:get("cat")
red:get("horse")
local results, err = red:commit_pipeline()
if not results then
ngx.say("falha ao confirmar as requisições em pipeline: ", err)
return
end
for i, res in ipairs(results) do
if type(res) == "table" then
if res[1] == false then
ngx.say("falha ao executar comando ", i, ": ", res[2])
else
-- processar o valor da tabela
end
else
-- processar o valor escalar
end
end
-- colocá-lo no pool de conexões de tamanho 100,
-- com 10 segundos de tempo ocioso máximo
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.say("falha ao definir keepalive: ", err)
return
end
-- ou apenas fechar a conexão imediatamente:
-- local ok, err = red:close()
-- if not ok then
-- ngx.say("falha ao fechar: ", err)
-- return
-- end
}
}
}
Métodos
Todos os comandos Redis têm seus próprios métodos com o mesmo nome, exceto todos em letras minúsculas.
Você pode encontrar a lista completa de comandos Redis aqui:
Você precisa consultar esta referência de comandos Redis para ver quais argumentos cada comando aceita.
Os argumentos do comando Redis podem ser diretamente passados para a chamada do método correspondente. Por exemplo, o comando redis "GET" aceita um único argumento de chave, então você pode apenas chamar o método "get" assim:
local res, err = red:get("key")
Da mesma forma, o comando redis "LRANGE" aceita três argumentos, então você deve chamar o método "lrange" assim:
local res, err = red:lrange("nokey", 0, 1)
Por exemplo, os comandos "SET", "GET", "LRANGE" e "BLPOP" correspondem aos métodos "set", "get", "lrange" e "blpop".
Aqui estão mais alguns exemplos:
-- HMGET myhash field1 field2 nofield
local res, err = red:hmget("myhash", "field1", "field2", "nofield")
-- HMSET myhash field1 "Hello" field2 "World"
local res, err = red:hmset("myhash", "field1", "Hello", "field2", "World")
Todos esses métodos de comando retornam um único resultado em caso de sucesso e nil caso contrário. Em caso de erros ou falhas, também retornará um segundo valor que é uma string descrevendo o erro.
Uma "resposta de status" do Redis resulta em um valor de retorno do tipo string com o prefixo "+" removido.
Uma "resposta inteira" do Redis resulta em um valor de retorno do tipo número Lua.
Uma "resposta de erro" do Redis resulta em um valor false e uma string descrevendo o erro.
Uma resposta "bulk" não nula do Redis resulta em uma string Lua como valor de retorno. Uma resposta bulk nula resulta em um valor de retorno ngx.null.
Uma resposta "multi-bulk" não nula do Redis resulta em uma tabela Lua contendo todos os valores que a compõem (se houver). Se algum dos valores que a compõem for um valor de erro Redis válido, então será uma tabela de dois elementos {false, err}.
Uma resposta multi-bulk nula retorna um valor ngx.null.
Veja http://redis.io/topics/protocol para detalhes sobre os vários tipos de resposta do Redis.
Além de todos esses métodos de comando Redis, os seguintes métodos também são fornecidos:
new
syntax: red, err = redis:new()
Cria um objeto redis. Em caso de falhas, retorna nil e uma string descrevendo o erro.
connect
syntax: ok, err = red:connect(host, port, options_table?)
syntax: ok, err = red:connect("unix:/path/to/unix.sock", options_table?)
Tenta conectar ao host remoto e à porta que o servidor redis está escutando ou a um arquivo de socket de domínio unix local escutado pelo servidor redis.
Antes de realmente resolver o nome do host e conectar ao backend remoto, este método sempre procurará no pool de conexões por conexões ociosas correspondentes criadas por chamadas anteriores deste método.
O argumento opcional options_table é uma tabela Lua contendo as seguintes chaves:
-
sslSe definido como verdadeiro, usa SSL para conectar ao redis (padrão é falso).
-
ssl_verifySe definido como verdadeiro, verifica a validade do certificado SSL do servidor (padrão é falso). Observe que você precisa configurar o lua_ssl_trusted_certificate para especificar o CA (ou certificado do servidor) usado pelo seu servidor redis. Você também pode precisar configurar a profundidade de verificação do lua_ssl_verify de acordo.
-
server_nameEspecifica o nome do servidor para a nova extensão TLS Server Name Indication (SNI) ao conectar via SSL.
-
poolEspecifica um nome personalizado para o pool de conexões em uso. Se omitido, o nome do pool de conexões será gerado a partir do template de string
<host>:<port>ou<unix-socket-path>. -
pool_sizeEspecifica o tamanho do pool de conexões. Se omitido e nenhuma opção
backlogfoi fornecida, nenhum pool será criado. Se omitido, masbacklogfoi fornecido, o pool será criado com um tamanho padrão igual ao valor da diretiva lua_socket_pool_size. O pool de conexões mantém atépool_sizeconexões ativas prontas para serem reutilizadas por chamadas subsequentes a connect, mas observe que não há limite superior para o número total de conexões abertas fora do pool. Se você precisar restringir o número total de conexões abertas, especifique a opçãobacklog. Quando o pool de conexões exceder seu limite de tamanho, a conexão menos recentemente usada (mantida ativa) já no pool será fechada para abrir espaço para a conexão atual. Observe que o pool de conexões cosocket é por processo de trabalho do Nginx, em vez de por instância do servidor Nginx, portanto, o limite de tamanho especificado aqui também se aplica a cada processo de trabalho do Nginx. Além disso, observe que o tamanho do pool de conexões não pode ser alterado uma vez criado. Observe que é necessário ter pelo menos ngx_lua 0.10.14 para usar essas opções. -
backlogSe especificado, este módulo limitará o número total de conexões abertas para este pool. Não mais conexões do que
pool_sizepodem ser abertas para este pool a qualquer momento. Se o pool de conexões estiver cheio, as operações de conexão subsequentes serão enfileiradas em uma fila igual ao valor desta opção (a fila "backlog"). Se o número de operações de conexão enfileiradas for igual abacklog, as operações de conexão subsequentes falharão e retornarão nil mais a string de erro"too many waiting connect operations". As operações de conexão enfileiradas serão retomadas uma vez que o número de conexões no pool seja menor quepool_size. A operação de conexão enfileirada abortará uma vez que tenha sido enfileirada por mais deconnect_timeout, controlado por set_timeout, e retornará nil mais a string de erro "timeout". Observe que é necessário ter pelo menos ngx_lua 0.10.14 para usar essas opções.
set_timeout
syntax: red:set_timeout(time)
Define o tempo limite (em ms) de proteção para operações subsequentes, incluindo o método connect.
Desde a versão v0.28 deste módulo, é aconselhável usar set_timeouts em vez deste método.
set_timeouts
syntax: red:set_timeouts(connect_timeout, send_timeout, read_timeout)
Define, respectivamente, os limites de tempo limite de conexão, envio e leitura (em ms) para operações de socket subsequentes. Definir limites de tempo limite com este método oferece mais granularidade do que set_timeout. Assim, é preferível usar set_timeouts em vez de set_timeout.
Este método foi adicionado na versão v0.28.
set_keepalive
syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)
Coloca a conexão Redis atual imediatamente no pool de conexões cosocket do ngx_lua.
Você pode especificar o tempo máximo de ociosidade (em ms) quando a conexão está no pool e o tamanho máximo do pool para cada processo de trabalho do nginx.
Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.
Chame este método apenas no lugar onde você teria chamado o método close em vez disso. Chamar este método transformará imediatamente o objeto redis atual no estado closed. Quaisquer operações subsequentes além de connect() no objeto atual retornarão o erro closed.
get_reused_times
syntax: times, err = red:get_reused_times()
Este método retorna o número de vezes que a conexão atual foi (com sucesso) reutilizada. Em caso de erro, retorna nil e uma string descrevendo o erro.
Se a conexão atual não vier do pool de conexões embutido, então este método sempre retornará 0, ou seja, a conexão nunca foi reutilizada (ainda). Se a conexão vier do pool de conexões, então o valor de retorno será sempre diferente de zero. Portanto, este método também pode ser usado para determinar se a conexão atual vem do pool.
close
syntax: ok, err = red:close()
Fecha a conexão redis atual e retorna o status.
Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.
init_pipeline
syntax: red:init_pipeline()
syntax: red:init_pipeline(n)
Habilita o modo de pipeline do redis. Todas as chamadas subsequentes aos métodos de comando Redis serão automaticamente armazenadas em cache e enviadas ao servidor em uma única execução quando o método commit_pipeline for chamado ou canceladas chamando o método cancel_pipeline.
Este método sempre é bem-sucedido.
Se o objeto redis já estiver no modo de pipeline do Redis, então chamar este método descartará as consultas Redis em cache existentes.
O argumento opcional n especifica o número (aproximado) de comandos que serão adicionados a este pipeline, o que pode tornar as coisas um pouco mais rápidas.
commit_pipeline
syntax: results, err = red:commit_pipeline()
Sai do modo de pipeline confirmando todas as consultas Redis em cache para o servidor remoto em uma única execução. Todas as respostas para essas consultas serão coletadas automaticamente e retornadas como se fosse uma grande resposta multi-bulk no nível mais alto.
Este método retorna nil e uma string Lua descrevendo o erro em caso de falhas.
cancel_pipeline
syntax: red:cancel_pipeline()
Sai do modo de pipeline descartando todos os comandos Redis em cache existentes desde a última chamada ao método init_pipeline.
Este método sempre é bem-sucedido.
Se o objeto redis não estiver no modo de pipeline do Redis, então este método não faz nada.
hmset
syntax: res, err = red:hmset(myhash, field1, value1, field2, value2, ...)
syntax: res, err = red:hmset(myhash, { field1 = value1, field2 = value2, ... })
Wrapper especial para o comando Redis "hmset".
Quando há apenas três argumentos (incluindo o próprio objeto "red"), o último argumento deve ser uma tabela Lua contendo todos os pares campo/valor.
array_to_hash
syntax: hash = red:array_to_hash(array)
Função auxiliar que converte uma tabela Lua semelhante a um array em uma tabela semelhante a um hash.
Este método foi introduzido pela primeira vez na versão v0.11.
read_reply
syntax: res, err = red:read_reply()
Lê uma resposta do servidor redis. Este método é mais útil para a API Pub/Sub do Redis, por exemplo,
local cjson = require "cjson"
local redis = require "resty.redis"
local red = redis:new()
local red2 = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 seg
red2:set_timeouts(1000, 1000, 1000) -- 1 seg
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("1: falha ao conectar: ", err)
return
end
ok, err = red2:connect("127.0.0.1", 6379)
if not ok then
ngx.say("2: falha ao conectar: ", err)
return
end
local res, err = red:subscribe("dog")
if not res then
ngx.say("1: falha ao assinar: ", err)
return
end
ngx.say("1: assinar: ", cjson.encode(res))
res, err = red2:publish("dog", "Hello")
if not res then
ngx.say("2: falha ao publicar: ", err)
return
end
ngx.say("2: publicar: ", cjson.encode(res))
res, err = red:read_reply()
if not res then
ngx.say("1: falha ao ler resposta: ", err)
return
end
ngx.say("1: receber: ", cjson.encode(res))
red:close()
red2:close()
Executar este exemplo gera a saída assim:
1: assinar: ["subscribe","dog",1]
2: publicar: 1
1: receber: ["message","dog","Hello"]
Os seguintes métodos de classe são fornecidos:
add_commands
syntax: hash = redis.add_commands(cmd_name1, cmd_name2, ...)
AVISO este método está agora obsoleto, uma vez que já fazemos a geração automática de métodos Lua para quaisquer comandos redis que o usuário tenta usar e, portanto, não precisamos mais disso.
Adiciona novos comandos redis à classe resty.redis. Aqui está um exemplo:
local redis = require "resty.redis"
redis.add_commands("foo", "bar")
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 seg
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("falha ao conectar: ", err)
return
end
local res, err = red:foo("a")
if not res then
ngx.say("falha ao foo: ", err)
end
res, err = red:bar()
if not res then
ngx.say("falha ao bar: ", err)
end
Autenticação Redis
O Redis usa o comando AUTH para autenticação: http://redis.io/commands/auth
Não há nada de especial para este comando em comparação com outros comandos Redis como GET e SET. Portanto, pode-se simplesmente invocar o método auth na sua instância resty.redis. Aqui está um exemplo:
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 seg
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("falha ao conectar: ", err)
return
end
local res, err = red:auth("foobared")
if not res then
ngx.say("falha ao autenticar: ", err)
return
end
onde assumimos que o servidor Redis está configurado com a senha foobared no arquivo redis.conf:
requirepass foobared
Se a senha especificada estiver errada, o exemplo acima exibirá o seguinte para o cliente HTTP:
falha ao autenticar: ERR senha inválida
Transações Redis
Esta biblioteca suporta transações Redis. Aqui está um exemplo:
local cjson = require "cjson"
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 seg
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("falha ao conectar: ", err)
return
end
local ok, err = red:multi()
if not ok then
ngx.say("falha ao executar multi: ", err)
return
end
ngx.say("resposta multi: ", cjson.encode(ok))
local ans, err = red:set("a", "abc")
if not ans then
ngx.say("falha ao executar sort: ", err)
return
end
ngx.say("resposta set: ", cjson.encode(ans))
local ans, err = red:lpop("a")
if not ans then
ngx.say("falha ao executar sort: ", err)
return
end
ngx.say("resposta set: ", cjson.encode(ans))
ans, err = red:exec()
ngx.say("resposta exec: ", cjson.encode(ans))
red:close()
Então a saída será
resposta multi: "OK"
resposta set: "QUEUED"
resposta set: "QUEUED"
resposta exec: ["OK",[false,"ERR Operação contra uma chave com o tipo de valor errado"]]
Módulo Redis
Esta biblioteca suporta o módulo Redis. Aqui está um exemplo com o módulo RedisBloom:
local cjson = require "cjson"
local redis = require "resty.redis"
-- registra o prefixo do módulo "bf" para RedisBloom
redis.register_module_prefix("bf")
local red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("falha ao conectar: ", err)
return
end
-- chama o comando BF.ADD com o prefixo 'bf'
res, err = red:bf():add("dog", 1)
if not res then
ngx.say(err)
return
end
ngx.say("receber: ", cjson.encode(res))
-- chama o comando BF.EXISTS
res, err = red:bf():exists("dog")
if not res then
ngx.say(err)
return
end
ngx.say("receber: ", cjson.encode(res))
Balanceamento de Carga e Failover
Você pode implementar trivialmente sua própria lógica de balanceamento de carga Redis em Lua. Basta manter uma tabela Lua com todas as informações de backend Redis disponíveis (como nome do host e números de porta) e escolher um servidor de acordo com alguma regra (como round-robin ou hashing baseado em chave) da tabela Lua a cada requisição. Você pode acompanhar o estado da regra atual nos dados do seu próprio módulo Lua, veja https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker
Da mesma forma, você pode implementar uma lógica de failover automático em Lua com grande flexibilidade.
Depuração
É geralmente conveniente usar a biblioteca lua-cjson para codificar os valores de retorno dos métodos de comando redis em JSON. Por exemplo,
local cjson = require "cjson"
...
local res, err = red:mget("h1234", "h5678")
if res then
print("res: ", cjson.encode(res))
end
Registro Automático de Erros
Por padrão, o módulo subjacente ngx_lua faz registro de erros quando ocorrem erros de socket. Se você já está fazendo o tratamento adequado de erros no seu próprio código Lua, então é recomendável desativar este registro automático de erros desligando a diretiva lua_socket_log_errors do ngx_lua, ou seja,
lua_socket_log_errors off;
Lista de Verificação para Problemas
- Certifique-se de configurar o tamanho do pool de conexões corretamente no set_keepalive. Basicamente, se seu Redis pode lidar com
nconexões simultâneas e seu NGINX temmtrabalhadores, então o tamanho do pool de conexões deve ser configurado comon/m. Por exemplo, se seu Redis geralmente lida com 1000 requisições simultâneas e você tem 10 trabalhadores NGINX, então o tamanho do pool de conexões deve ser 100. Da mesma forma, se você tempdiferentes instâncias do NGINX, então o tamanho do pool de conexões deve sern/m/p. - Certifique-se de que a configuração de backlog do lado do Redis é grande o suficiente. Para Redis 2.8+, você pode ajustar diretamente o parâmetro
tcp-backlogno arquivoredis.conf(e também ajustar o parâmetro do kernelSOMAXCONNde acordo, pelo menos no Linux). Você também pode querer ajustar o parâmetromaxclientsnoredis.conf. - Certifique-se de que você não está usando uma configuração de tempo limite muito curta nos métodos set_timeout ou set_timeouts. Se necessário, tente refazer a operação após o tempo limite e desligar o registro automático de erros (porque você já está fazendo o tratamento adequado de erros no seu próprio código Lua).
- Se o uso de CPU dos processos de trabalho do seu NGINX for muito alto sob carga, então o loop de eventos do NGINX pode estar bloqueado pela computação da CPU demais. Tente amostrar um C-land on-CPU Flame Graph e Lua-land on-CPU Flame Graph para um típico processo de trabalho do NGINX. Você pode otimizar as coisas que consomem CPU de acordo com esses Flame Graphs.
- Se o uso de CPU dos processos de trabalho do seu NGINX for muito baixo sob carga, então o loop de eventos do NGINX pode estar bloqueado por algumas chamadas de sistema bloqueantes (como chamadas de sistema de IO de arquivos). Você pode confirmar o problema executando a ferramenta epoll-loop-blocking-distr contra um típico processo de trabalho do NGINX. Se for realmente o caso, então você pode amostrar um C-land off-CPU Flame Graph para um processo de trabalho do NGINX para analisar os bloqueadores reais.
- Se seu processo
redis-serverestiver rodando perto de 100% de uso da CPU, então você deve considerar escalar seu backend Redis por múltiplos nós ou usar a ferramenta C-land on-CPU Flame Graph para analisar os gargalos internos dentro do processo do servidor Redis.
Limitações
- Esta biblioteca não pode ser usada em contextos de código como init_by_lua, set_by_lua, log_by_lua, e header_filter_by_lua onde a API cosocket do ngx_lua não está disponível.
- A instância do objeto
resty.redisnão pode ser armazenada em uma variável Lua no nível do módulo Lua, porque então será compartilhada por todas as requisições concorrentes tratadas pelo mesmo processo de trabalho do nginx (veja https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker) e resultará em condições de corrida ruins quando requisições concorrentes tentarem usar a mesma instânciaresty.redis(você verá o erro "bad request" ou "socket busy" retornado das chamadas de método). Você deve sempre iniciar objetosresty.redisem variáveis locais de função ou na tabelangx.ctx. Esses lugares têm suas próprias cópias de dados para cada requisição.
Clonar a versão mais recente, assumindo v0.29
wget https://github.com/openresty/lua-resty-redis/archive/refs/tags/v0.29.tar.gz
Extrair
tar -xvzf v0.29.tar.gz
entrar no diretório
cd lua-resty-redis-0.29
export LUA_LIB_DIR=/usr/local/openresty/site/lualib
Compilar e Instalar
make install
Agora o caminho compilado será exibido
/usr/local/lib/lua/resty = lua_package_path na conf do nginx
```
Veja Também
- o módulo ngx_lua: https://github.com/openresty/lua-nginx-module/#readme
- a especificação do protocolo wired do redis: http://redis.io/topics/protocol
- a biblioteca lua-resty-memcached
- a biblioteca lua-resty-mysql
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-redis.