Saltar a contenido

redis2: Módulo upstream de NGINX para el protocolo Redis 2.0

Instalación

Puedes instalar este módulo en cualquier distribución basada en RHEL, incluyendo, pero no limitado a:

  • RedHat Enterprise Linux 7, 8, 9 y 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 y 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

Habilita el módulo añadiendo lo siguiente en la parte superior de /etc/nginx/nginx.conf:

load_module modules/ngx_http_redis2_module.so;

Este documento describe nginx-module-redis2 v0.15 lanzado el 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;  # esto requiere 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;  # esto requiere ngx_set_misc
     set_unescape_uri $val $arg_val;  # esto requiere ngx_set_misc
     redis2_query set $key $val;
     redis2_pass foo.com:6379;
 }

 # múltiples consultas en 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 {
     # $ no es especial aquí...
     redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
     redis2_pass 127.0.0.1:6379;
 }

 location = /bar {
     # las variables pueden ser usadas abajo y $ es 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; # esto requiere el 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;
 }

Descripción

Este es un módulo upstream de NGINX que permite que NGINX se comunique con un servidor Redis 2.x de manera no bloqueante. Se ha implementado el protocolo unificado completo de Redis 2.0, incluyendo el soporte para pipelining de Redis.

Este módulo devuelve la respuesta TCP en bruto del servidor Redis. Se recomienda utilizar mi lua-redis-parser (escrito en C puro) para analizar estas respuestas en estructuras de datos de Lua cuando se combina con lua-nginx-module.

Cuando se utiliza en conjunto con lua-nginx-module, se recomienda utilizar la biblioteca lua-resty-redis en lugar de este módulo, ya que la primera es mucho más flexible y eficiente en memoria.

Si solo deseas usar el comando redis get, puedes probar el HttpRedisModule. Devuelve la parte de contenido analizada de la respuesta de Redis porque solo se necesita get para implementar.

Otra opción es analizar las respuestas de Redis en el lado del cliente tú mismo.

Directivas

redis2_query

syntax: redis2_query cmd arg1 arg2 ...

default: no

context: location, location if

Especifica un comando Redis indicando sus argumentos individuales (incluyendo el nombre del comando Redis en sí) de manera similar a la utilidad redis-cli.

Se permiten múltiples instancias de esta directiva en una sola ubicación y estas consultas se enviarán en pipeline. Por ejemplo,

 location = /pipelined {
     redis2_query set hello world;
     redis2_query get hello;

     redis2_pass 127.0.0.1:$TEST_NGINX_REDIS_PORT;
 }

entonces GET /pipelined producirá dos respuestas en bruto sucesivas de Redis

 +OK
 $5
 world

mientras que las nuevas líneas aquí son en realidad CR LF (\r\n).

redis2_raw_query

syntax: redis2_raw_query QUERY

default: no

context: location, location if

Especifica consultas Redis en bruto y las variables de NGINX son reconocidas en el argumento QUERY.

Solo se permite un comando Redis en el argumento QUERY, o recibirás un error. Si deseas especificar múltiples comandos en pipeline en una sola consulta, utiliza la directiva redis2_raw_queries en su lugar.

redis2_raw_queries

syntax: redis2_raw_queries N QUERIES

default: no

context: location, location if

Especifica N comandos en el argumento QUERIES. Tanto los argumentos N como QUERIES pueden tomar variables de NGINX.

Aquí hay algunos ejemplos

 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;
 }
Ten en cuenta que en el segundo ejemplo anterior, la directiva set_unescape_uri es proporcionada por el set-misc-nginx-module.

redis2_literal_raw_query

syntax: redis2_literal_raw_query QUERY

default: no

context: location, location if

Especifica una consulta Redis en bruto, pero las variables de NGINX en ella no serán reconocidas. En otras palabras, puedes usar el carácter de signo de dólar ($) en tu argumento QUERY.

Solo se permite un comando Redis en el argumento QUERY.

redis2_pass

syntax: redis2_pass <upstream_name>

syntax: redis2_pass <host>:<port>

default: no

context: location, location if

phase: content

Especifica el backend del servidor Redis.

redis2_connect_timeout

syntax: redis2_connect_timeout <time>

default: 60s

context: http, server, location

El tiempo de espera para conectarse al servidor Redis, en segundos por defecto.

Es recomendable siempre especificar explícitamente la unidad de tiempo para evitar confusiones. Las unidades de tiempo soportadas son s(segundos), ms(milisegundos), y(años), M(meses), w(semanas), d(días), h(horas) y m(minutos).

Este tiempo debe ser menor a 597 horas.

redis2_send_timeout

syntax: redis2_send_timeout <time>

default: 60s

context: http, server, location

El tiempo de espera para enviar solicitudes TCP al servidor Redis, en segundos por defecto.

Es recomendable siempre especificar explícitamente la unidad de tiempo para evitar confusiones. Las unidades de tiempo soportadas son s(segundos), ms(milisegundos), y(años), M(meses), w(semanas), d(días), h(horas) y m(minutos).

redis2_read_timeout

syntax: redis2_read_timeout <time>

default: 60s

context: http, server, location

El tiempo de espera para leer respuestas TCP del servidor Redis, en segundos por defecto.

Es recomendable siempre especificar explícitamente la unidad de tiempo para evitar confusiones. Las unidades de tiempo soportadas son s(segundos), ms(milisegundos), y(años), M(meses), w(semanas), d(días), h(horas) y m(minutos).

redis2_buffer_size

syntax: redis2_buffer_size <size>

default: 4k/8k

context: http, server, location

Este tamaño de buffer se utiliza para leer las respuestas de Redis, pero no es necesario que sea tan grande como la respuesta de Redis más grande posible.

Este tamaño por defecto es el tamaño de página, que puede ser 4k o 8k.

redis2_next_upstream

syntax: redis2_next_upstream [ error | timeout | invalid_response | off ]

default: error timeout

context: http, server, location

Especifica qué condiciones de fallo deben causar que la solicitud se reenvíe a otro servidor upstream. Aplica solo cuando el valor en redis2_pass es un upstream con dos o más servidores.

Aquí hay un ejemplo 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 Conexiones

Puedes usar el excelente HttpUpstreamKeepaliveModule con este módulo para proporcionar un pool de conexiones TCP para Redis.

Un fragmento de configuración de ejemplo se ve así

 http {
     upstream backend {
       server 127.0.0.1:6379;

       # un pool con un máximo de 1024 conexiones
       # y no distinguir los servidores:
       keepalive 1024;
     }

     server {
         ...
         location = /redis {
             set_unescape_uri $query $arg_query;
             redis2_query $query;
             redis2_pass backend;
         }
     }
 }

Seleccionando Bases de Datos Redis

Redis proporciona el comando select para cambiar entre bases de datos Redis. Este comando no es diferente de otros comandos normales como get o set. Así que puedes usarlos en las directivas redis2_query, por ejemplo,

redis2_query select 8;
redis2_query get foo;

Interoperabilidad con Lua

Este módulo puede servir como un cliente redis2 no bloqueante para lua-nginx-module (pero hoy en día se recomienda usar la biblioteca lua-resty-redis en su lugar, que es mucho más simple de usar y más eficiente la mayor parte del tiempo). Aquí hay un ejemplo usando una subconsulta GET:

 location = /redis {
     internal;

     # set_unescape_uri es proporcionado por 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 .. "]")
     ';
 }

Luego, acceder a /main produce

[+PONG\r\n]

donde \r\n es CRLF. Es decir, este módulo devuelve las respuestas TCP en bruto del servidor redis remoto. Para los desarrolladores de aplicaciones basadas en Lua, pueden querer utilizar la biblioteca lua-redis-parser (escrita en C puro) para analizar tales respuestas en bruto en estructuras de datos de Lua.

Al mover el código Lua en línea a un archivo externo .lua, es importante usar la secuencia de escape \r\n directamente. Usamos \\r\\n arriba solo porque el código Lua en sí necesita comillas cuando se coloca en una cadena literal de NGINX.

También puedes usar subconsultas POST/PUT para transferir la solicitud Redis en bruto a través del cuerpo de la solicitud, lo que no requiere escape y desescape de URI, ahorrando así algunos ciclos de CPU. Aquí hay un ejemplo de esto:

 location = /redis {
     internal;

     # $echo_request_body es proporcionado por el 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 .. "]")
     ';
 }

Esto produce exactamente la misma salida que el ejemplo anterior (GET).

También se puede usar Lua para seleccionar un backend Redis concreto basado en algunas reglas de hash complicadas. Por ejemplo,

 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 "
             -- seleccionar un 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)
         ";
     }
 }

Solicitudes Redis en Pipeline por Lua

Aquí hay un ejemplo completo que demuestra cómo usar Lua para emitir múltiples solicitudes Redis en pipeline a través de este módulo de NGINX.

Primero, incluimos lo siguiente en nuestro archivo 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;
 }

Básicamente, usamos los argumentos de consulta de URI para pasar el número de solicitudes Redis y el cuerpo de la solicitud para pasar la cadena de solicitud Redis en pipeline.

Y luego creamos el archivo conf/test.lua (cuyo camino es relativo a la raíz del servidor de NGINX) para incluir el siguiente 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, "falló la consulta a 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

Aquí asumimos que tu servidor Redis está escuchando en el puerto por defecto (6379) de localhost. También hacemos uso de la biblioteca lua-redis-parser para construir consultas Redis en bruto para nosotros y también la usamos para analizar las respuestas.

Acceder a la ubicación /test a través de clientes HTTP como curl produce la siguiente salida

OK
hello world

Un ajuste más realista es usar una definición de upstream adecuada para nuestro backend Redis y habilitar el pool de conexiones TCP a través de la directiva keepalive en él.

Soporte para Publicar/Suscribirse en Redis

Este módulo tiene soporte limitado para la función de publicar/suscribirse de Redis. No puede ser totalmente soportado debido a la naturaleza sin estado del modelo REST y HTTP.

Considera el siguiente ejemplo:

 location = /redis {
     redis2_raw_queries 2 "subscribe /foo/bar\r\n";
     redis2_pass 127.0.0.1:6379;
 }

Y luego publica un mensaje para la clave /foo/bar en la línea de comandos de redis-cli. Y luego recibirás dos respuestas de múltiples bulk desde la ubicación /redis.

Seguramente puedes analizar las respuestas con la biblioteca lua-redis-parser si estás usando Lua para acceder a la ubicación de este módulo.

Limitaciones Para Publicar/Suscribirse en Redis

Si deseas utilizar la función de pub/sub de Redis con este módulo, entonces debes tener en cuenta las siguientes limitaciones:

  • No puedes usar HttpUpstreamKeepaliveModule con este upstream de Redis. Solo funcionarán conexiones Redis cortas.
  • Puede haber algunas condiciones de carrera que produzcan las inofensivas advertencias El servidor Redis devolvió bytes adicionales en el error.log de tu nginx. Tales advertencias pueden ser raras, pero solo prepárate para ello.
  • Debes ajustar los diversos ajustes de tiempo de espera proporcionados por este módulo como redis2_connect_timeout y redis2_read_timeout.

Si no puedes soportar estas limitaciones, se te recomienda encarecidamente cambiar a la biblioteca lua-resty-redis para lua-nginx-module.

Ajuste de Rendimiento

  • Cuando uses este módulo, asegúrate de estar utilizando un pool de conexiones TCP (proporcionado por HttpUpstreamKeepaliveModule) y pipelining de Redis siempre que sea posible. Estas características mejorarán significativamente el rendimiento.
  • Usar múltiples instancias de servidores Redis en tus máquinas de múltiples núcleos también ayuda mucho debido a la naturaleza de procesamiento secuencial de una sola instancia de servidor Redis.
  • Cuando estés evaluando el rendimiento utilizando algo como ab o http_load, asegúrate de que el nivel de registro de errores sea lo suficientemente alto (como warn) para evitar que los trabajadores de NGINX gasten demasiados ciclos en vaciar el archivo error.log, que siempre es no bufferizado y bloqueante y, por lo tanto, muy costoso.

VER TAMBIÉN

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-redis2.