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;
}
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 adicionalesen 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
abohttp_load, asegúrate de que el nivel de registro de errores sea lo suficientemente alto (comowarn) para evitar que los trabajadores de NGINX gasten demasiados ciclos en vaciar el archivoerror.log, que siempre es no bufferizado y bloqueante y, por lo tanto, muy costoso.
VER TAMBIÉN
- La página de inicio del servidor Redis.
- El protocolo de red de Redis: http://redis.io/topics/protocol
- un analizador de respuestas redis y un constructor de solicitudes para Lua: lua-redis-parser.
- lua-nginx-module
- El paquete ngx_openresty.
- La biblioteca lua-resty-redis basada en la API de cosocket de lua-nginx-module.
GitHub
Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-redis2.