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