Casos de uso de Redis: Caché, Colas y Limitación de Tasa
Has añadido Redis a tu stack, pero solo lo usas como un simple almacén de clave-valor. Redis ofrece estructuras de datos y comandos potentes que pueden resolver el caché, las colas y la limitación de tasa de forma elegante. Esta guía te muestra cómo implementar estos patrones de manera efectiva.
¿Por qué Redis para estos casos de uso?
Redis es un almacén de datos en memoria conocido por su velocidad y versatilidad. Soporta cadenas, hashes, listas, conjuntos, conjuntos ordenados y más. Sus operaciones atómicas y su expiración incorporada lo hacen ideal para caché, colas y limitación de tasa. Además, es fácil de integrar con cualquier lenguaje.
Caché con Redis
El caché es el caso de uso más común de Redis. Almacenas datos a los que se accede con frecuencia en Redis para reducir la carga de la base de datos y acelerar las respuestas.
Patrón básico de cache-aside
En el patrón cache-aside, tu aplicación comprueba primero Redis. Si los datos no están allí (fallo de caché), los obtiene de la base de datos y luego los almacena en Redis para la próxima vez.
import redis
import json
r = redis.Redis(host='localhost', port=6379, db=0)
def get_user(user_id):
cache_key = f'user:{user_id}'
cached = r.get(cache_key)
if cached:
return json.loads(cached)
user = fetch_user_from_db(user_id) # tu llamada a la BD
r.setex(cache_key, 3600, json.dumps(user)) # caché durante 1 hora
return user
Mejores prácticas:
- Establece siempre un tiempo de expiración (TTL) para evitar datos obsoletos.
- Usa nombres de clave significativos, como
user:123:profile. - Maneja la invalidación de caché: cuando los datos cambien, actualiza o elimina la entrada de caché.
- Considera usar hashes de Redis para almacenar múltiples campos de un objeto y ahorrar memoria.
Patrones avanzados de caché
Para aplicaciones de alto tráfico, considera estos patrones:
- Write-through: Actualiza la caché y la base de datos juntas.
- Write-behind: Actualiza primero la caché y luego actualiza la base de datos de forma asíncrona.
- Refresh-ahead: Refresca proactivamente las entradas de caché antes de que expiren.
Colas con Redis
Las listas y los streams de Redis son perfectos para construir colas de mensajes simples. Usa listas para colas de trabajos básicas y streams para procesamiento de eventos más complejo.
Cola de trabajos simple con listas
Usa LPUSH para añadir trabajos y BRPOP para procesarlos. BRPOP se bloquea hasta que haya un trabajo disponible, lo que lo hace eficiente para los workers.
# Productor
r.lpush('jobs', json.dumps({'task': 'send_email', 'to': 'user@example.com'}))
# Consumidor (worker)
while True:
_, job_data = r.brpop('jobs', timeout=30)
job = json.loads(job_data)
process_job(job)
Mejores prácticas:
- Usa listas separadas para diferentes tipos de trabajos o prioridades.
- Implementa una cola de mensajes muertos para trabajos fallidos.
- Usa
BRPOPLPUSH(oBLMOVEen Redis 6.2+) para mover trabajos atómicamente a una lista de procesamiento, asegurando la fiabilidad. - Monitorea la longitud de la cola con
LLENpara escalar los workers.
Redis Streams para event sourcing
Redis Streams (introducido en Redis 5.0) proporciona una cola más robusta con grupos de consumidores, confirmaciones y reproducción. Usa XADD para añadir eventos y XREADGROUP para consumirlos.
# Añadir evento
r.xadd('events', {'type': 'order_placed', 'order_id': '123'})
# Grupo de consumidores
r.xgroup_create('events', 'order_processors', id='0', mkstream=True)
while True:
events = r.xreadgroup('order_processors', 'worker1', {'events': '>'}, count=10, block=5000)
for event in events:
process_event(event)
r.xack('events', 'order_processors', event.id)
Limitación de tasa con Redis
La limitación de tasa protege tus APIs del abuso y asegura un uso justo. Las operaciones atómicas de Redis lo hacen ideal para contar solicitudes.
Limitación de tasa de ventana fija
Incrementa un contador para cada usuario y establece una expiración. Si el conteo excede el límite, bloquea la solicitud.
def is_allowed(user_id, limit=100, window=60):
key = f'rate_limit:{user_id}'
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current