Banco de Dados • 5 min de leitura

Redis além do cache: pub/sub e filas

A maioria dos desenvolvedores conhece o Redis pela função mais óbvia: cache. Você armazena um resultado caro de computar, define um TTL, e evita recalcular até expirar. É simples, funciona bem, e para muitos projetos é tudo que precisam. Mas o Redis tem uma arquitetura de dados muito mais rica do que um mapa chave-valor com expiração — e duas dessas estruturas, pub/sub e listas como filas, mudam como sistemas distribuídos podem ser desenhados.

O problema que elas resolvem é o mesmo: comunicação entre componentes sem acoplamento direto. Em um sistema onde o serviço de pedidos precisa notificar o serviço de e-mail, a abordagem mais simples é uma chamada HTTP direta. Funciona, até o serviço de e-mail ficar fora do ar — aí o pedido falha junto, por um problema que não era dele. Ou até o volume crescer e a chamada síncrona virar gargalo. Mensageria assíncrona resolve os dois: o serviço de pedidos publica um evento e segue em frente, sem saber ou se importar quem vai consumir.

Pub/sub no Redis funciona com canais. Um publisher envia uma mensagem para um canal; todos os subscribers conectados àquele canal recebem a mensagem imediatamente. É um modelo fire-and-forget — o Redis não persiste a mensagem, não confirma entrega, não retransmite para quem conectar depois. Se nenhum subscriber estiver ouvindo quando a mensagem chegar, ela some.

Isso torna pub/sub ideal para eventos que fazem sentido apenas agora: invalidação de cache distribuído, notificações em tempo real, broadcasts de estado. Para qualquer coisa que precise de garantia de entrega, a ferramenta certa é outra.

# publisher.py
import redis

r = redis.Redis(host='localhost', port=6379)

def publicar_evento(canal: str, payload: str) -> None:
    destinatarios = r.publish(canal, payload)
    print(f"Mensagem entregue a {destinatarios} subscriber(s)")

publicar_evento('pedidos:criados', '{"id": 42, "total": 149.90}')
# subscriber.py
import redis
import json

r = redis.Redis(host='localhost', port=6379)
pubsub = r.pubsub()

pubsub.subscribe('pedidos:criados')

for mensagem in pubsub.listen():
    if mensagem['type'] == 'message':
        dados = json.loads(mensagem['data'])
        print(f"Novo pedido recebido: {dados}")
        # processar notificação, atualizar cache, etc.

O mesmo padrão em JavaScript, onde pub/sub se encaixa naturalmente no modelo de eventos do Node.js:

import { createClient } from 'redis';

// Publisher
const pub = createClient();
await pub.connect();

await pub.publish('pedidos:criados', JSON.stringify({ id: 42, total: 149.90 }));

// Subscriber — cliente separado, Redis exige conexões distintas para pub e sub
const sub = createClient();
await sub.connect();

await sub.subscribe('pedidos:criados', (mensagem) => {
  const dados = JSON.parse(mensagem);
  console.log('Novo pedido:', dados);
});

Filas têm uma proposta diferente. O Redis implementa filas via listas com LPUSH/RPOP — ou BRPOP para a variante bloqueante, que suspende o consumidor até uma mensagem chegar em vez de fazer polling. Ao contrário do pub/sub, mensagens em lista persistem até serem consumidas, e cada mensagem é consumida por exatamente um worker. É o padrão work queue: vários workers competem pelas mensagens, e o Redis distribui naturalmente.

# producer.py
import redis
import json

r = redis.Redis(host='localhost', port=6379)

def enfileirar(fila: str, tarefa: dict) -> None:
    r.lpush(fila, json.dumps(tarefa))

enfileirar('emails:envio', {'para': 'joao@exemplo.com', 'assunto': 'Pedido confirmado'})
enfileirar('emails:envio', {'para': 'maria@exemplo.com', 'assunto': 'Pedido confirmado'})
# worker.py
import redis
import json

r = redis.Redis(host='localhost', port=6379)

def processar(tarefa: dict) -> None:
    print(f"Enviando e-mail para {tarefa['para']}: {tarefa['assunto']}")

while True:
    # BRPOP bloqueia até ter mensagem — sem polling, sem CPU desperdiçada
    _, raw = r.brpop('emails:envio')
    tarefa = json.loads(raw)
    processar(tarefa)

Em Rust, o padrão é o mesmo mas o modelo assíncrono com Tokio deixa o comportamento bloqueante mais explícito:

use redis::AsyncCommands;
use tokio;

#[tokio::main]
async fn main() -> redis::RedisResult<()> {
    let client = redis::Client::open("redis://127.0.0.1/")?;
    let mut con = client.get_async_connection().await?;

    loop {
        // brpop retorna (chave, valor) — timeout 0 = aguarda indefinidamente
        let (_, raw): (String, String) = con.brpop("emails:envio", 0).await?;
        println!("Processando tarefa: {}", raw);
        // processar tarefa aqui
    }
}

Para quem precisa de mais garantias do que listas oferecem — reprocessamento automático em caso de falha do worker, múltiplos grupos de consumidores independentes, replay de mensagens — o Redis tem Streams, introduzidos na versão 5.0. É uma estrutura de log append-only com consumer groups, semanticamente próxima ao Kafka para volumes menores. Vale conhecer antes de adicionar um broker externo ao stack.

Alguns padrões aparecem em toda implementação que vai para produção. Conexões Redis são caras de criar; usar um pool (redis.ConnectionPool em Python, pool nativo no cliente Go, deadpool-redis em Rust) elimina o overhead de handshake a cada operação. Canais de pub/sub devem ter nomes estruturados — pedidos:criados, usuario:123:notificacoes — para facilitar wildcard subscriptions e evitar colisões. Em filas, workers que falham no meio do processamento são o caso que mais pega: BRPOPLPUSH move a mensagem para uma fila de processamento antes de entregá-la, permitindo recuperação se o worker cair antes de terminar.

O que o Redis não é: um substituto para brokers como Kafka ou RabbitMQ em sistemas que precisam de persistência durável, replicação de mensagens com garantias fortes, ou throughput na casa de milhões de mensagens por segundo. Para volumes menores e quando a simplicidade operacional importa — não adicionar mais um serviço ao stack — Redis pub/sub e filas cobrem uma fatia enorme dos casos reais.

Referências

  • Redis. Documentation. https://redis.io/docs/
  • Redis. Pub/Sub. https://redis.io/docs/manual/pubsub/
  • Redis. Streams. https://redis.io/docs/data-types/streams/
  • Fowler, M. Enterprise Integration Patterns. https://martinfowler.com/books/eip.html
  • 12factor.net. Backing Services. https://12factor.net/backing-services