- Introduction
Redis est un système de stockage clé-valeur. Similaire à Memcached, il supporte davantage de types de valeurs, notamment les chaînes (string), listes (list), ensembles (set), ensembles triés (sorted set) et hachages (hash). Ces types de données prennent en charge des opérations telles que push/pop, add/remove, intersection, union, différence, ainsi que des opérations plus riches, toutes atomiques. De plus, Redis propose différentes méthodes de tri. Comme Memcached, les données sont stockées en mémoire pour des performances optimales. Contrairement à Memcached, Redis écrit périodiquement les données mises à jour sur le disque ou enregistre les opérations de modification dans un fichier journal, et permet la synchronisation maître-esclave.
Avantages de Redis
- Rapidité : les données sont en mémoire, les opérations de recherche et de modification sont en O(1).
- Types de données riches : string, list, set, sorted set, hash.
- Transactions : les opérations sont atomiques (tout ou rien).
- Fonctionnalités avancées : cache, messages, expiration automatique des clés.
Avantages de Redis par rapport à Memcached
- Memcached ne stocke que des chaînes simples, Redis offre des types de données plus riches.
- Redis est généralement plus rapide.
- Redis permet la persistance des données.
Problèmes de performence et solutions
- Le maître ne doit pas effectuer de persistance lourde (snapshot RDB, journal AOF).
- Un esclave peut activer la persistance AOF avec une sauvegarde toutes les secondes.
- Le maître et l'esclave doivent être sur le même réseau local.
- Éviter d'ajouter des esclaves à un maître très chargé.
- Utiliser une structure en chaîne plutôt qu'en graphe pour la réplication.
Stratégies d'éviction de données
- volatile-lru
- volatile-ttl
- volatile-random
- allkeys-lru
- allkeys-random
- no-eviction
Types de données supportés
redis = {
k1: '123', # string
k2: [1,2,3,4], # list/array
k3: {1,2,3,4}, # set
k4: {name: lqz, age: 12}, # hash/dictionnaire
k5: {('lqz',18), ('egon',33)} # sorted set (zset)
}
Caractéristiques
- Persistance possible
- Mono-thread, mono-processus
- Installation et utilisation de Redis
Installation sous Linux
wget http://download.redis.io/releases/redis-3.0.6.tar.gz
tar xzf redis-3.0.6.tar.gz
cd redis-3.0.6
make
Démarrage du serveur
src/redis-server
Démarrage du client
src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
Pour Windows, voir la documentation officielle.
- Python et Redis : installation et types supportés
Installation du module redis
pip3 install redis
- Connexion simple avec Python
La bibliothèque redis-py propose deux classes : Redis et StrictRedis. StrictRedis implémente presque toutes les commandes officielles avec la syntaxe officielle, tandis que Redis est une sous-classe assurant la rétrocompatibilité.
import redis
client = redis.Redis(host='127.0.0.1', port=6379)
client.set('foo', 'Bar')
print(client.get('foo'))
- Connexion avec pool de connexions
Redis-py utilise un pool de connexions pour gérer toutes les connexions à un serveur Redis, évitant ainsi le surcoût de création/libération de connexions. Par défaut, chaque instance Redis possède son propre pool. Vous pouvez créer un pool partagé.
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
client = redis.Redis(connection_pool=pool)
client.set('foo', 'Bar')
print(client.get('foo'))
- Opérations sur les chaînes (String)
Dans Redis, chaque chaîne est associée à une clé unique.
set(name, value, ex=None, px=None, nx=False, xx=False)
Définit une valeur. Crée si absente, modifie si présente. ex : expiration en secondes, px : en millisecondes, nx : ne crée que si la clé n'existe pas, xx : ne modifie que si la clé existe.
client.set('key', 'value', ex=60, nx=True)
setnx(name, value)
Équivalent à set(nx=True).
setex(name, value, time)
Définit une valeur avec expiration en secondes (ou objet timedelta).
psetex(name, time_ms, value)
Idem avec expiration en millisecondes.
mset(*args, **kwargs)
Définit plusieurs clés en une seule commande.
client.mset(k1='v1', k2='v2')
# ou
client.mset({'k1': 'v1', 'k2': 'v2'})
get(name)
Retourne la valeur.
mget(keys, *args)
Récupère plusieurs valeurs.
client.mget('k1', 'k2')
# ou
client.mget(['k3', 'k4'])
getset(name, value)
Définit une nouvelle valeur et retourne l'ancienne.
getrange(key, start, end)
Retourne une sous-chaîne (basée sur les octets, pas les caractères). Exemple : getrange('clé', 0, 2) pour les 3 premiers octets.
setrange(name, offset, value)
Remplace la chaîne à partir d'un offset (en octets). Les caractères non latins (comme le chinois) occupent 3 octets.
setbit(name, offset, value)
Modifie un bit spécifique de la représentation binaire de la valeur.
# "foo" en binaire : 01100110 01101111 01101111
# setbit('n1', 7, 1) donne : 01100111 01101111 01101111 => "goo"
client.setbit('n1', 7, 1)
getbit(name, offset)
Retourne la valeur du bit à l'offfset donné (0 ou 1).
bitcount(key, start=None, end=None)
Compte le nombre de bits à 1 dans la représentation binaire.
bitop(operation, dest, *keys)
Effectue une opération bit à bit (AND, OR, NOT, XOR) sur plusieurs clés et stocke le résultat dans dest.
client.bitop("AND", 'resultat', 'cle1', 'cle2')
strlen(name)
Retourne la longueur en octets de la valeur (les caractères chinois comptent 3 octets).
incr(name, amount=1)
Incémente la valeur. Crée la clé avec amount si elle n'existe pas.
incrbyfloat(name, amount=1.0)
Incémente avec un flottant.
decr(name, amount=1)
Décrémente la valeur.
append(key, value)
Ajoute la chaîne à la fin de la valeur existante.
- Opérations sur les hachages (Hash)
Les hachages stockent des paires clé-valeur sous une clé principale.
hset(name, key, value)
Définit une paire clé-valeur dans le hachage. hsetnx crée seulement si la clé n'existe pas.
client.hset('utilisateur', 'nom', 'Alice')
hmset(name, mapping)
Définit plusieurs paires en une fois.
client.hmset('utilisateur', {'nom': 'Bob', 'age': 25})
hget(name, key)
Récupère la valeur d'une clé dans le hachage.
hmget(name, keys, *args)
Récupère plusieurs clés.
client.hmget('utilisateur', ['nom', 'age'])
# ou
client.hmget('utilisateur', 'nom', 'age')
hgetall(name)
Retourne toutes les paires clé-valeur du hachage (sous forme de dictionnaire). Attention : peut consommer beaucoup de mémoire pour les grands hachages.
hlen(name)
Nombre de paires dans le hachage.
hkeys(name)
Liste de toutes les clés du hachage.
hvals(name)
Liste de toutes les valeurs.
hexists(name, key)
Vérifie si une clé existe dans le hachage.
hdel(name, *keys)
Supprime une ou plusieurs clés du hachage.
hincrby(name, key, amount=1)
Incémente la valeur d'une clé (entier).
hincrbyfloat(name, key, amount=1.0)
Incémente avec un flottant.
hscan(name, cursor=0, match=None, count=None)
Itération incrémentale sur le hachage. Retourne un nouveau curseur et une partie des données. Utiliser en boucle jusqu'à ce que le curseur soit 0.
hscan_iter(name, match=None, count=None)
Générateur qui encapsule hscan pour une itération facile.
for cle in client.hscan_iter('mon_hash'):
print(cle)
- Opérations sur les listes (List)
Les listes sont des séquences ordonnées d'éléments.
lpush(name, *values)
Ajoute des éléments à gauche de la liste. L'ordre final est l'inverse de l'ordre d'insertion.
client.lpush('ma_liste', 11, 22, 33) # stocké : 33, 22, 11
rpush(name, *values)
Ajoute à droite.
lpushx(name, value)
Ajoute à gauche seulement si la clé existe déjà. rpushx pour la droite.
llen(name)
Longueur de la liste.
linsert(name, where, refvalue, value)
Insère une valeur avant ou après la première occurrence de refvalue. where peut être "BEFORE" ou "AFTER".
lset(name, index, value)
Remplace l'élément à l'index donné.
lrem(name, value, num)
Supprime les occurrences de value. num=0 supprime toutes ; num>0 supprime de gauche à droite ; num<0 supprime de droite à gauche.
lpop(name)
Supprime et retourne l'élément le plus à gauche. rpop pour la droite.
lindex(name, index)
Retourne l'élément à l'index donné.
lrange(name, start, end)
Retourne une tranche de la liste (les indices sont inclusifs).
ltrim(name, start, end)
Conserve uniquement les éléments entre start et end (inclus).
rpoplpush(src, dst)
Déplace le dernier élément de src en tête de dst.
blpop(keys, timeout)
Version bloquante de lpop. Attend jusqu'à timeout secondes qu'un élément soit disponible. Utilisé pour les files d'attente.
brpoplpush(src, dst, timeout=0)
Version bloquante de rpoplpush.
Itération incrémentale sur une liste
Il n'existe pas de commande intégrée. Voici un générateur personnalisé.
import redis
client = redis.Redis(host='127.0.0.1', port=6379)
# client.lpush('test', *[1,2,3,4,45,5,6,7,7,8,43,5,6,768,89,9,65,4,23,54,6757,8,68])
def iterer_liste(nom, taille=2):
index = 0
while True:
data = client.lrange(nom, index, index + taille - 1)
if not data:
break
index += taille
for elem in data:
yield elem
for elem in iterer_liste('test', 5):
print('---')
print(elem)
- Opérations sur les ensembles (Set)
Les ensembles sont des collections non ordonnées d'éléments uniques.
sadd(name, *values)
Ajoute un ou plusieurs éléments à l'ensemble.
scard(name)
Nombre d'éléments dans l'ensemble.
sdiff(keys, *args)
Différence entre le premier ensemble et les autres.
sdiffstore(dest, keys, *args)
Stocke la différence dans dest.
sinter(keys, *args)
Intersection de plusieurs ensembles.
sinterstore(dest, keys, *args)
Stocke l'intersection dans dest.
sismember(name, value)
Teste si value est dans l'ensemble.
smembers(name)
Retourne tous les éléments.
smove(src, dst, value)
Déplace un élément d'un ensemble vers un autre.
spop(name)
Supprime et retourne un élément aléatoire (côté droit).
srandmember(name, numbers)
Retourne numbers éléments aléatoires sans les supprimer.
srem(name, *values)
Supprime les valeurs spécifiées.
sunion(keys, *args)
Union de plusieurs ensembles.
sunionstore(dest, keys, *args)
Stocke l'union dans dest.
sscan et sscan_iter
Itération incrémentale, identique à hscan.
Ensembles triés (Sorted Set)
Chaque élément possède un score utilisé pour le tri.
zadd(name, *args, **kwargs)
client.zadd('zz', 'n1', 1, 'n2', 2) # ou client.zadd('zz', n1=11, n2=22)
zcard(name)
Nombre d'éléments.
zcount(name, min, max)
Nombre d'éléments avec un score entre min et max.
zincrby(name, value, amount)
Augmente le score de l'élément.
zrange(name, start, end, desc=False, withscores=False, score_cast_func=float)
Retourne les éléments par index (score croissant). zrevrange pour ordre décroissant.
zrank(name, value)
Rang (index) d'un élément (le plus petit score = rang 0). zrevrank pour l'ordre inverse.
zrangebylex(name, min, max, start=None, num=None)
Retourne des éléments par ordre lexicographique (tous les scores doivent être égaux).
zrem(name, *values)
Supprime les éléments spécifiés.
zremrangebyrank(name, min, max)
Supprime par plage de rangs.
zremrangebyscore(name, min, max)
Supprime par plage de scores.
zscore(name, value)
Retourne le score d'un élément.
zinterstore(dest, keys, aggregate=None)
Intersection de plusieurs ensembles triés. aggregate peut être SUM, MIN, MAX.
zunionstore(dest, keys, aggregate=None)
Union de plusieurs ensembles triés.
zscan et zscan_iter
Itération incrémentale.
- Autres opérations
delete(*names)
Supprime une ou plusieurs clés.
exists(name)
Teste l'existence d'une clé.
keys(pattern='*')
Recherche des clés par motif (Attention : coûteux sur de grands ensembles).
# KEYS * => toutes les clés
# KEYS h?llo => hello, hallo, hxllo
# KEYS h*llo => hllo, heeeeello
# KEYS h[ae]llo => hello, hallo
expire(name, time)
Définit un temps d'expiration (en secondes).
rename(src, dst)
Renomme une clé.
move(name, db)
Déplace une clé vers une autre base de données Redis.
randomkey()
Retourne une clé aléatoire.
type(name)
Retourne le type de la valeur (string, list, set, zset, hash).
scan et scan_iter
Itération incrémentale sur toutes les clés.
- Pipeline
Par défaut, chaque commande crée une nouvelle connexion. Un pipeline permet d'envoyer plusieurs commandes en une seule requête, ce qui réduit la latence. Par défaut, un pipeline est atomique (transaction).
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
client = redis.Redis(connection_pool=pool)
# pipeline transactionnel
pipe = client.pipeline(transaction=True)
pipe.multi()
pipe.set('nom', 'Alice')
pipe.set('role', 'admin')
pipe.execute()
- Utilisation de Redis dans Django
Méthode 1 : Pool de connexions partagé
Créez un fichier redis_pool.py dans le dossier utils.
# utils/redis_pool.py
import redis
POOL = redis.ConnectionPool(
host='127.0.0.1',
port=6379,
password='1234',
max_connections=1000
)
Dans la vue :
from django.shortcuts import HttpResponse
from utils.redis_pool import POOL
import redis
def index(request):
client = redis.Redis(connection_pool=POOL)
client.hset('kkk', 'age', 18)
return HttpResponse('Valeur définie')
def order(request):
client = redis.Redis(connection_pool=POOL)
age = client.hget('kkk', 'age')
return HttpResponse('Valeur récupérée')
Méthode 2 : Avec django-redis
Installez le module :
pip3 install django-redis
Dans settings.py :
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
# "PASSWORD": "123",
}
}
}
Dans la vue :
from django_redis import get_redis_connection
def ma_vue(request):
conn = get_redis_connection('default')
data = conn.hgetall('xxx')
print(data)
- Configuration des sessions Django avec Redis
Par défaut, les sessions sont stockées en base de données. Pour les stocker dans Redis et améliorer les performances, deux méthodes existent.
Méthode 1 : Utiliser un backend Redis direct
pip install django-redis-sessions==0.5.6
Dans settings.py :
SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 2
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'
Méthode 2 : Utiliser le cache Redis pour les sessions
pip install django_redis
Dans settings.py :
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://172.16.179.142:6379/12",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"