redis2: Módulo upstream do NGINX para o protocolo Redis 2.0
Instalação
Você pode instalar este módulo em qualquer distribuição baseada em RHEL, incluindo, mas não se limitando a:
- RedHat Enterprise Linux 7, 8, 9 e 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 e Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-redis2
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 nginx-module-redis2
Ative o módulo adicionando o seguinte no topo de /etc/nginx/nginx.conf:
load_module modules/ngx_http_redis2_module.so;
Este documento descreve o nginx-module-redis2 v0.15 lançado em 19 de abril de 2018.
location = /foo {
set $value 'first';
redis2_query set one $value;
redis2_pass 127.0.0.1:6379;
}
# GET /get?key=some_key
location = /get {
set_unescape_uri $key $arg_key; # isso requer ngx_set_misc
redis2_query get $key;
redis2_pass foo.com:6379;
}
# GET /set?key=one&val=first%20value
location = /set {
set_unescape_uri $key $arg_key; # isso requer ngx_set_misc
set_unescape_uri $val $arg_val; # isso requer ngx_set_misc
redis2_query set $key $val;
redis2_pass foo.com:6379;
}
# múltiplas consultas em pipeline
location = /foo {
set $value 'first';
redis2_query set one $value;
redis2_query get one;
redis2_query set one two;
redis2_query get one;
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# $ não é especial aqui...
redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# variáveis podem ser usadas abaixo e $ é especial
redis2_raw_query 'get one\r\n';
redis2_pass 127.0.0.1:6379;
}
# GET /baz?get%20foo%0d%0a
location = /baz {
set_unescape_uri $query $query_string; # isso requer o módulo ngx_set_misc
redis2_raw_query $query;
redis2_pass 127.0.0.1:6379;
}
location = /init {
redis2_query del key1;
redis2_query lpush key1 C;
redis2_query lpush key1 B;
redis2_query lpush key1 A;
redis2_pass 127.0.0.1:6379;
}
location = /get {
redis2_query lrange key1 0 -1;
redis2_pass 127.0.0.1:6379;
}
Descrição
Este é um módulo upstream do NGINX que permite que o NGINX se comunique com um servidor Redis 2.x de forma não bloqueante. O protocolo unificado completo do Redis 2.0 foi implementado, incluindo o suporte a pipelining do Redis.
Este módulo retorna a resposta TCP bruta do servidor Redis. É recomendado usar meu lua-redis-parser (escrito em C puro) para analisar essas respostas em estruturas de dados Lua quando combinado com o lua-nginx-module.
Quando usado em conjunto com o lua-nginx-module, é recomendado usar a biblioteca lua-resty-redis em vez deste módulo, pois a primeira é muito mais flexível e eficiente em termos de memória.
Se você só deseja usar o comando redis get, pode experimentar o HttpRedisModule. Ele retorna a parte de conteúdo analisada da resposta do Redis, pois apenas get é necessário para implementar.
Outra opção é analisar as respostas do Redis no lado do cliente você mesmo.
Diretrizes
redis2_query
syntax: redis2_query cmd arg1 arg2 ...
default: não
context: location, location if
Especifique um comando Redis especificando seus argumentos individuais (incluindo o nome do comando Redis em si) de maneira semelhante à utilidade redis-cli.
Múltiplas instâncias desta diretiva são permitidas em uma única localização e essas consultas serão pipelinadas. Por exemplo,
location = /pipelined {
redis2_query set hello world;
redis2_query get hello;
redis2_pass 127.0.0.1:$TEST_NGINX_REDIS_PORT;
}
então GET /pipelined resultará em duas respostas brutas do Redis sucessivas
+OK
$5
world
enquanto novas linhas aqui são na verdade CR LF (\r\n).
redis2_raw_query
syntax: redis2_raw_query QUERY
default: não
context: location, location if
Especifique consultas Redis brutas e variáveis do NGINX são reconhecidas no argumento QUERY.
Apenas um comando Redis é permitido no argumento QUERY, ou você receberá um erro. Se você quiser especificar múltiplos comandos em pipeline em uma única consulta, use a diretiva redis2_raw_queries em vez disso.
redis2_raw_queries
syntax: redis2_raw_queries N QUERIES
default: não
context: location, location if
Especifique N comandos no argumento QUERIES. Tanto os argumentos N quanto QUERIES podem aceitar variáveis do NGINX.
Aqui estão alguns exemplos
location = /pipelined {
redis2_raw_queries 3 "flushall\r\nget key1\r\nget key2\r\n";
redis2_pass 127.0.0.1:6379;
}
# GET /pipelined2?n=2&cmds=flushall%0D%0Aget%20key%0D%0A
location = /pipelined2 {
set_unescape_uri $n $arg_n;
set_unescape_uri $cmds $arg_cmds;
redis2_raw_queries $n $cmds;
redis2_pass 127.0.0.1:6379;
}
redis2_literal_raw_query
syntax: redis2_literal_raw_query QUERY
default: não
context: location, location if
Especifique uma consulta Redis bruta, mas variáveis do NGINX nela não serão reconhecidas. Em outras palavras, você está livre para usar o caractere de cifrão ($) no seu argumento QUERY.
Apenas um comando Redis é permitido no argumento QUERY.
redis2_pass
syntax: redis2_pass <upstream_name>
syntax: redis2_pass <host>:<port>
default: não
context: location, location if
phase: content
Especifique o backend do servidor Redis.
redis2_connect_timeout
syntax: redis2_connect_timeout <time>
default: 60s
context: http, server, location
O tempo limite para conectar ao servidor Redis, em segundos por padrão.
É sábio sempre especificar explicitamente a unidade de tempo para evitar confusão. As unidades de tempo suportadas são s(segundos), ms(milissegundos), y(anos), M(meses), w(semanas), d(dias), h(horas) e m(minutos).
Esse tempo deve ser inferior a 597 horas.
redis2_send_timeout
syntax: redis2_send_timeout <time>
default: 60s
context: http, server, location
O tempo limite para enviar solicitações TCP ao servidor Redis, em segundos por padrão.
É sábio sempre especificar explicitamente a unidade de tempo para evitar confusão. As unidades de tempo suportadas são s(segundos), ms(milissegundos), y(anos), M(meses), w(semanas), d(dias), h(horas) e m(minutos).
redis2_read_timeout
syntax: redis2_read_timeout <time>
default: 60s
context: http, server, location
O tempo limite para ler respostas TCP do servidor Redis, em segundos por padrão.
É sábio sempre especificar explicitamente a unidade de tempo para evitar confusão. As unidades de tempo suportadas são s(segundos), ms(milissegundos), y(anos), M(meses), w(semanas), d(dias), h(horas) e m(minutos).
redis2_buffer_size
syntax: redis2_buffer_size <size>
default: 4k/8k
context: http, server, location
Esse tamanho de buffer é usado para ler respostas do Redis, mas não é necessário que seja tão grande quanto a maior resposta possível do Redis.
Esse tamanho padrão é o tamanho da página, que pode ser 4k ou 8k.
redis2_next_upstream
syntax: redis2_next_upstream [ error | timeout | invalid_response | off ]
default: error timeout
context: http, server, location
Especifique quais condições de falha devem fazer com que a solicitação seja encaminhada para outro servidor upstream. Aplica-se apenas quando o valor em redis2_pass é um upstream com dois ou mais servidores.
Aqui está um exemplo artificial:
upstream redis_cluster {
server 127.0.0.1:6379;
server 127.0.0.1:6380;
}
server {
location = /redis {
redis2_next_upstream error timeout invalid_response;
redis2_query get foo;
redis2_pass redis_cluster;
}
}
Pool de Conexões
Você pode usar o excelente HttpUpstreamKeepaliveModule com este módulo para fornecer um pool de conexões TCP para o Redis.
Um trecho de configuração de exemplo se parece com isto
http {
upstream backend {
server 127.0.0.1:6379;
# um pool com no máximo 1024 conexões
# e não distingue os servidores:
keepalive 1024;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass backend;
}
}
}
Selecionando Bancos de Dados Redis
O Redis fornece o comando select para alternar entre bancos de dados Redis. Este comando não é diferente de outros comandos normais, como get ou set. Portanto, você pode usá-los nas diretivas redis2_query, por exemplo,
redis2_query select 8;
redis2_query get foo;
Interoperabilidade Lua
Este módulo pode ser usado como um cliente redis2 não bloqueante para o lua-nginx-module (mas atualmente é recomendado usar a biblioteca lua-resty-redis em vez disso, que é muito mais simples de usar e mais eficiente na maioria das vezes). Aqui está um exemplo usando uma subsolicitação GET:
location = /redis {
internal;
# set_unescape_uri é fornecido pelo ngx_set_misc
set_unescape_uri $query $arg_query;
redis2_raw_query $query;
redis2_pass 127.0.0.1:6379;
}
location = /main {
content_by_lua '
local res = ngx.location.capture("/redis",
{ args = { query = "ping\\r\\n" } }
)
ngx.print("[" .. res.body .. "]")
';
}
Então, acessar /main resulta em
[+PONG\r\n]
onde \r\n é CRLF. Ou seja, este módulo retorna as respostas TCP brutas do servidor Redis remoto. Para desenvolvedores de aplicativos baseados em Lua, pode ser interessante utilizar a biblioteca lua-redis-parser (escrita em C puro) para analisar tais respostas brutas em estruturas de dados Lua.
Ao mover o código Lua embutido para um arquivo externo .lua, é importante usar a sequência de escape \r\n diretamente. Usamos \\r\\n acima apenas porque o código Lua em si precisa de aspas ao ser colocado em uma string literal do NGINX.
Você também pode usar subsolicitações POST/PUT para transferir a solicitação Redis bruta via corpo da solicitação, o que não requer escape e desescape de URI, economizando assim alguns ciclos de CPU. Aqui está um exemplo:
location = /redis {
internal;
# $echo_request_body é fornecido pelo módulo ngx_echo
redis2_raw_query $echo_request_body;
redis2_pass 127.0.0.1:6379;
}
location = /main {
content_by_lua '
local res = ngx.location.capture("/redis",
{ method = ngx.HTTP_PUT,
body = "ping\\r\\n" }
)
ngx.print("[" .. res.body .. "]")
';
}
Isso resulta exatamente na mesma saída que o exemplo anterior (GET).
Você também pode usar Lua para escolher um backend Redis concreto com base em algumas regras de hash complicadas. Por exemplo,
upstream redis-a {
server foo.bar.com:6379;
}
upstream redis-b {
server bar.baz.com:6379;
}
upstream redis-c {
server blah.blah.org:6379;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass $arg_backend;
}
location = /foo {
content_by_lua "
-- escolha um servidor aleatoriamente
local servers = {'redis-a', 'redis-b', 'redis-c'}
local i = ngx.time() % #servers + 1;
local srv = servers[i]
local res = ngx.location.capture('/redis',
{ args = {
query = '...',
backend = srv
}
}
)
ngx.say(res.body)
";
}
}
Solicitações Redis em Pipeline via Lua
Aqui está um exemplo completo demonstrando como usar Lua para emitir várias solicitações Redis em pipeline através deste módulo do NGINX.
Primeiramente, incluímos o seguinte em nosso arquivo nginx.conf:
location = /redis2 {
internal;
redis2_raw_queries $args $echo_request_body;
redis2_pass 127.0.0.1:6379;
}
location = /test {
content_by_lua_file conf/test.lua;
}
Basicamente, usamos os argumentos de consulta da URI para passar o número de solicitações Redis e o corpo da solicitação para passar a string de solicitação Redis em pipeline.
E então criamos o arquivo conf/test.lua (cujo caminho é relativo à raiz do servidor do NGINX) para incluir o seguinte código Lua:
-- conf/test.lua
local parser = require "redis.parser"
local reqs = {
{"set", "foo", "hello world"},
{"get", "foo"}
}
local raw_reqs = {}
for i, req in ipairs(reqs) do
table.insert(raw_reqs, parser.build_query(req))
end
local res = ngx.location.capture("/redis2?" .. #reqs,
{ body = table.concat(raw_reqs, "") })
if res.status ~= 200 or not res.body then
ngx.log(ngx.ERR, "falha ao consultar o redis")
ngx.exit(500)
end
local replies = parser.parse_replies(res.body, #reqs)
for i, reply in ipairs(replies) do
ngx.say(reply[1])
end
Aqui assumimos que seu servidor Redis está escutando na porta padrão (6379) do localhost. Também utilizamos a biblioteca lua-redis-parser para construir consultas Redis brutas para nós e também a usamos para analisar as respostas.
Acessar a localização /test via clientes HTTP como curl resulta na seguinte saída
OK
hello world
Uma configuração mais realista é usar uma definição de upstream adequada para nosso backend Redis e habilitar o pool de conexões TCP através da diretiva keepalive nela.
Suporte a Publicação/Assinatura do Redis
Este módulo tem suporte limitado para a funcionalidade de publicação/assinatura do Redis. Não pode ser totalmente suportado devido à natureza sem estado do modelo REST e HTTP.
Considere o seguinte exemplo:
location = /redis {
redis2_raw_queries 2 "subscribe /foo/bar\r\n";
redis2_pass 127.0.0.1:6379;
}
E então publique uma mensagem para a chave /foo/bar na linha de comando do redis-cli. E então você receberá duas respostas de múltiplas partes da localização /redis.
Você certamente pode analisar as respostas com a biblioteca lua-redis-parser se estiver usando Lua para acessar a localização deste módulo.
Limitações para Publicação/Assinatura do Redis
Se você deseja usar a funcionalidade de pub/sub do Redis com este módulo, então você deve observar as seguintes limitações:
- Você não pode usar o HttpUpstreamKeepaliveModule com este upstream Redis. Apenas conexões Redis curtas funcionarão.
- Pode haver algumas condições de corrida que produzem os avisos inofensivos
Redis server returned extra bytesem seu error.log do NGINX. Esses avisos podem ser raros, mas esteja preparado para isso. - Você deve ajustar as várias configurações de tempo limite fornecidas por este módulo, como redis2_connect_timeout e redis2_read_timeout.
Se você não pode suportar essas limitações, é altamente recomendado mudar para a biblioteca lua-resty-redis para o lua-nginx-module.
Ajuste de Desempenho
- Quando você estiver usando este módulo, certifique-se de que está usando um pool de conexões TCP (fornecido pelo HttpUpstreamKeepaliveModule) e pipelining do Redis sempre que possível. Esses recursos melhorarão significativamente o desempenho.
- Usar múltiplas instâncias de servidores Redis em suas máquinas multicore também ajuda muito devido à natureza de processamento sequencial de uma única instância do servidor Redis.
- Quando você estiver avaliando o desempenho usando algo como
abouhttp_load, certifique-se de que o nível do seu log de erros é alto o suficiente (comowarn) para evitar que os trabalhadores do NGINX gastem muitos ciclos limpando o arquivoerror.log, que é sempre não bufferizado e bloqueante e, portanto, muito caro.
VEJA TAMBÉM
- A página inicial do servidor Redis.
- O protocolo de comunicação do Redis: http://redis.io/topics/protocol
- um analisador de resposta do Redis e um construtor de solicitações para Lua: lua-redis-parser.
- lua-nginx-module
- O pacote ngx_openresty.
- A biblioteca lua-resty-redis baseada na API cosocket do lua-nginx-module.
GitHub
Você pode encontrar dicas de configuração adicionais e documentação para este módulo no repositório do GitHub para nginx-module-redis2.