Redis Pub/Sub no Node.js permite distribuir mensagens em tempo real entre processos por meio de canais. Um produtor publica um evento e todos os clientes inscritos naquele canal recebem a mensagem enquanto estiverem conectados.
O modelo é simples e rápido, mas possui uma característica decisiva: mensagens não são armazenadas para consumidores desconectados. Por isso, Pub/Sub funciona bem para notificações efêmeras, invalidação de cache, atualização de presença e sinais internos, mas não substitui uma fila durável.
Neste guia, você aprenderá a usar Pub/Sub com o cliente oficial do Redis, separar conexões, criar canais seguros, tratar reconexões, evitar perda silenciosa e decidir quando usar Redis Streams.
Como o Pub/Sub funciona
O Redis mantém canais lógicos. Clientes podem publicar sem conhecer os assinantes:
await publisher.publish('orders.created', JSON.stringify({
orderId: 'o-123',
createdAt: new Date().toISOString()
}));Um assinante registra uma função para receber novas mensagens:
await subscriber.subscribe('orders.created', message => {
const event = JSON.parse(message);
console.log(event.orderId);
});A entrega ocorre apenas para clientes conectados naquele instante. Não existe confirmação, retry automático ou histórico.
Instalando e conectando
npm install redis
import { createClient } from 'redis';
const publisher = createClient({
url: process.env.REDIS_URL
});
publisher.on('error', error => {
console.error('Redis publisher error', error);
});
await publisher.connect();Valide a variável de ambiente antes de iniciar e use TLS quando o Redis estiver fora de uma rede confiável. Consulte também Redis com Node.js: Cache e Filas.
Use uma conexão dedicada para inscrição
Uma conexão em modo subscriber fica dedicada a comandos de Pub/Sub. Duplique o cliente:
const subscriber = publisher.duplicate();
subscriber.on('error', error => {
console.error('Redis subscriber error', error);
});
await subscriber.connect();
await subscriber.subscribe('orders.created', handleOrderCreated);Não reutilize a mesma conexão para cache, locks ou comandos comuns. Separar conexões também facilita métricas e shutdown.
Envelope de evento
Evite publicar objetos sem versão. Use um envelope estável:
{
"id": "evt-01J...",
"type": "orders.created.v1",
"occurredAt": "2026-07-26T12:00:00.000Z",
"source": "orders-api",
"tenantId": "t-7",
"data": {
"orderId": "o-123"
}
}O campo id ajuda a rastrear duplicações causadas por reconexão ou lógica do produtor. A versão no tipo permite evoluir o contrato sem quebrar assinantes antigos.
Validação do payload
Mesmo em rede interna, trate a mensagem como entrada externa:
function parseEvent(raw) {
let value;
try {
value = JSON.parse(raw);
} catch {
throw new Error('Mensagem Pub/Sub não contém JSON válido');
}
if (value?.type !== 'orders.created.v1') {
throw new Error('Tipo de evento inesperado');
}
if (typeof value.data?.orderId !== 'string') {
throw new Error('orderId inválido');
}
return value;
}Uma biblioteca de schema pode centralizar validação. Não execute comandos, URLs ou nomes de arquivo recebidos sem regras adicionais.
Canais e namespaces
Use nomes previsíveis:
production.orders.created.v1
production.cache.products.invalidated.v1Inclua ambiente para evitar que desenvolvimento consuma eventos de produção. Em multi-tenancy, normalmente é melhor manter o tenant no payload, evitando milhares de canais e assinaturas difíceis de administrar.
Pattern subscriptions
await subscriber.pSubscribe('production.orders.*', (message, channel) => {
console.log({ channel, message });
});Assinaturas por padrão são convenientes, mas podem receber volume maior que o esperado. Monitore canais correspondidos e limite o trabalho feito pelo callback.
Não bloqueie o callback
Processamento pesado dentro do assinante atrasa mensagens seguintes. Encaminhe trabalho para uma fila interna limitada:
const pending = [];
const maxPending = 1000;
function enqueue(message) {
if (pending.length >= maxPending) {
throw new Error('Fila local cheia');
}
pending.push(message);
}Para tarefas que não podem ser perdidas, publique diretamente em uma fila durável em vez de depender de memória local.
Reconexão e perda de mensagens
Quando a conexão cai, mensagens publicadas durante a interrupção não são recuperadas. A aplicação deve decidir se isso é aceitável. Para invalidação de cache, uma expiração curta pode funcionar como defesa. Para pagamentos ou pedidos, não é aceitável.
Depois da reconexão, confirme se as inscrições foram restauradas pelo cliente e exponha uma métrica de estado:
let subscribed = false;
await subscriber.subscribe(channel, message => {
subscribed = true;
handle(message);
});Use readiness separada caso o serviço dependa obrigatoriamente da inscrição.
Pub/Sub para invalidação de cache
Um processo altera um produto e publica:
await publisher.publish(
'cache.products.invalidated.v1',
JSON.stringify({ productId })
);Outras instâncias removem a chave local:
await subscriber.subscribe(channel, raw => {
const { productId } = JSON.parse(raw);
localCache.delete(`product:${productId}`);
});A estratégia reduz inconsistência entre instâncias, mas deve ser combinada com TTL. Se um assinante estiver offline, o valor antigo desaparecerá quando expirar.
Pub/Sub com WebSocket
Em aplicações com múltiplas instâncias, Pub/Sub pode distribuir eventos para todos os servidores WebSocket. Cada instância recebe a mensagem e envia somente aos clientes locais interessados.
Consulte WebSocket com Node.js. Aplique autenticação e autorização antes de entregar dados; receber um evento interno não significa que todo socket pode vê-lo.
Presença e eventos efêmeros
Indicadores como “usuário está digitando” são bons candidatos, pois perder uma atualização isolada não compromete o estado permanente. Ainda assim, limite frequência e tamanho das mensagens.
Pub/Sub não é fila
Use uma solução durável quando precisar de:
- armazenar mensagens;
- confirmar processamento;
- repetir falhas;
- reprocessar histórico;
- distribuir cada mensagem para apenas um consumidor;
- controlar backlog.
Redis Streams, RabbitMQ e Kafka atendem cenários diferentes. Pub/Sub envia uma cópia a cada assinante conectado.
Controle de tamanho
Não envie arquivos ou payloads grandes. Publique identificadores e deixe o consumidor buscar o dado autorizado:
{
"type": "reports.ready.v1",
"data": { "reportId": "r-10" }
}Defina um limite de bytes antes de fazer parse. Payloads grandes aumentam latência, memória e pressão de rede.
Segurança
- use ACLs do Redis;
- separe credenciais por aplicação;
- ative TLS fora da rede privada;
- não exponha o Redis diretamente à internet;
- não inclua segredos no payload;
- redija mensagens em logs;
- valide tenant e autorização no consumidor.
O nome do canal não é um mecanismo de autorização.
Observabilidade
Registre e monitore:
- estado das conexões;
- reconexões;
- mensagens publicadas e recebidas;
- erros de parsing;
- tempo do callback;
- fila local;
- mensagens descartadas;
- versões desconhecidas.
Inclua o ID do evento em logs e traces. Veja Distributed Tracing no Node.js.
Shutdown gracioso
async function shutdown() {
await subscriber.unsubscribe();
await subscriber.quit();
await publisher.quit();
}
process.once('SIGTERM', shutdown);
process.once('SIGINT', shutdown);Primeiro pare novas publicações, depois aguarde callbacks ativos e feche as conexões.
Testes
Teste com uma instância real do Redis:
- publicação e recebimento;
- dois assinantes no mesmo canal;
- payload inválido;
- mensagem acima do limite;
- reconexão;
- assinatura por padrão;
- shutdown durante processamento;
- isolamento entre ambientes.
Não baseie o teste apenas em sleeps; use Promises que resolvem quando a mensagem chega, com timeout explícito.
Erros comuns
- usar a conexão subscriber para comandos comuns;
- esperar replay de mensagens;
- processar tarefas críticas sem persistência;
- não versionar eventos;
- publicar dados sensíveis;
- não limitar payload;
- bloquear o callback;
- não aplicar TTL ao cache invalidado por Pub/Sub.
Conclusão
Redis Pub/Sub no Node.js é uma ferramenta eficiente para sinais efêmeros e comunicação em tempo real entre instâncias. Sua simplicidade vem da ausência de persistência e confirmação.
Use conexões dedicadas, contratos versionados, payloads pequenos e observabilidade. Quando perder uma mensagem não for aceitável, escolha Redis Streams ou outro broker durável.
Consulte a documentação oficial de Redis Pub/Sub e a documentação do cliente node-redis.




