Redis além do cache: filas, pub/sub e rate limiting na prática
1. Redis como plataforma de mensageria: introdução aos padrões além do cache
1.1. Do cache à fila: por que Redis é mais que um armazenamento chave-valor
Quando pensamos em Redis, a primeira associação geralmente é cache de banco de dados ou sessões de usuário. No entanto, o Redis é uma plataforma de dados versátil que oferece estruturas de dados ricas e operações atômicas, tornando-o ideal para cenários de mensageria e controle de acesso. Sua capacidade de operar em memória com latência de microssegundos o diferencia de sistemas de fila tradicionais como RabbitMQ ou Kafka em casos onde a simplicidade e velocidade são prioridades.
1.2. Estruturas de dados essenciais para filas, pub/sub e controle de taxa
Para implementar filas, utilizamos Listas (LPUSH/RPOP) e Streams (XADD/XREADGROUP). Para comunicação em tempo real, o Pub/Sub oferece canais com entrega best-effort. Para rate limiting, combinamos Strings com expiração (INCR/EXPIRE) e Sorted Sets para janelas deslizantes. Scripts Lua garantem atomicidade em operações complexas.
1.3. Casos de uso reais: processamento assíncrono, notificações e proteção de APIs
- Filas: processamento de e-mails, geração de relatórios, upload de arquivos
- Pub/Sub: notificações em tempo real, feeds de eventos, atualização de dashboards
- Rate limiting: proteção de endpoints públicos, controle de uso por plano de assinatura
2. Filas com Redis: implementando filas de tarefas com Listas e Streams
2.1. Operações LPUSH/RPOP e BRPOP para filas simples e bloqueantes
A abordagem mais básica usa Listas como fila FIFO:
# Produtor: adiciona tarefa
LPUSH fila:tarefas "email:joao@exemplo.com"
# Consumidor: retira tarefa (bloqueante por 30 segundos)
BRPOP fila:tarefas 30
2.2. Redis Streams: grupos de consumidores, confirmação de mensagens e persistência
Streams oferecem recursos avançados como grupos de consumidores e confirmação explícita:
# Criar grupo de consumidores
XGROUP CREATE stream:eventos grupo-email $ MKSTREAM
# Produtor adiciona mensagem
XADD stream:eventos * tipo email destinatario joao@exemplo.com
# Consumidor lê e confirma
XREADGROUP GROUP grupo-email consumidor-1 COUNT 1 BLOCK 5000 STREAMS stream:eventos >
XACK stream:eventos grupo-email 1712345678-0
2.3. Exemplo prático: fila de envio de e-mails com retry e dead-letter queue
# Produtor
XADD fila:email * para joao@exemplo.com assunto "Bem-vindo" tentativas 0
# Consumidor com retry
local msg = redis.call('XREADGROUP', 'GROUP', 'grupo-email', 'worker-1', 'COUNT', 1, 'BLOCK', 5000, 'STREAMS', 'fila:email', '>')
if msg then
local id = msg[1][2][1][1]
local data = msg[1][2][1][2]
local tentativas = tonumber(data[6]) + 1
if tentativas > 3 then
redis.call('XADD', 'fila:email:dead-letter', '*', 'original_id', id, 'motivo', 'max_retries')
redis.call('XACK', 'fila:email', 'grupo-email', id)
else
-- reencaminhar com tentativa incrementada
redis.call('XADD', 'fila:email', '*', 'para', data[2], 'assunto', data[4], 'tentativas', tentativas)
redis.call('XACK', 'fila:email', 'grupo-email', id)
end
end
3. Pub/Sub no Redis: comunicação em tempo real entre serviços
3.1. Modelo publish-subscribe: canais, subscribers e mensagens voláteis
Pub/Sub segue o padrão fire-and-forget. Mensagens não são persistidas:
# Subscriber (terminal 1)
SUBSCRIBE canal:notificacoes
# Publisher (terminal 2)
PUBLISH canal:notificacoes "Novo usuário registrado: joao@exemplo.com"
3.2. Diferenças entre Pub/Sub e Streams: quando usar cada um
| Característica | Pub/Sub | Streams |
|---|---|---|
| Persistência | Não | Sim (configurável) |
| Grupos de consumidores | Não | Sim |
| Confirmação de entrega | Não | Sim (XACK) |
| Reprodução de mensagens | Não | Sim (range queries) |
| Ideal para | Notificações em tempo real, broadcasts | Filas confiáveis, processamento assíncrono |
3.3. Exemplo prático: notificações de eventos em um sistema de chat
# Servidor publica evento de chat
PUBLISH chat:sala-geral '{"usuario":"joao","mensagem":"Olá todos!","timestamp":1712345678}'
# Cliente subscribe
SUBSCRIBE chat:sala-geral
# Para múltiplas salas, use padrão PSUBSCRIBE
PSUBSCRIBE chat:*
4. Rate limiting na prática: controlando acesso com Redis
4.1. Algoritmos clássicos: Token Bucket, Leaky Bucket e Sliding Window
Sliding Window Log é o mais preciso e fácil de implementar com Redis:
# Janela deslizante de 60 segundos, máximo 10 requisições
-- Script Lua (rate_limit.lua)
local key = KEYS[1]
local max_requests = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < max_requests then
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1
else
return 0
end
4.2. Implementação com INCR, EXPIRE e scripts Lua (EVAL)
# Rate limiting simples por IP (10 requisições por minuto)
local key = "rate:ip:" .. ARGV[1]
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, 60)
end
if current > 10 then
return 0 -- bloqueado
end
return 1 -- permitido
4.3. Exemplo prático: limitador de requisições por IP e por usuário autenticado
# Combinação de limites: por IP (100/min) e por usuário (1000/min)
EVAL "
local ip_key = 'rate:ip:' .. ARGV[1]
local user_key = 'rate:user:' .. ARGV[2]
local ip_limit = 100
local user_limit = 1000
local window = 60
local ip_count = redis.call('INCR', ip_key)
if ip_count == 1 then redis.call('EXPIRE', ip_key, window) end
if ip_count > ip_limit then return 0 end
local user_count = redis.call('INCR', user_key)
if user_count == 1 then redis.call('EXPIRE', user_key, window) end
if user_count > user_limit then return 0 end
return 1
" 0 "192.168.1.1" "usuario_123"
5. Padrões avançados: combinando filas, pub/sub e rate limiting
5.1. Pipeline de processamento: fila recebe tarefa, pub/sub notifica workers
# Produtor
XADD fila:processamento * tipo imagem usuario_id 123
PUBLISH canal:novas-tarefas "nova_tarefa"
# Worker
SUBSCRIBE canal:novas-tarefas
-- ao receber notificação, consome da fila
XREADGROUP GROUP grupo-workers worker-1 COUNT 1 BLOCK 0 STREAMS fila:processamento >
5.2. Rate limiting em filas: controle de throughput para consumidores
# Worker verifica rate limit antes de processar
local rate_key = "rate:worker:" .. ARGV[1]
local current = redis.call('INCR', rate_key)
if current == 1 then redis.call('EXPIRE', rate_key, 1) end
if current > 10 then
return 0 -- aguardar próximo segundo
end
return 1
5.3. Exemplo integrado: sistema de upload e processamento de imagens com backpressure
# Upload: verifica rate limit, enfileira e notifica
EVAL "
local user_rate = 'rate:upload:' .. ARGV[1]
local uploads = redis.call('INCR', user_rate)
if uploads == 1 then redis.call('EXPIRE', user_rate, 60) end
if uploads > 5 then return 0 end
redis.call('XADD', 'fila:imagens', '*', 'usuario', ARGV[1], 'arquivo', ARGV[2])
redis.call('PUBLISH', 'canal:novas-imagens', ARGV[2])
return 1
" 0 "usuario_123" "foto_perfil.jpg"
# Worker com backpressure: só processa se fila < 100
local fila_size = redis.call('XLEN', 'fila:imagens')
if fila_size < 100 then
-- processa imagem
end
6. Boas práticas e armadilhas comuns
6.1. Configurações de memória, persistência (RDB/AOF) e replicação
Para filas e mensagens críticas, configure AOF com fsync everysec. Use maxmemory-policy allkeys-lru para evitar estouro de memória. Em produção, utilize Redis Sentinel ou Cluster para alta disponibilidade.
6.2. Evitando perda de mensagens em filas e Pub/Sub
- Filas: sempre use Streams com XACK para mensagens críticas
- Pub/Sub: não use para dados que não podem ser perdidos. Considere usar Streams com XREADGROUP como alternativa confiável
- Rate limiting: configure TTL adequado para evitar acumulo de chaves expiradas
6.3. Monitoramento de latência, chaves expiradas e contenção de recursos
# Monitorar latência do Redis
redis-cli --latency -h localhost -p 6379
# Verificar chaves expiradas por segundo
INFO stats | grep expires
# Monitorar filas
LLEN fila:tarefas
XLEN stream:eventos
Referências
- Redis Documentation: Streams — Documentação oficial sobre Redis Streams, incluindo grupos de consumidores e confirmação de mensagens
- Redis Pub/Sub Explained — Guia completo do modelo publish-subscribe no Redis
- Rate Limiting with Redis: Sliding Window Algorithm — Artigo técnico da Redis sobre implementação de rate limiting com janela deslizante
- Redis Lua Scripting — Documentação oficial sobre scripts Lua para operações atômicas no Redis
- Redis as a Message Queue: Best Practices — Blog da Redis sobre boas práticas para usar Redis como fila de mensagens
- Mastering Redis: Streams vs Pub/Sub — Comparação detalhada entre Streams e Pub/Sub para escolha do padrão adequado