redis: pilote client Lua redis pour nginx-module-lua basé sur l'API cosocket
Installation
Si vous n'avez pas configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.
CentOS/RHEL 7 ou Amazon Linux 2
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 lua-resty-redis
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-redis
Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.
Ce document décrit lua-resty-redis v0.33 publié le 09 juillet 2025.
Cette bibliothèque Lua est un pilote client Redis pour le module ngx_lua de nginx :
https://github.com/openresty/lua-nginx-module/#readme
Cette bibliothèque Lua tire parti de l'API cosocket de ngx_lua, qui garantit un comportement 100 % non-bloquant.
Notez qu'au moins ngx_lua 0.5.14 ou OpenResty 1.2.1.14 est requis.
Synopsis
# vous n'avez pas besoin de la ligne suivante si vous utilisez
# le bundle OpenResty :
server {
location /test {
# besoin de spécifier le résolveur pour résoudre le nom d'hôte
resolver 8.8.8.8;
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
-- ou se connecter à un fichier de socket de domaine unix écouté
-- par un serveur redis :
-- local ok, err = red:connect("unix:/path/to/redis.sock")
-- se connecter directement via l'adresse IP
local ok, err = red:connect("127.0.0.1", 6379)
-- ou se connecter via le nom d'hôte, besoin de spécifier le résolveur comme ci-dessus
local ok, err = red:connect("redis.openresty.com", 6379)
if not ok then
ngx.say("échec de la connexion : ", err)
return
end
ok, err = red:set("dog", "un animal")
if not ok then
ngx.say("échec de la définition de dog : ", err)
return
end
ngx.say("résultat de la définition : ", ok)
local res, err = red:get("dog")
if not res then
ngx.say("échec de l'obtention de dog : ", err)
return
end
if res == ngx.null then
ngx.say("dog non trouvé.")
return
end
ngx.say("dog : ", res)
red:init_pipeline()
red:set("cat", "Marry")
red:set("horse", "Bob")
red:get("cat")
red:get("horse")
local results, err = red:commit_pipeline()
if not results then
ngx.say("échec de l'engagement des requêtes en pipeline : ", err)
return
end
for i, res in ipairs(results) do
if type(res) == "table" then
if res[1] == false then
ngx.say("échec de l'exécution de la commande ", i, ": ", res[2])
else
-- traiter la valeur du tableau
end
else
-- traiter la valeur scalaire
end
end
-- le mettre dans le pool de connexions de taille 100,
-- avec un temps d'inactivité maximum de 10 secondes
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.say("échec de la définition du keepalive : ", err)
return
end
-- ou simplement fermer la connexion immédiatement :
-- local ok, err = red:close()
-- if not ok then
-- ngx.say("échec de la fermeture : ", err)
-- return
-- end
}
}
}
Méthodes
Toutes les commandes Redis ont leurs propres méthodes avec le même nom, sauf qu'elles sont toutes en minuscules.
Vous pouvez trouver la liste complète des commandes Redis ici :
Vous devez consulter cette référence de commande Redis pour voir quelles commandes Redis acceptent quels arguments.
Les arguments de commande Redis peuvent être directement fournis dans l'appel de méthode correspondant. Par exemple, la commande redis "GET" accepte un seul argument clé, vous pouvez donc simplement appeler la méthode "get" comme ceci :
local res, err = red:get("key")
De même, la commande redis "LRANGE" accepte trois arguments, vous devez donc appeler la méthode "lrange" comme ceci :
local res, err = red:lrange("nokey", 0, 1)
Par exemple, les commandes "SET", "GET", "LRANGE" et "BLPOP" correspondent aux méthodes "set", "get", "lrange" et "blpop".
Voici quelques exemples supplémentaires :
-- HMGET myhash field1 field2 nofield
local res, err = red:hmget("myhash", "field1", "field2", "nofield")
-- HMSET myhash field1 "Hello" field2 "World"
local res, err = red:hmset("myhash", "field1", "Hello", "field2", "World")
Toutes ces méthodes de commande retournent un seul résultat en cas de succès et nil sinon. En cas d'erreurs ou d'échecs, elle renverra également une seconde valeur qui est une chaîne décrivant l'erreur.
Une "réponse de statut" Redis résulte en une valeur de retour de type chaîne avec le préfixe "+" supprimé.
Une "réponse entière" Redis résulte en une valeur de retour de type nombre Lua.
Une "réponse d'erreur" Redis résulte en une valeur false et une chaîne décrivant l'erreur.
Une réponse "bulk" Redis non nulle résulte en une chaîne Lua comme valeur de retour. Une réponse bulk nulle résulte en une valeur de retour ngx.null.
Une réponse "multi-bulk" Redis non nulle résulte en une table Lua contenant toutes les valeurs composantes (le cas échéant). Si l'une des valeurs composantes est une valeur d'erreur redis valide, alors ce sera une table à deux éléments {false, err}.
Une réponse multi-bulk nulle retourne une valeur ngx.null.
Voir http://redis.io/topics/protocol pour des détails concernant les différents types de réponses Redis.
En plus de toutes ces méthodes de commande redis, les méthodes suivantes sont également fournies :
new
syntax: red, err = redis:new()
Crée un objet redis. En cas d'échec, retourne nil et une chaîne décrivant l'erreur.
connect
syntax: ok, err = red:connect(host, port, options_table?)
syntax: ok, err = red:connect("unix:/path/to/unix.sock", options_table?)
Tente de se connecter à l'hôte distant et au port que le serveur redis écoute ou à un fichier de socket de domaine unix local écouté par le serveur redis.
Avant de résoudre effectivement le nom d'hôte et de se connecter au backend distant, cette méthode cherchera toujours dans le pool de connexions des connexions inactives correspondantes créées par des appels précédents de cette méthode.
L'argument optionnel options_table est une table Lua contenant les clés suivantes :
-
sslS'il est défini sur true, utilise SSL pour se connecter à redis (par défaut false).
-
ssl_verifyS'il est défini sur true, vérifie la validité du certificat SSL du serveur (par défaut false). Notez que vous devez configurer lua_ssl_trusted_certificate pour spécifier le certificat CA (ou serveur) utilisé par votre serveur redis. Vous devrez peut-être également configurer lua_ssl_verify_depth en conséquence.
-
server_nameSpécifie le nom du serveur pour la nouvelle extension TLS Server Name Indication (SNI) lors de la connexion via SSL.
-
poolSpécifie un nom personnalisé pour le pool de connexions utilisé. S'il est omis, le nom du pool de connexions sera généré à partir du modèle de chaîne
<host>:<port>ou<unix-socket-path>. -
pool_sizeSpécifie la taille du pool de connexions. S'il est omis et qu'aucune option
backlogn'a été fournie, aucun pool ne sera créé. S'il est omis mais quebackloga été fourni, le pool sera créé avec une taille par défaut égale à la valeur de la directive lua_socket_pool_size. Le pool de connexions contient jusqu'àpool_sizeconnexions vivantes prêtes à être réutilisées par des appels ultérieurs à connect, mais notez qu'il n'y a pas de limite supérieure au nombre total de connexions ouvertes en dehors du pool. Si vous devez restreindre le nombre total de connexions ouvertes, spécifiez l'optionbacklog. Lorsque le pool de connexions dépasserait sa limite de taille, la connexion la moins récemment utilisée (keep-alive) déjà dans le pool sera fermée pour faire de la place pour la connexion actuelle. Notez que le pool de connexions cosocket est par processus de travail Nginx plutôt que par instance de serveur Nginx, donc la limite de taille spécifiée ici s'applique également à chaque processus de travail Nginx. Notez également que la taille du pool de connexions ne peut pas être modifiée une fois qu'il a été créé. Notez qu'au moins ngx_lua 0.10.14 est requis pour utiliser ces options. -
backlogSi spécifié, ce module limitera le nombre total de connexions ouvertes pour ce pool. Pas plus de connexions que
pool_sizepeuvent être ouvertes pour ce pool à tout moment. Si le pool de connexions est plein, les opérations de connexion suivantes seront mises en file d'attente dans une file d'attente égale à la valeur de cette option (la file d'attente "backlog"). Si le nombre d'opérations de connexion mises en file d'attente est égal àbacklog, les opérations de connexion suivantes échoueront et retourneront nil plus la chaîne d'erreur"trop d'opérations de connexion en attente". Les opérations de connexion mises en file d'attente seront reprises une fois que le nombre de connexions dans le pool est inférieur àpool_size. L'opération de connexion mise en file d'attente sera abandonnée une fois qu'elle a été mise en file d'attente pendant plus deconnect_timeout, contrôlé par set_timeout, et retournera nil plus la chaîne d'erreur "timeout". Notez qu'au moins ngx_lua 0.10.14 est requis pour utiliser ces options.
set_timeout
syntax: red:set_timeout(time)
Définit la protection de délai d'attente (en ms) pour les opérations suivantes, y compris la méthode connect.
Depuis la version v0.28 de ce module, il est conseillé d'utiliser set_timeouts plutôt que cette méthode.
set_timeouts
syntax: red:set_timeouts(connect_timeout, send_timeout, read_timeout)
Définit respectivement les seuils de délai d'attente de connexion, d'envoi et de lecture (en ms) pour les opérations de socket suivantes. Définir les seuils de délai d'attente avec cette méthode offre plus de granularité que set_timeout. En tant que tel, il est préférable d'utiliser set_timeouts plutôt que set_timeout.
Cette méthode a été ajoutée dans la version v0.28.
set_keepalive
syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)
Met immédiatement la connexion Redis actuelle dans le pool de connexions cosocket ngx_lua.
Vous pouvez spécifier le délai d'inactivité maximum (en ms) lorsque la connexion est dans le pool et la taille maximale du pool pour chaque processus de travail nginx.
En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.
N'appelez cette méthode que là où vous auriez appelé la méthode close à la place. Appeler cette méthode transformera immédiatement l'objet redis actuel en état closed. Toute opération ultérieure autre que connect() sur l'objet actuel retournera l'erreur closed.
get_reused_times
syntax: times, err = red:get_reused_times()
Cette méthode retourne le nombre de fois (avec succès) que la connexion actuelle a été réutilisée. En cas d'erreur, elle retourne nil et une chaîne décrivant l'erreur.
Si la connexion actuelle ne provient pas du pool de connexions intégré, alors cette méthode retourne toujours 0, c'est-à-dire que la connexion n'a jamais été réutilisée (encore). Si la connexion provient du pool de connexions, alors la valeur de retour est toujours non nulle. Ainsi, cette méthode peut également être utilisée pour déterminer si la connexion actuelle provient du pool.
close
syntax: ok, err = red:close()
Ferme la connexion redis actuelle et retourne le statut.
En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.
init_pipeline
syntax: red:init_pipeline()
syntax: red:init_pipeline(n)
Active le mode de pipeline redis. Tous les appels suivants aux méthodes de commande Redis seront automatiquement mis en cache et envoyés au serveur en une seule fois lorsque la méthode commit_pipeline est appelée ou annulés en appelant la méthode cancel_pipeline.
Cette méthode réussit toujours.
Si l'objet redis est déjà en mode pipeline Redis, alors appeler cette méthode annulera les requêtes Redis mises en cache existantes.
L'argument optionnel n spécifie le nombre (approximatif) de commandes qui vont être ajoutées à ce pipeline, ce qui peut rendre les choses un peu plus rapides.
commit_pipeline
syntax: results, err = red:commit_pipeline()
Quitte le mode pipeline en engageant toutes les requêtes Redis mises en cache au serveur distant en une seule fois. Toutes les réponses à ces requêtes seront collectées automatiquement et seront retournées comme s'il s'agissait d'une grande réponse multi-bulk au plus haut niveau.
Cette méthode retourne nil et une chaîne Lua décrivant l'erreur en cas d'échecs.
cancel_pipeline
syntax: red:cancel_pipeline()
Quitte le mode pipeline en annulant toutes les commandes Redis mises en cache existantes depuis le dernier appel à la méthode init_pipeline.
Cette méthode réussit toujours.
Si l'objet redis n'est pas en mode pipeline Redis, alors cette méthode est une opération no-op.
hmset
syntax: res, err = red:hmset(myhash, field1, value1, field2, value2, ...)
syntax: res, err = red:hmset(myhash, { field1 = value1, field2 = value2, ... })
Wrapper spécial pour la commande Redis "hmset".
Lorsqu'il n'y a que trois arguments (y compris l'objet "red" lui-même), alors le dernier argument doit être une table Lua contenant toutes les paires champ/valeur.
array_to_hash
syntax: hash = red:array_to_hash(array)
Fonction auxiliaire qui convertit une table Lua de type tableau en une table de type hash.
Cette méthode a été introduite pour la première fois dans la version v0.11.
read_reply
syntax: res, err = red:read_reply()
Lit une réponse du serveur redis. Cette méthode est principalement utile pour l'API Pub/Sub de Redis, par exemple,
local cjson = require "cjson"
local redis = require "resty.redis"
local red = redis:new()
local red2 = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
red2:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("1: échec de la connexion : ", err)
return
end
ok, err = red2:connect("127.0.0.1", 6379)
if not ok then
ngx.say("2: échec de la connexion : ", err)
return
end
local res, err = red:subscribe("dog")
if not res then
ngx.say("1: échec de l'abonnement : ", err)
return
end
ngx.say("1: abonnement : ", cjson.encode(res))
res, err = red2:publish("dog", "Hello")
if not res then
ngx.say("2: échec de la publication : ", err)
return
end
ngx.say("2: publication : ", cjson.encode(res))
res, err = red:read_reply()
if not res then
ngx.say("1: échec de la lecture de la réponse : ", err)
return
end
ngx.say("1: réception : ", cjson.encode(res))
red:close()
red2:close()
L'exécution de cet exemple donne la sortie suivante :
1: abonnement : ["subscribe","dog",1]
2: publication : 1
1: réception : ["message","dog","Hello"]
Les méthodes de classe suivantes sont fournies :
add_commands
syntax: hash = redis.add_commands(cmd_name1, cmd_name2, ...)
AVERTISSEMENT cette méthode est maintenant obsolète puisque nous générons déjà automatiquement des méthodes Lua pour toutes les commandes redis que l'utilisateur tente d'utiliser et nous n'en avons donc plus besoin.
Ajoute de nouvelles commandes redis à la classe resty.redis. Voici un exemple :
local redis = require "resty.redis"
redis.add_commands("foo", "bar")
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("échec de la connexion : ", err)
return
end
local res, err = red:foo("a")
if not res then
ngx.say("échec de foo : ", err)
end
res, err = red:bar()
if not res then
ngx.say("échec de bar : ", err)
end
Authentification Redis
Redis utilise la commande AUTH pour faire de l'authentification : http://redis.io/commands/auth
Il n'y a rien de spécial pour cette commande par rapport à d'autres commandes Redis comme GET et SET. Vous pouvez donc simplement invoquer la méthode auth sur votre instance resty.redis. Voici un exemple :
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("échec de la connexion : ", err)
return
end
local res, err = red:auth("foobared")
if not res then
ngx.say("échec de l'authentification : ", err)
return
end
où nous supposons que le serveur Redis est configuré avec le mot de passe foobared dans le fichier redis.conf :
requirepass foobared
Si le mot de passe spécifié est incorrect, alors l'exemple ci-dessus affichera ce qui suit au client HTTP :
échec de l'authentification : ERR mot de passe invalide
Transactions Redis
Cette bibliothèque prend en charge les transactions Redis. Voici un exemple :
local cjson = require "cjson"
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("échec de la connexion : ", err)
return
end
local ok, err = red:multi()
if not ok then
ngx.say("échec de l'exécution de multi : ", err)
return
end
ngx.say("réponse multi : ", cjson.encode(ok))
local ans, err = red:set("a", "abc")
if not ans then
ngx.say("échec de l'exécution de sort : ", err)
return
end
ngx.say("réponse set : ", cjson.encode(ans))
local ans, err = red:lpop("a")
if not ans then
ngx.say("échec de l'exécution de sort : ", err)
return
end
ngx.say("réponse set : ", cjson.encode(ans))
ans, err = red:exec()
ngx.say("réponse exec : ", cjson.encode(ans))
red:close()
Ensuite, la sortie sera
réponse multi : "OK"
réponse set : "QUEUED"
réponse set : "QUEUED"
réponse exec : ["OK",[false,"ERR Opération contre une clé contenant le mauvais type de valeur"]]
Module Redis
Cette bibliothèque prend en charge le module Redis. Voici un exemple avec le module RedisBloom :
local cjson = require "cjson"
local redis = require "resty.redis"
-- enregistrer le préfixe de module "bf" pour RedisBloom
redis.register_module_prefix("bf")
local red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("échec de la connexion : ", err)
return
end
-- appeler la commande BF.ADD avec le préfixe 'bf'
res, err = red:bf():add("dog", 1)
if not res then
ngx.say(err)
return
end
ngx.say("réception : ", cjson.encode(res))
-- appeler la commande BF.EXISTS
res, err = red:bf():exists("dog")
if not res then
ngx.say(err)
return
end
ngx.say("réception : ", cjson.encode(res))
Équilibrage de charge et basculement
Vous pouvez facilement implémenter votre propre logique d'équilibrage de charge Redis vous-même en Lua. Il suffit de garder une table Lua de toutes les informations de backend Redis disponibles (comme le nom d'hôte et les numéros de port) et de choisir un serveur selon une règle (comme le round-robin ou le hachage basé sur la clé) à partir de la table Lua à chaque requête. Vous pouvez suivre l'état de la règle actuelle dans les données de votre propre module Lua, voir https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker
De même, vous pouvez implémenter une logique de basculement automatique en Lua avec une grande flexibilité.
Débogage
Il est généralement pratique d'utiliser la bibliothèque lua-cjson pour encoder les valeurs de retour des méthodes de commande redis en JSON. Par exemple,
local cjson = require "cjson"
...
local res, err = red:mget("h1234", "h5678")
if res then
print("res : ", cjson.encode(res))
end
Journalisation automatique des erreurs
Par défaut, le module sous-jacent ngx_lua effectue une journalisation des erreurs lorsque des erreurs de socket se produisent. Si vous gérez déjà correctement les erreurs dans votre propre code Lua, il est recommandé de désactiver cette journalisation automatique des erreurs en désactivant la directive lua_socket_log_errors de ngx_lua, c'est-à-dire,
lua_socket_log_errors off;
Liste de contrôle pour les problèmes
- Assurez-vous de configurer correctement la taille du pool de connexions dans set_keepalive. En gros, si votre Redis peut gérer
nconnexions concurrentes et que votre NGINX amtravailleurs, alors la taille du pool de connexions doit être configurée commen/m. Par exemple, si votre Redis gère généralement 1000 requêtes concurrentes et que vous avez 10 travailleurs NGINX, alors la taille du pool de connexions doit être de 100. De même, si vous avezpinstances NGINX différentes, alors la taille du pool de connexions doit êtren/m/p. - Assurez-vous que le paramètre de backlog côté Redis est suffisamment grand. Pour Redis 2.8+, vous pouvez directement ajuster le paramètre
tcp-backlogdans le fichierredis.conf(et également ajuster le paramètre du noyauSOMAXCONNen conséquence, au moins sous Linux). Vous voudrez peut-être également ajuster le paramètremaxclientsdansredis.conf. - Assurez-vous de ne pas utiliser un paramètre de délai d'attente trop court dans les méthodes set_timeout ou set_timeouts. Si vous devez le faire, essayez de refaire l'opération en cas de délai d'attente et de désactiver la journalisation automatique des erreurs (car vous gérez déjà correctement les erreurs dans votre propre code Lua).
- Si l'utilisation du CPU de vos processus de travail NGINX est très élevée sous charge, alors la boucle d'événements NGINX pourrait être bloquée par le calcul CPU trop longtemps. Essayez d'échantillonner un C-land on-CPU Flame Graph et un Lua-land on-CPU Flame Graph pour un processus de travail NGINX typique. Vous pouvez optimiser les choses liées au CPU en fonction de ces Flame Graphs.
- Si l'utilisation du CPU de vos processus de travail NGINX est très faible sous charge, alors la boucle d'événements NGINX pourrait être bloquée par certains appels système bloquants (comme les appels système d'E/S de fichiers). Vous pouvez confirmer le problème en exécutant l'outil epoll-loop-blocking-distr contre un processus de travail NGINX typique. Si c'est effectivement le cas, alors vous pouvez échantillonner un C-land off-CPU Flame Graph pour un processus de travail NGINX afin d'analyser les véritables blocages.
- Si votre processus
redis-serverfonctionne près de 100 % d'utilisation CPU, alors vous devriez envisager de mettre à l'échelle votre backend Redis par plusieurs nœuds ou d'utiliser l'outil C-land on-CPU Flame Graph pour analyser les goulets d'étranglement internes au sein du processus serveur Redis.
Limitations
- Cette bibliothèque ne peut pas être utilisée dans des contextes de code comme init_by_lua, set_by_lua, log_by_lua, et header_filter_by_lua où l'API cosocket ngx_lua n'est pas disponible.
- L'instance d'objet
resty.redisne peut pas être stockée dans une variable Lua au niveau du module Lua, car elle sera alors partagée par toutes les requêtes concurrentes traitées par le même processus de travail nginx (voir https://github.com/openresty/lua-nginx-module/#data-sharing-within-an-nginx-worker) et entraînera de mauvaises conditions de concurrence lorsque des requêtes concurrentes essaient d'utiliser la même instanceresty.redis(vous verrez l'erreur "mauvaise requête" ou "socket occupé" retournée par les appels de méthodes). Vous devez toujours initier des objetsresty.redisdans des variables locales de fonction ou dans la tablengx.ctx. Ces endroits ont tous leurs propres copies de données pour chaque requête.
Cloner la dernière version, en supposant v0.29
wget https://github.com/openresty/lua-resty-redis/archive/refs/tags/v0.29.tar.gz
Extraire
tar -xvzf v0.29.tar.gz
aller dans le répertoire
cd lua-resty-redis-0.29
export LUA_LIB_DIR=/usr/local/openresty/site/lualib
Compiler et installer
make install
Maintenant, le chemin compilé sera affiché
/usr/local/lib/lua/resty = lua_package_path dans la conf nginx
```
Voir aussi
- le module ngx_lua : https://github.com/openresty/lua-nginx-module/#readme
- la spécification du protocole câblé redis : http://redis.io/topics/protocol
- la bibliothèque lua-resty-memcached
- la bibliothèque lua-resty-mysql
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-redis.