Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Redis Pub/Sub no Node.js

Atualizado em: 14 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O Redis Pub/Sub no Node.js permite distribuir mensagens em tempo real entre processos usando canais. Um produtor publica um evento e todos os assinantes conectados naquele momento recebem uma cópia. O modelo é simples, rápido e útil para notificações efêmeras, invalidação de cache, atualização de dashboards e comunicação entre instâncias.

Pub/Sub, porém, não é uma fila durável. Se um consumidor estiver desconectado, a mensagem é perdida. Não há confirmação, reprocessamento, histórico ou consumer groups. Por isso, o recurso deve ser usado quando a perda é aceitável ou quando outra fonte de verdade permite reconstruir o estado.

Neste guia, você aprenderá a publicar e assinar canais com o cliente Redis para Node.js, separar conexões, usar patterns, validar payloads, lidar com reconexão, escalar em cluster, testar e decidir quando usar Redis Streams ou uma fila dedicada.

Como funciona o Pub/Sub?

A documentação oficial de Redis Pub/Sub descreve um modelo de entrega at-most-once. Cada mensagem é enviada aos clientes inscritos naquele instante. Se a conexão falhar antes do processamento, não existe replay automático.

Os comandos principais são:

  • PUBLISH para enviar;
  • SUBSCRIBE para canais exatos;
  • PSUBSCRIBE para padrões;
  • UNSUBSCRIBE e PUNSUBSCRIBE para encerrar inscrições.

Instalação

npm install redis

Crie um cliente base:

import { createClient } from 'redis';

const redis = createClient({
  url: process.env.REDIS_URL
});

redis.on('error', error => {
  console.error('Redis error', error);
});

await redis.connect();

Por que usar duas conexões?

Uma conexão em modo subscriber fica dedicada à recepção de mensagens e não deve ser usada para comandos comuns. Duplique o cliente:

const publisher = createClient({
  url: process.env.REDIS_URL
});

const subscriber = publisher.duplicate();

await Promise.all([
  publisher.connect(),
  subscriber.connect()
]);

Essa separação evita conflitos e deixa o lifecycle mais claro.

Publicando mensagens

const event = {
  type: 'product.updated',
  productId: 'product-42',
  version: 8,
  occurredAt: new Date().toISOString()
};

await publisher.publish(
  'catalog.events',
  JSON.stringify(event)
);

O retorno informa quantos subscribers receberam a mensagem naquele momento. Isso não significa que o processamento foi concluído.

Assinando um canal

await subscriber.subscribe(
  'catalog.events',
  async message => {
    const event = JSON.parse(message);
    await handleCatalogEvent(event);
  }
);

O callback precisa tratar erros. Uma exception não deve derrubar silenciosamente o consumidor:

await subscriber.subscribe('catalog.events', message => {
  void handleMessage(message).catch(error => {
    logger.error({ err: error }, 'Falha ao processar evento Redis');
  });
});

Validando o payload

Nunca confie no JSON recebido. Valide tipo, versão e campos obrigatórios:

const CatalogEventSchema = z.object({
  type: z.literal('product.updated'),
  productId: z.string().min(1),
  version: z.number().int().positive(),
  occurredAt: z.string().datetime()
});

function parseEvent(message) {
  return CatalogEventSchema.parse(
    JSON.parse(message)
  );
}

Para validação por schema, consulte Ajv no Node.js: JSON Schema.

Nomes de canais

Use nomes previsíveis e versionados:

production.catalog.events.v1
production.notifications.user.v1

Evite criar um canal por usuário quando isso produz cardinalidade e gerenciamento desnecessários. Um canal compartilhado com filtros no payload pode ser mais simples.

Pattern subscriptions

await subscriber.pSubscribe(
  'production.*.events.v1',
  (message, channel) => {
    logger.info({ channel }, 'Evento recebido');
  }
);

Patterns facilitam observar vários canais, mas podem receber volume inesperado. Defina allowlists e métricas.

Sem histórico

Pub/Sub não armazena mensagens. Um novo subscriber só recebe eventos publicados depois da inscrição. Para processamento durável, use Redis Streams no Node.js, RabbitMQ, Kafka ou NATS JetStream.

Casos de uso adequados

  • invalidação de cache entre instâncias;
  • atualização de dashboards;
  • notificações temporárias;
  • presença online;
  • fan-out para WebSockets;
  • sinalização de configuração alterada;
  • telemetria não crítica.

Casos inadequados

  • pagamentos;
  • processamento de pedidos;
  • envio obrigatório de e-mail;
  • jobs que precisam retry;
  • eventos de auditoria;
  • integrações que exigem confirmação.

Fan-out para WebSocket

Uma instância publica e todas as instâncias recebem:

await publisher.publish(
  'chat.room.42',
  JSON.stringify({
    type: 'message.created',
    roomId: '42',
    messageId: 'msg-1'
  })
);

Cada processo envia aos sockets locais. Veja WebSocket com Node.js.

Invalidação de cache

await publisher.publish(
  'cache.invalidate',
  JSON.stringify({
    keys: ['product:42', 'catalog:featured']
  })
);

O subscriber remove as chaves locais. O Redis continua como fonte central, mas caches em memória permanecem coerentes.

Idempotência

Embora a entrega seja at-most-once, duplicidades podem surgir no nível da aplicação por retries de publicação ou lógica externa. Inclua um eventId e torne handlers seguros:

{
  "eventId": "evt-123",
  "type": "catalog.invalidate",
  "version": 1
}

Consulte Idempotência em APIs Node.js.

Ordenação

Mensagens publicadas na mesma conexão e canal chegam em ordem para subscribers conectados. Não transforme isso em garantia global entre múltiplos publishers, canais ou reconexões. Inclua versão ou sequência no payload.

Backpressure

Se o subscriber processa lentamente, buffers podem crescer. O callback não transforma automaticamente Pub/Sub em fila com controle de concorrência. Para trabalho pesado:

  • faça parsing rápido;
  • encaminhe para fila interna limitada;
  • defina concorrência;
  • monitore backlog local;
  • descarte ou degrade quando permitido.

Reconexão

O cliente Redis tenta reconectar conforme configuração. Durante a desconexão, mensagens publicadas não serão recuperadas. Quando reconectar, confirme se a biblioteca restaura subscriptions e emita métricas de perda potencial.

Eventos de conexão

subscriber.on('reconnecting', () => {
  logger.warn('Redis subscriber reconectando');
});

subscriber.on('ready', () => {
  logger.info('Redis subscriber pronto');
});

Não registre credenciais ou URL completa.

Shutdown

async function shutdown() {
  await subscriber.unsubscribe();
  await Promise.allSettled([
    subscriber.quit(),
    publisher.quit()
  ]);
}

Integre com Graceful Shutdown no Node.js.

Redis Cluster

Em Redis Cluster, o comportamento de Pub/Sub depende do modo global ou shard channels. Versões modernas oferecem Sharded Pub/Sub para limitar propagação ao shard relevante. Consulte a documentação da versão do servidor e do cliente.

Segurança

  • Use TLS quando disponível.
  • Autentique o cliente.
  • Restrinja comandos por ACL.
  • Não publique dados sensíveis.
  • Separe ambientes.
  • Use nomes de canal previsíveis.
  • Defina limites de payload.

Observabilidade

Registre e meça:

  • mensagens publicadas;
  • mensagens recebidas;
  • falhas de parsing;
  • duração do handler;
  • reconexões;
  • subscribers ativos;
  • payloads descartados.

Veja Métricas Prometheus no Node.js.

Testes unitários

Extraia o handler:

export async function handleCatalogEvent(event, deps) {
  await deps.cache.delete(`product:${event.productId}`);
}

Teste a função sem Redis. Depois faça teste de integração com uma instância descartável.

Teste de integração

test('subscriber recebe evento', async () => {
  const received = new Promise(resolve => {
    subscriber.subscribe('test.events', message => {
      resolve(JSON.parse(message));
    });
  });

  await publisher.publish(
    'test.events',
    JSON.stringify({ type: 'ping' })
  );

  assert.deepEqual(await received, {
    type: 'ping'
  });
});

Use timeout e cleanup. Testcontainers pode fornecer Redis real em CI.

Erros comuns

  • Usar a mesma conexão: comandos comuns deixam de funcionar.
  • Esperar durabilidade: mensagens desconectadas são perdidas.
  • Sem validação: payload inválido derruba handler.
  • Trabalho pesado no callback: buffers crescem.
  • Sem shutdown: processo mantém conexões.
  • Dados sensíveis: todos os subscribers recebem.
  • Canal por entidade: complexidade aumenta.
  • Sem métricas de reconexão: perda fica invisível.

Conclusão

O Redis Pub/Sub no Node.js é uma solução leve para mensagens efêmeras em tempo real. Ele funciona bem para invalidação de cache, fan-out de WebSockets e notificações que podem ser reconstruídas.

Use conexões separadas, valide mensagens, trate reconexão e aceite explicitamente a entrega at-most-once. Quando o evento precisa persistir, ser confirmado ou reprocessado, escolha Redis Streams ou um broker durável em vez de tentar transformar Pub/Sub em uma fila.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita