redis2: Module NGINX en amont pour le protocole Redis 2.0
Installation
Vous pouvez installer ce module dans n'importe quelle distribution basée sur RHEL, y compris, mais sans s'y limiter :
- RedHat Enterprise Linux 7, 8, 9 et 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 et 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
Activez le module en ajoutant ce qui suit en haut de /etc/nginx/nginx.conf :
load_module modules/ngx_http_redis2_module.so;
Ce document décrit nginx-module-redis2 v0.15 publié le 19 avril 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; # cela nécessite 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; # cela nécessite ngx_set_misc
set_unescape_uri $val $arg_val; # cela nécessite ngx_set_misc
redis2_query set $key $val;
redis2_pass foo.com:6379;
}
# requêtes pipelinées multiples
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'est pas spécial ici...
redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# les variables peuvent être utilisées ci-dessous et $ est spécial
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; # cela nécessite le module 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;
}
Description
Il s'agit d'un module Nginx en amont qui permet à nginx de communiquer avec un serveur Redis 2.x de manière non bloquante. Le protocole unifié complet de Redis 2.0 a été implémenté, y compris le support du pipelining Redis.
Ce module retourne la réponse TCP brute du serveur Redis. Il est recommandé d'utiliser ma lua-redis-parser (écrite en pur C) pour analyser ces réponses en structures de données Lua lorsqu'elle est combinée avec le lua-nginx-module.
Lorsqu'il est utilisé en conjonction avec le lua-nginx-module, il est recommandé d'utiliser la bibliothèque lua-resty-redis au lieu de ce module, car la première est beaucoup plus flexible et efficace en mémoire.
Si vous souhaitez uniquement utiliser la commande redis get, vous pouvez essayer le HttpRedisModule. Il retourne la partie de contenu analysée de la réponse Redis car seule la commande get est nécessaire à l'implémentation.
Une autre option consiste à analyser les réponses Redis vous-même côté client.
Directives
redis2_query
syntaxe : redis2_query cmd arg1 arg2 ...
par défaut : non
contexte : location, location if
Spécifiez une commande Redis en indiquant ses arguments individuels (y compris le nom de la commande Redis lui-même) de manière similaire à l'utilitaire redis-cli.
Plusieurs instances de cette directive sont autorisées dans une seule location et ces requêtes seront pipelinées. Par exemple,
location = /pipelined {
redis2_query set hello world;
redis2_query get hello;
redis2_pass 127.0.0.1:$TEST_NGINX_REDIS_PORT;
}
Alors GET /pipelined produira deux réponses Redis brutes successives
+OK
$5
world
où les nouvelles lignes ici sont en réalité CR LF (\r\n).
redis2_raw_query
syntaxe : redis2_raw_query QUERY
par défaut : non
contexte : location, location if
Spécifiez des requêtes Redis brutes et les variables nginx sont reconnues dans l'argument QUERY.
Seule une commande Redis est autorisée dans l'argument QUERY, sinon vous recevrez une erreur. Si vous souhaitez spécifier plusieurs commandes pipelinées dans une seule requête, utilisez la directive redis2_raw_queries à la place.
redis2_raw_queries
syntaxe : redis2_raw_queries N QUERIES
par défaut : non
contexte : location, location if
Spécifiez N commandes dans l'argument QUERIES. Les arguments N et QUERIES peuvent prendre des variables Nginx.
Voici quelques exemples
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
syntaxe : redis2_literal_raw_query QUERY
par défaut : non
contexte : location, location if
Spécifiez une requête Redis brute mais les variables Nginx à l'intérieur ne seront pas reconnues. En d'autres termes, vous êtes libre d'utiliser le caractère dollar ($) dans votre argument QUERY.
Seule une commande Redis est autorisée dans l'argument QUERY.
redis2_pass
syntaxe : redis2_pass <upstream_name>
syntaxe : redis2_pass <host>:<port>
par défaut : non
contexte : location, location if
phase : content
Spécifiez le serveur backend Redis.
redis2_connect_timeout
syntaxe : redis2_connect_timeout <time>
par défaut : 60s
contexte : http, server, location
Le délai d'attente pour se connecter au serveur Redis, en secondes par défaut.
Il est judicieux de toujours spécifier explicitement l'unité de temps pour éviter toute confusion. Les unités de temps prises en charge sont s(secondes), ms(millisecondes), y(années), M(mois), w(semaines), d(jours), h(heures), et m(minutes).
Ce temps doit être inférieur à 597 heures.
redis2_send_timeout
syntaxe : redis2_send_timeout <time>
par défaut : 60s
contexte : http, server, location
Le délai d'attente pour envoyer des requêtes TCP au serveur Redis, en secondes par défaut.
Il est judicieux de toujours spécifier explicitement l'unité de temps pour éviter toute confusion. Les unités de temps prises en charge sont s(secondes), ms(millisecondes), y(années), M(mois), w(semaines), d(jours), h(heures), et m(minutes).
redis2_read_timeout
syntaxe : redis2_read_timeout <time>
par défaut : 60s
contexte : http, server, location
Le délai d'attente pour lire les réponses TCP du serveur Redis, en secondes par défaut.
Il est judicieux de toujours spécifier explicitement l'unité de temps pour éviter toute confusion. Les unités de temps prises en charge sont s(secondes), ms(millisecondes), y(années), M(mois), w(semaines), d(jours), h(heures), et m(minutes).
redis2_buffer_size
syntaxe : redis2_buffer_size <size>
par défaut : 4k/8k
contexte : http, server, location
Cette taille de tampon est utilisée pour lire les réponses Redis, mais il n'est pas nécessaire qu'elle soit aussi grande que la plus grande réponse Redis possible.
Cette taille par défaut est la taille de la page, qui peut être de 4k ou 8k.
redis2_next_upstream
syntaxe : redis2_next_upstream [ error | timeout | invalid_response | off ]
par défaut : error timeout
contexte : http, server, location
Spécifiez quelles conditions d'échec doivent entraîner le transfert de la requête vers un autre serveur en amont. S'applique uniquement lorsque la valeur dans redis2_pass est un amont avec deux serveurs ou plus.
Voici un exemple artificiel :
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 connexions
Vous pouvez utiliser le excellent HttpUpstreamKeepaliveModule avec ce module pour fournir un pool de connexions TCP pour Redis.
Un extrait de configuration d'exemple ressemble à ceci
http {
upstream backend {
server 127.0.0.1:6379;
# un pool avec au maximum 1024 connexions
# et ne pas distinguer les serveurs :
keepalive 1024;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass backend;
}
}
}
Sélection des bases de données Redis
Redis fournit la commande select pour changer de base de données Redis. Cette commande n'est pas différente des autres commandes normales comme get ou set. Vous pouvez donc les utiliser dans les directives redis2_query, par exemple,
redis2_query select 8;
redis2_query get foo;
Interopérabilité Lua
Ce module peut être utilisé comme un client redis2 non bloquant pour le lua-nginx-module (mais de nos jours, il est recommandé d'utiliser la bibliothèque lua-resty-redis à la place, qui est beaucoup plus simple à utiliser et plus efficace dans la plupart des cas). Voici un exemple utilisant une sous-requête GET :
location = /redis {
internal;
# set_unescape_uri est fourni par 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 .. "]")
';
}
Ensuite, accéder à /main produit
[+PONG\r\n]
où \r\n est CRLF. C'est-à-dire que ce module retourne les réponses TCP brutes du serveur Redis distant. Pour les développeurs d'applications basées sur Lua, ils peuvent vouloir utiliser la bibliothèque lua-redis-parser (écrite en pur C) pour analyser ces réponses brutes en structures de données Lua.
Lors du déplacement du code Lua en ligne dans un fichier externe .lua, il est important d'utiliser la séquence d'échappement \r\n directement. Nous avons utilisé \\r\\n ci-dessus simplement parce que le code Lua lui-même doit être cité lorsqu'il est placé dans une chaîne littérale Nginx.
Vous pouvez également utiliser des sous-requêtes POST/PUT pour transférer la requête Redis brute via le corps de la requête, ce qui ne nécessite pas d'échappement et de déséchappement d'URI, économisant ainsi quelques cycles CPU. Voici un exemple :
location = /redis {
internal;
# $echo_request_body est fourni par le module 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 .. "]")
';
}
Cela produit exactement la même sortie que l'exemple précédent (GET).
On peut également utiliser Lua pour choisir un backend Redis concret en fonction de certaines règles de hachage compliquées. Par exemple,
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 "
-- choisir un serveur au hasard
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)
";
}
}
Requêtes Redis pipelinées par Lua
Voici un exemple complet démontrant comment utiliser Lua pour émettre plusieurs requêtes Redis pipelinées via ce module Nginx.
Tout d'abord, nous incluons ce qui suit dans notre fichier 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;
}
Fondamentalement, nous utilisons les arguments de requête URI pour passer le nombre de requêtes Redis et le corps de la requête pour passer la chaîne de requête Redis pipelinée.
Ensuite, nous créons le fichier conf/test.lua (dont le chemin est relatif à la racine du serveur Nginx) pour inclure le code Lua suivant :
-- 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, "échec de la requête vers 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
Ici, nous supposons que votre serveur Redis écoute sur le port par défaut (6379) de l'hôte local. Nous utilisons également la bibliothèque lua-redis-parser pour construire des requêtes Redis brutes pour nous et l'utilisons également pour analyser les réponses.
Accéder à l'emplacement /test via des clients HTTP comme curl produit la sortie suivante
OK
hello world
Un cadre plus réaliste consiste à utiliser une définition d'amont appropriée pour notre backend Redis et à activer le pool de connexions TCP via la directive keepalive à l'intérieur.
Support de publication/abonnement Redis
Ce module a un support limité pour la fonctionnalité de publication/abonnement de Redis. Il ne peut pas être entièrement pris en charge en raison de la nature sans état du modèle REST et HTTP.
Considérez l'exemple suivant :
location = /redis {
redis2_raw_queries 2 "subscribe /foo/bar\r\n";
redis2_pass 127.0.0.1:6379;
}
Et ensuite publiez un message pour la clé /foo/bar dans la ligne de commande redis-cli. Ensuite, vous recevrez deux réponses multi-bulk de l'emplacement /redis.
Vous pouvez certainement analyser les réponses avec la bibliothèque lua-redis-parser si vous utilisez Lua pour accéder à l'emplacement de ce module.
Limitations pour la publication/abonnement Redis
Si vous souhaitez utiliser la fonctionnalité pub/sub de Redis avec ce module, vous devez noter les limitations suivantes :
- Vous ne pouvez pas utiliser le HttpUpstreamKeepaliveModule avec cet amont Redis. Seules les connexions Redis courtes fonctionneront.
- Il peut y avoir des conditions de concurrence qui produisent les avertissements inoffensifs
Le serveur Redis a renvoyé des octets supplémentairesdans le fichier error.log de votre nginx. De tels avertissements peuvent être rares, mais soyez simplement préparé à cela. - Vous devez ajuster les différents paramètres de délai d'attente fournis par ce module comme redis2_connect_timeout et redis2_read_timeout.
Si vous ne pouvez pas supporter ces limitations, il est fortement recommandé de passer à la bibliothèque lua-resty-redis pour le lua-nginx-module.
Optimisation des performances
- Lorsque vous utilisez ce module, veuillez vous assurer que vous utilisez un pool de connexions TCP (fourni par le HttpUpstreamKeepaliveModule) et le pipelining Redis chaque fois que cela est possible. Ces fonctionnalités amélioreront considérablement les performances.
- Utiliser plusieurs instances de serveurs Redis sur vos machines multi-cœurs aide également beaucoup en raison de la nature de traitement séquentiel d'une seule instance de serveur Redis.
- Lorsque vous évaluez les performances en utilisant quelque chose comme
abouhttp_load, veuillez vous assurer que le niveau de votre journal d'erreurs est suffisamment élevé (commewarn) pour empêcher les travailleurs Nginx de passer trop de cycles à vider le fichiererror.log, qui est toujours non tamponné et bloquant et donc très coûteux.
VOIR ÉGALEMENT
- La page d'accueil du serveur Redis.
- Le protocole de communication Redis : http://redis.io/topics/protocol
- un analyseur de réponse redis et un constructeur de requêtes pour Lua : lua-redis-parser.
- lua-nginx-module
- Le bundle ngx_openresty.
- La bibliothèque lua-resty-redis basée sur l'API cosocket du lua-nginx-module.
GitHub
Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-redis2.