--- categories: databases nosql title: Howto Redis ... * Documentation: * Rôle Ansible : [Redis](https://redis.io/) est un serveur noSQL clé-valeur très stable (écrit en 2009). Une valeur peut être une chaîne de caractères, un tableau, une liste, etc. Redis tourne en mémoire, et si besoin sauvegarde de temps en temps ses données sur le disque. ## Installation Les versions suivantes sont disponibles selon la version de Debian : * Debian 12 (Bookworm) : Redis 7.0.15 * Debian 11 (Bullseye) : Redis 6.0.16 * Debian 10 (Buster) : Redis 5.0.14 * Debian 9 (Stretch) : Redis 3.2.6 ~~~ # apt install redis-server redis-tools $ /usr/bin/redis-server -v Redis server v=7.0.15 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=8fef3e995a542118 # systemctl status redis ● redis-server.service - Advanced key-value store Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; preset: enabled) Active: active (running) since Thu 2024-02-15 09:55:26 CET; 6 days ago Docs: http://redis.io/documentation, man:redis-server(1) Main PID: 192625 (redis-server) Status: "Ready to accept connections" Tasks: 5 (limit: 9514) Memory: 8.2M CPU: 12min 56.115s CGroup: /system.slice/redis-server.service └─192625 "/usr/bin/redis-server 127.0.0.1:6379" ~~~ ## Configuration La configuration principale se fait dans `/etc/redis/redis.conf` dont voici quelques options : ~~~ daemonize yes pidfile /var/run/redis.pid unixsocket /var/run/redis/redis.sock bind 127.0.0.1 port 6379 timeout 300 loglevel notice logfile /var/log/redis/redis-server.log databases 16 save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb dir /var/lib/redis #requirepass maxclients 4096 maxmemory 0 ~~~ **Note importante**: la valeur de `maxmemory` indique la quantité maximale de mémoire utilisable, au delà de laquelle Redis applique une [politique spécifique](#politique-sur-la-mémoire). Par défaut il s'agit de `noeviction`. Voir plus bas pour plus de détails. Pour choisir la bonne valeur de `maxmemory`, on peut s'aider de la commande Redis `INFO MEMORY` (Redis < 4) ou `MEMORY STATS` (Redis >= 4). ### Configuration à chaud La plupart des options sont reconfigurables à chaud en mode CLI via la commande `CONFIG SET`. La liste des arguments peut être listée : ~~~ # redis-cli redis> CONFIG GET * 1) "dir" 2) "/var/lib/redis" 3) "dbfilename" 4) "dump.rdb" 5) "requirepass" 6) (nil) 7) "masterauth" 8) (nil) 9) "maxmemory" 10) "0" ~~~ Par exemple, pour passer le paramètre `maxmemory` à 1G : ~~~ # redis-cli redis> AUTH OK redis> CONFIG SET maxmemory 1024000000 OK ~~~ Attention : Si on veut que la configuration soit persistante, il faut également mettre à jour le paramètre dans `/etc/redis/redis.conf`. ### bind Pour écouter sur plusieurs interfaces : ~~~ bind 192.0.32.42 127.0.0.1 ~~~ **Attention** : si on fait un `bind` sur plusieurs IP, il faut que la première de la liste soit l'IP publique du serveur. Par exemple : `bind 192.0.32.42 127.0.0.1` ### Réglage du noyau Il est recommandé, surtout pour les grosses instances redis d'activer l'overcommit au niveau du Kernel Linux. Si ce n'est pas le cas, ça peut empêcher Redis de faire ses sauvegardes. ~~~ # cat /etc/sysctl.d/evolinux-redis.conf vm.overcommit_memory=1 # sysctl vm.overcommit_memory=1 ~~~ ### Politique sur la mémoire Il est possible de démarrer Redis sans limite de mémoire. Il va donc occuper autant de RAM que nécessaire. Si les données stockées croissent suffisamment, ça peut conduire à une saturation de la RAM disponible et une intervention du noyau qui pourra tuer des processus (_Out Of Memory Kill_). On peut aussi définir une quantité maximale de mémoire utilisable par Redis (paramètre `maxmemory`, en octets, fixé à 100 Mo par défaut dans nos installations). Il faut indiquer à Redis quoi faire lorsque celle-ci est épuisée (paramètre `maxmemory-policy`). Il y a plusieurs options, et le choix dépend du type de données que l'on stocke dans Redis et de la manière on les utilise. le mieux est de se référer à la [documentation officielle](https://redis.io/docs/reference/eviction/), mais voici les politiques d'éviction disponibles : * `noeviction`: Ne sauvegarde pas les nouvelles valeurs lorsque la limite est atteinte. En cas de réplication, ça s'applique au primaire. * `allkeys-lru`: Conserve les clés les plus récemment utilisées et supprime les moins récemment utilisées (LRU = « least recently used »). * `allkeys-lfu`: Conserve les clés les plus fréquemment utilisées et supprime les moins fréquemment utilisées (LFU = « least frequently used »). * `volatile-lru`: Supprime les clés ayant une expiration définie les moins récemment utilisées. * `volatile-lfu`: Supprime les clés ayant une expiration définie les moins fréquemment utilisées. * `allkeys-random`: Supprime au hasard des clés. * `volatile-random`: Supprime au hasard des clés ayant une expiration définie. * `volatile-ttl`: Supprime des clés ayant une expiration définie, par ordre du plus petit TTL restant (TTL = « Time To Live »). Lorsque Redis est utilisé exclusivement pour du cache, il est conseillé de choisir `allkeys-lfu` (ou `allkeys-lru` si la version de Redis ne supporte pas encore `allkeys-lfu`). Lorsque les données stockées dans Redis ne doivent surtout pas risquer de disparaître, il faut choisir `noeviction` et gérer applicativement les éventuelles erreurs d'écriture. ## Utilisation En mode CLI : ~~~ $ redis-cli redis 127.0.0.1:6379> redis> set foo 3 OK redis> get blabla (nil) redis> get foo 3 redis> keys * 1) "foo" redis> mset un 1 deux 2 trois 3 quatre 4 OK redis> keys * 1) "un" 2) "foo" 3) "deux" 4) "trois" 5) "quatre" redis> *keys *r* 1) "four" 2) "three" redis> get four "4" redis> del "trois" "quatre" (integer) 2 ~~~ On peut se connecter avec la socket si elle existe : ~~~ $ redis-cli -s /path/to/redis.sock ~~~ Mais aussi en réseau (sans authentification, attention) : ~~~ $ telnet 127.0.0.1 6379 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. get foo $1 3 quit Connection closed by foreign host. ~~~ En PHP, il existe différentes bibliothèques. Nous utilisons principalement et nous utilisons un fork pour avoir un packaging Debian propre : ~~~ $ git clone $ cd phpredis $ tar --exclude debian --exclude .git -cvzf ../php5-redis_0.1~git20120519.orig.tar.gz . $ git-buildpackage -us -uc ~~~ Sur Debian Stretch, on peut aussi installer le paquet ~php-redis~. ### Sessions PHP avec Redis Redis peut notamment être utilisé pour stocker les sessions [PHP](HowtoPHP). Par exemple : ~~~ session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379?auth=" ~~~ ## Instances Redis Il est possible de faire fonctionner plusieurs instances de Redis sur un serveur ; chacune avec ses propres données, sa propre configuration et son propre utilisateur. ### Template systemd Créer le template d'unité [systemd](HowtoSystemd) suivant dans `/etc/systemd/system/redis-server@.service` : ~~~{.ini} [Unit] Description=Advanced key-value store After=network.target [Service] Type=forking ExecStart=/usr/bin/redis-server /etc/redis-%i/redis.conf PIDFile=/var/run/redis-%i/redis-server.pid TimeoutStopSec=0 Restart=always User=redis-%i Group=redis-%i RuntimeDirectory=redis-%i ExecStartPre=-/bin/run-parts --verbose /etc/redis-%i/redis-server.pre-up.d ExecStartPost=-/bin/run-parts --verbose /etc/redis-%i/redis-server.post-up.d ExecStop=-/bin/run-parts --verbose /etc/redis-%i/redis-server.pre-down.d ExecStop=/bin/kill -s TERM $MAINPID ExecStopPost=-/bin/run-parts --verbose /etc/redis-%i/redis-server.post-down.d UMask=007 PrivateTmp=yes LimitNOFILE=65535 PrivateDevices=yes ProtectHome=yes ReadOnlyDirectories=/ ReadWriteDirectories=-/var/lib/redis-%i ReadWriteDirectories=-/var/log/redis-%i ReadWriteDirectories=-/var/run/redis-%i CapabilityBoundingSet=~CAP_SYS_PTRACE # redis-server writes its own config file when in cluster mode so we allow # writing there (NB. ProtectSystem=true over ProtectSystem=full) ProtectSystem=true ReadWriteDirectories=-/etc/redis-%i [Install] WantedBy=multi-user.target ~~~ Recharger la configuration systemd : ~~~ systemctl daemon-reload ~~~ ### Installation Créer un utilisateur système pour l’instance : ~~~ useradd --system --home-dir /var/lib/redis/instance1 redis-instance1 ~~~ Créer ensuite le *datadir* et le dossier de log : ~~~ mkdir -m 0750 -p /var/lib/redis/instance1 /var/log/redis/instance1 chown redis-instance1: /var/lib/redis/instance1 /var/log/redis/instance1 ~~~ Créer ensuite le fichier de configuration suivant dans `/etc/redis/redis-instance1.conf` : ~~~ daemonize yes port 0 # Listen only on unix socket defined by systemd unit unixsocketperm 770 # Unix socket only accessible by user and group of instance1 timeout 300 loglevel notice logfile /var/log/redis/instance1/redis-server.log databases 16 save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb dir /var/lib/redis/instance1 #requirepass maxclients 128 maxmemory 104857600 ~~~ ### Administration L'instance Redis « instance1 » est maintenant administrable via : ~~~ systemctl start redis-server@instance1 systemctl stop redis-server@instance1 systemctl restart redis-server@instance1 systemctl status redis-server@instance1 systemctl enable redis-server@instance1 systemctl disable redis-server@instance1 ~~~ Pour s'y connecter en CLI : ~~~ redis-cli -p redis-cli -s ~~~ ## Sauvegardes Par défaut, Redis sauvegarde de temps en temps ses données sur le disque (mode RDB persistance). Il suffit donc de copier le fichier */var/lib/redis/dump.rdb* pour sauvegarder une base Redis à chaud ! Pour restaurer les données, il suffira d'éteindre Redis, remettre en place le fichier RDB, et relancer Redis. ## Réplication interne Note: le terme « slave » a été remplacé par « replica » dans la plupart des commandes et des messages, mais il peut tout de même apparaître à certains endroits. À priori les commandes basées sur ce terme sont toujours utilisables (pour rétro-compatibilité). Côté « replica » il suffit de mettre en place la configuration : ~~~ replicaof 6379 ~~~ Une fois le démon relancé on peut vérifier l'état de la réplication de la façon suivante : ~~~ # redis-cli -h X.X.X.X -p 6379 info replication ~~~ On peut arrêter la réplication et passer un replica en master ~~~ # redis-cli -h X.X.X.X -p 6379 replicaof no one ~~~ ~~~ # redis-cli -h X.X.X.X -p 6379 info replication # Replication role:master connected_slaves:1 master_repl_offset:631177410 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:630128835 repl_backlog_histlen:1048576 ~~~ ### Tests On teste une écriture sur le master, est-ce répliqué sur le replica ? ~~~ redis [master] 127.0.0.1:6379> set key value redis [replica] 127.0.0.1:6379> get key ~~~ On devrait avoir « value ». Par ailleurs on pourra lire dans les logs du replica (`/var/log/redis/redis-server.log`) : ~~~ [26287] 06 Sep 15:04:04 * MASTER <-> REPLICA sync: receiving 34 bytes from master ~~~ Depuis redis 2.6 (2.4 en Wheezy), par défaut, le replica est en _read-only_, on peut le passer en _read-write_, en mettant ceci dans la configuration du replica : ~~~ replica-read-only off ~~~ ## Réplication avec Sentinel Sentinel permet de surveiller un ensemble d'instances avec réplication. Il repose sur plusieurs processus qui communiquent entre-eux afin d'aboutir à un consensus sur l'état des réplications et l'élection d'un master. C'est un processus séparé de Redis lui-même. Par exemple, une instance _master_ "redis1" et un _replica_ "redis2". En cas de panne de "redis1", les processus Sentinel vont s'accorder sur le fait que "redis2" devient le _master_. À son retour en ligne, "redis1" sera promu _replica_ de "redis2". Un seul ensemble de processus Sentinel peut surveiller un ou plusieurs ensemble de réplications Redis. ### Unité systemd Depuis Debian 9, Le paquet `redis-sentinel` fournit une unité systemd. Pour des versions précédentes, on peut créer l’unité systemd suivante dans `/etc/systemd/system/redis-sentinel.service` : ~~~{.ini} [Unit] Description=Advanced key-value store (monitoring) After=network.target [Service] Type=simple PIDFile=/run/redis/sentinel.pid ExecStart=/usr/bin/redis-sentinel /etc/redis/sentinel.conf --unixsocket /run/redis/sentinel.sock --pidfile /run/redis/sentinel.pid ExecStop=/usr/bin/redis-cli -s /run/redis/sentinel.sock shutdown Restart=always User=redis Group=redis [Install] WantedBy=multi-user.target ~~~ ### Configuration Créer ensuite le fichier de configuration suivant dans `/etc/redis/sentinel.conf` : ~~~ port 16379 dir "/tmp" sentinel monitor replication1 127.0.0.1 6379 1 sentinel down-after-milliseconds replication1 1000 sentinel config-epoch replication1 7 sentinel leader-epoch replication1 8 sentinel known-replica replication1 127.0.0.1 6380 sentinel current-epoch 8 ~~~ **Attention** : si on fait un `bind` sur plusieurs IP, il faut que la première de la liste soit l'IP publique du serveur. Par exemple : `bind 192.168.2.1 127.0.0.1` Sentinel doit pouvoir ecrire dans son fichier de configuration : ~~~ chown redis: /etc/redis/sentinel.conf ~~~ ### Utilisation Quelques commandes Sentinel utiles : ~~~ sentinel> info => infos complètes sentinel> sentinel masters => liste des groupes Redis connus sentinel> sentinel master => infos spécifique à un groupe sentinel> sentinel get-master-addr-by-name => info du master d'un groupe sentinel> sentinel replicas => liste des réplica d'un groupe sentinel> sentinel failover => force le failover d'un groupe ~~~ ## Load-balancing avec HAProxy Il est possible d'avoir un master et plusieurs replica derrière un proxy/load-balancer HAProxy (sur `172.19.3.100` dans l'exemple). ~~~.haproxy frontend fe_redis mode tcp option tcplog bind 127.0.0.1:6379 default_backend be_redis backend be_redis mode tcp balance first default-server maxconn 1024 check agent-check agent-port 16379 agent-inter 1s server srv1 172.20.3.200:6379 server srv2 172.20.3.201:6379 ~~~ Sur les serveurs avec Redis (`172.19.3.200` et `172.19.3.201` dans notre exemple), il faut installer le paquet `xinetd` puis créer une configuration `/etc/xinetd.d/haproxy-agent-redis-role` : ~~~ service haproxy-agent-redis-role { socket_type = stream bind = 172.19.3.200 port = 16379 protocol = tcp wait = no type = UNLISTED user = root server = /usr/share/scripts/haproxy-agent-redis-role.sh log_on_failure += USERID disable = no only_from = 172.19.3.100 } ~~~ On active ce service dans `/etc/services` : ~~~ haproxy-agent-redis-role 16379/tcp ~~~ Puis on redémarre `xinetd` : `systemctl restart xinetd` Enfin, on installe le script qui sert d'agent `/usr/share/scripts/haproxy-agent-redis-role.sh` : ~~~.bash #!/bin/sh set -u config_var() { variable=$1 file=$2 test -f "${file}" && grep -E "^${variable}\s+.+$" "${file}" | awk '{ print $2 }' | sed -e "s/^[\"']//" -e "s/[\"']$//" } get_role() { host=$(config_var "bind" "${conf_file}") port=$(config_var "port" "${conf_file}") pass=$(config_var "requirepass" "${conf_file}") if [ -n "${pass}" ]; then export REDISCLI_AUTH="${pass}" fi cmd="${redis_cli_bin} -h ${host} -p ${port} INFO REPLICATION" ${cmd} 2>/dev/null | grep -Eo "role:\w+" | cut -d ':' -f2 } get_status() { systemctl is-active "${1}" } redis_cli_bin=$(command -v redis-cli) if [ -z "${redis_cli_bin}" ]; then printf "%s\n" "down # missing redis" exit 1 fi if [ $# -lt 1 ]; then service="redis-server.service" conf_file="/etc/redis/redis.conf" else service="redis-server@${1}.service" conf_file="/etc/redis-${1}/redis.conf" fi if [ ! -r "${conf_file}" ]; then printf "%s\n" "down # missing config" exit 3 fi status=$(get_status "${service}") case "${status}" in active) printf "%s " "up" ;; inactive) printf "%s\n" "stopped # systemd:${status}" exit 1 ;; failed) printf "%s\n" "fail # systemd:${status}" exit 1 ;; *) printf "%s\n" "down # systemd:${status}" exit 1 ;; esac role=$(get_role "${conf_file}") case "${role}" in master) printf "%s" "ready # role:master" ;; slave) printf "%s" "maint # role:slave" ;; *) printf "%s" "maint # role:unknown" ;; esac printf "\n" exit 0 ~~~ ### Redis multi-instances Ce script est compatible avec la présence de multiples instances de Redis. Si on l'exécute avec un argument `foo` il cherchera une configuration `/etc/redis-foo/redis.conf` et un service systemd `redis-server@foo`. Il faudra alors déclarer autant de services xinetd que d'instances à surveiller : ~~~ haproxy-agent-redis-role-foo 16380/tcp haproxy-agent-redis-role-bar 16381/tcp ~~~ Et enfin, modifier les services pour ajouter le paramètre `server_args` : ~~~ service haproxy-agent-redis-role-foo { socket_type = stream bind = 172.19.3.200 port = 16380 protocol = tcp wait = no type = UNLISTED user = root server = /usr/share/scripts/haproxy-agent-redis-role.sh server_args = foo log_on_failure += USERID disable = no only_from = 172.19.3.100 } ~~~ ## Benchmarks ~~~ redis-benchmark -n 100000 =##### PING (inline)= 100000 requests completed in 1.88 seconds 50 parallel clients 3 bytes payload keep alive: 1 97.82% <= 1 milliseconds 99.80% <= 2 milliseconds 99.89% <= 3 milliseconds 99.93% <= 4 milliseconds 99.93% <= 5 milliseconds 100.00% <= 6 milliseconds 100.00% <= 6 milliseconds 53276.50 requests per second =##### PING= 100000 requests completed in 1.97 seconds 50 parallel clients 3 bytes payload keep alive: 1 93.58% <= 1 milliseconds 99.36% <= 2 milliseconds 99.61% <= 3 milliseconds 99.71% <= 4 milliseconds 99.81% <= 5 milliseconds 99.87% <= 6 milliseconds 99.88% <= 7 milliseconds 99.88% <= 8 milliseconds 99.92% <= 10 milliseconds 99.93% <= 11 milliseconds 99.93% <= 12 milliseconds 99.95% <= 13 milliseconds 100.00% <= 13 milliseconds 50761.42 requests per second =##### MSET (10 keys)= 100000 requests completed in 3.12 seconds 50 parallel clients 3 bytes payload keep alive: 1 19.71% <= 1 milliseconds 91.36% <= 2 milliseconds 98.93% <= 3 milliseconds 99.66% <= 4 milliseconds 99.75% <= 5 milliseconds 99.76% <= 8 milliseconds 99.77% <= 9 milliseconds 99.77% <= 10 milliseconds 99.80% <= 11 milliseconds 99.80% <= 14 milliseconds 99.85% <= 15 milliseconds 99.89% <= 22 milliseconds 99.89% <= 23 milliseconds 99.94% <= 24 milliseconds 99.95% <= 160 milliseconds 99.95% <= 208 milliseconds 99.95% <= 245 milliseconds 99.95% <= 246 milliseconds 100.00% <= 246 milliseconds 32020.49 requests per second =##### SET= 100000 requests completed in 1.86 seconds 50 parallel clients 3 bytes payload keep alive: 1 97.80% <= 1 milliseconds 99.78% <= 2 milliseconds 99.84% <= 3 milliseconds 99.87% <= 4 milliseconds 99.93% <= 5 milliseconds 99.95% <= 10 milliseconds 99.96% <= 11 milliseconds 100.00% <= 12 milliseconds 100.00% <= 12 milliseconds 53850.30 requests per second =##### GET= 100000 requests completed in 2.02 seconds 50 parallel clients 3 bytes payload keep alive: 1 91.26% <= 1 milliseconds 99.65% <= 2 milliseconds 99.86% <= 3 milliseconds 99.89% <= 4 milliseconds 99.92% <= 5 milliseconds 99.97% <= 7 milliseconds 100.00% <= 7 milliseconds 49407.12 requests per second [etc.] ~~~ ## Monitoring ### En live Voir des stats toutes les secondes : ~~~ $ redis-cli --stat ------- data ------ --------------------- load -------------------- - child - keys mem clients blocked requests connections 227947 1.25G 21 0 15259436851 (+0) 252707982 227948 1.25G 22 0 15259437470 (+619) 252708002 227951 1.25G 20 0 15259438412 (+942) 252708021 227951 1.25G 18 0 15259438787 (+375) 252708025 […] ~~~ Voir les requêtes que redis reçoit : ~~~ $ redis-cli monitor ~~~ ### Nagios Depuis *Debian Stretch*, un check redis a été ajouté dans le paquet `nagios-plugins-contrib`, permettant d'avoir un check plus sophistiqué qu'un simple check TCP. Il est capable de surveiller la réplication, la consommation de mémoire… tout en retournant diverses informations sur le status du serveur. Il faut penser à installer la lib redis pour perl, absente des dépendances. ~~~ # apt install libredis-perl nagios-plugins-contrib $ /usr/lib/nagios/plugins/check_redis -H 127.0.0.1 OK: REDIS 3.2.6 on 127.0.0.1:6379 has 42 databases (foo00,foo01,foo01...) with 179927 keys, up 17 hours 56 minutes ~~~ Sur *Debian Jessie*, il n'y a pas de check redis embarqué. A place, on peut faire un check Nagios « basique » consistant à initier une connexion TCP sur le socket unix du serveur Redis, et s'assurer qu'il répond bien : ~~~ $ /usr/lib/nagios/plugins/check_tcp -H /var/run/redis.pid ~~~ Il est toujours possible de récupérer le check avancé, pour l'utiliser sur *Jessie* : * * ### Munin Un plugin Munin existe pour grapher divers paramètres comme la mémoire utilisée par Redis, le nombre de clés utilisées, le nombre de requêtes par seconde, et le nombre de clients connectés. **TODO:** section obsolète, lien mort Installer le paquet suivant si ne n'est pas déjà fait : ~~~ apt install libswitch-perl ~~~ Puis suivre la procédure : ~~~ # mkdir -p /usr/local/share/munin/plugins/ # cd /usr/local/share/munin/plugins/ # wget "https://raw.githubusercontent.com/munin-monitoring/contrib/master/plugins/redis/redis_" # chmod -R 755 /usr/local/share/munin # cd /etc/munin/plugins/ # for module in connected_clients key_ratio keys_per_sec per_sec used_keys used_memory; do ln -s /usr/local/share/munin/plugins/redis_ redis_${module}; done ~~~ Si vous avez une authentification pour votre serveur Redis : ~~~ # cat /etc/munin/plugin-conf.d/munin-node [redis_*] env.password PASSWORD ~~~