Pular para conteúdo

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;
 }
Note que no segundo exemplo acima, a diretiva set_unescape_uri é fornecida pelo set-misc-nginx-module.

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 bytes em 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 ab ou http_load, certifique-se de que o nível do seu log de erros é alto o suficiente (como warn) para evitar que os trabalhadores do NGINX gastem muitos ciclos limpando o arquivo error.log, que é sempre não bufferizado e bloqueante e, portanto, muito caro.

VEJA TAMBÉM

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.