Solução de Problemas Comuns de Configuração do Redis Pub/Sub

Garanta mensagens em tempo real confiáveis dominando os desafios de configuração do Redis Pub/Sub. Este guia fornece etapas práticas para solucionar consumidores lentos, a principal causa de instabilidade, usando a diretiva crucial `client-output-buffer-limit`. Aprenda a diagnosticar picos de memória com o comando `CLIENT LIST`, gerenciar conexões dedicadas de assinantes e implementar práticas recomendadas para isolamento de Pub/Sub de alto volume, mantendo a integridade do sistema.

Solução de Problemas Comuns de Configuração do Redis Pub/Sub

O Redis Pub/Sub é tão simples que as equipes frequentemente o tratam como um pequeno broker de mensagens que não precisam operar. Um publicador chama PUBLISH, os assinantes recebem mensagens e tudo parece instantâneo.

O problema começa quando redes reais, clientes lentos, reconexões e instâncias Redis compartilhadas entram em cena. Pub/Sub não tem histórico de mensagens, nem confirmações de recebimento, nem fila de repetição. É um mecanismo de transmissão ao vivo. Se um assinante estiver ausente, a mensagem é perdida para esse assinante. Se um assinante estiver conectado, mas não conseguir ler rápido o suficiente, o Redis precisa manter a saída pendente para esse cliente até que um limite configurado o desconecte.

Isso torna o Redis Pub/Sub excelente para invalidação de cache, atualizações de presença, dashboards ao vivo e notificações de "último valor". É uma escolha inadequada para fluxos de trabalho onde cada mensagem deve ser processada exatamente uma vez ou reproduzida após uma interrupção. Para isso, considere Redis Streams, RabbitMQ, Kafka ou outro broker com rastreamento de entrega.

Sintoma: a memória cresce enquanto o tráfego Pub/Sub é alto

A falha mais comum do Pub/Sub é um assinante lento. O publicador está bem. O Redis está bem no início. Um consumidor está atrasado porque seu processo foi pausado, sua rede está lenta ou ele realiza trabalho pesado dentro do manipulador de mensagens. O Redis continua escrevendo mensagens no buffer de saída desse cliente. Se o buffer puder crescer sem um limite útil, a pressão na memória se espalha de um único assinante problemático para toda a instância.

Verifique os clientes:

redis-cli CLIENT LIST

Procure por clientes com comandos Pub/Sub e grande memória de saída:

id=88 addr=10.0.4.12:51244 flags=P db=0 sub=3 psub=0 omem=13421772 obl=128 oll=64 cmd=subscribe

Campos úteis:

  • flags=P indica um cliente Pub/Sub.
  • sub e psub mostram contagens de assinaturas de canal e padrão.
  • omem é a memória do buffer de saída em bytes.
  • obl e oll mostram detalhes do backlog do buffer de saída.
  • cmd=subscribe ou cmd=psubscribe confirma o que o cliente está fazendo.

Não se preocupe com idle para assinantes. Uma conexão Pub/Sub pode ficar ociosa do ponto de vista do comando enquanto ainda recebe mensagens enviadas.

Corrigindo consumidores lentos com limites de buffer de saída

O Redis fornece sua própria classe de limite de buffer de saída para clientes Pub/Sub:

client-output-buffer-limit pubsub 32mb 8mb 60

Leia assim:

  • Desconecte imediatamente se o buffer de saída de um cliente Pub/Sub atingir 32 MB.
  • Desconecte se permanecer acima de 8 MB por 60 segundos.

Os números exatos devem corresponder ao seu tráfego e comportamento do cliente. O erro comum é definir o limite como 0 0 0 porque as desconexões são irritantes durante os testes. Isso remove a barreira de segurança. Em produção, uma desconexão geralmente é melhor do que deixar um assinante travado consumir memória até que o Redis se torne instável.

Após alterar o redis.conf, recarregue ou reinicie de acordo com seu processo de implantação. Você também pode inspecionar o valor ativo:

redis-cli CONFIG GET client-output-buffer-limit

Se você usa um serviço Redis gerenciado, essa configuração pode estar exposta através do grupo de parâmetros ou interface de configuração do provedor, em vez de edição direta de arquivos.

Sintoma: assinantes conectam mas não recebem nada

Comece com o teste mais simples possível. Abra um terminal:

redis-cli SUBSCRIBE test.channel

Abra outro:

redis-cli PUBLISH test.channel hello

Se isso funcionar a partir do host Redis, mas não a partir da sua aplicação, provavelmente você tem um problema no lado do cliente ou de rede. Se falhar em todos os lugares, verifique autenticação, ACLs, roteamento de cluster e o nome real do canal.

Os nomes dos canais são sequências de bytes exatas. events.user, events:User e events:user são canais diferentes. Em sistemas maiores, prefiro constantes ou um pequeno módulo de nomenclatura de canais em vez de strings digitadas manualmente espalhadas pelos serviços.

Assinaturas de padrão adicionam outra fonte de confusão:

PSUBSCRIBE events:*

Isso corresponde a events:user.created, mas não a prod:events:user.created. Se sua aplicação prefixa nomes de ambiente, inclua o prefixo no padrão.

Problemas de autenticação e ACL

Implantações modernas do Redis geralmente usam usuários ACL em vez de uma senha requirepass compartilhada. Um cliente Pub/Sub precisa de permissão para conectar, autenticar, assinar o padrão do canal e, às vezes, publicar.

Uma verificação rápida via CLI:

redis-cli -u redis://app-user:[email protected]:6379 PING
redis-cli -u redis://app-user:[email protected]:6379 SUBSCRIBE events:test

Sintomas comuns de autenticação:

  • NOAUTH Authentication required: o cliente enviou um comando antes de autenticar.
  • WRONGPASS invalid username-password pair: senha errada, usuário errado ou um segredo rotacionado não implantado em todos os lugares.
  • NOPERM this user has no permissions: o usuário autenticou, mas não tem permissões de comando ou canal.

Com ACLs, as permissões de canal são separadas das permissões de chave. Um usuário pode ter permissão para executar comandos de chave normais, mas não para assinar os canais que você espera. Revise o usuário configurado:

redis-cli ACL GETUSER app-user

Não cole segredos de produção no histórico do shell durante os testes. Use variáveis de ambiente ou uma credencial temporária quando possível.

Não compartilhe conexões Pub/Sub com comandos normais

Depois que uma conexão entra no modo de assinatura, ela não é mais uma conexão de comando Redis normal. Ela recebe mensagens enviadas e só pode emitir comandos relacionados à assinatura, como SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PING, RESET ou comandos do tipo quit, dependendo do cliente e da versão do Redis.

Se você pegar uma conexão emprestada de um pool geral e chamar SUBSCRIBE, essa conexão do pool efetivamente não estará mais disponível para GET, SET ou outros comandos comuns. O resultado pode parecer timeouts aleatórios da aplicação porque o pool lentamente se enche de conexões presas no modo de assinante.

Use conexões separadas:

  • Um cliente ou pool normal para comandos Redis regulares.
  • Uma conexão de assinante de longa duração por processo ou thread de assinante.
  • Uma conexão de publicador separada se sua biblioteca cliente recomendar.

Muitas bibliotecas cliente fornecem um método duplicate() ou new connection para esse fim. Use-o. Não adivinhe que o pool resolverá.

O comportamento de reconexão é importante

Pub/Sub não reproduz mensagens perdidas após a reconexão. Se um assinante cair por 30 segundos, cada mensagem publicada durante essa janela é perdida para esse assinante.

Isso é aceitável para invalidação de cache apenas se sua aplicação tiver um fallback. Por exemplo, um serviço que perde um evento de invalidação ainda deve ter TTLs, verificações de versão ou uma maneira de reconstruir a partir do banco de dados de origem. Sem um fallback, uma única falha de rede pode deixar entradas de cache local obsoletas por muito tempo.

Na reconexão, certifique-se de que o cliente reassine todos os canais e padrões. Algumas bibliotecas fazem isso automaticamente. Outras exigem que você registre novamente os manipuladores após a reconexão. Teste isso matando a conexão do assinante:

redis-cli CLIENT LIST | grep subscribe
redis-cli CLIENT KILL ID <client-id>

Em seguida, publique uma mensagem de teste e confirme se a aplicação a recebe após a reconexão.

Alto fan-out pode se tornar pressão de CPU e rede

O trabalho PUBLISH cresce com o número de assinantes e padrões correspondentes. Um canal com milhares de assinantes significa que o Redis precisa enviar a mensagem milhares de vezes. Isso ainda pode ser aceitável, mas não é gratuito.

Monitore:

redis-cli INFO stats
redis-cli PUBSUB NUMSUB events:test
redis-cli PUBSUB NUMPAT

PUBSUB NUMSUB ajuda a confirmar se um canal tem a contagem de assinantes que você pensa. PUBSUB NUMPAT é útil porque assinaturas de padrão amplas podem surpreender. Alguns serviços usando PSUBSCRIBE * ou PSUBSCRIBE events:* podem receber muito mais tráfego do que o pretendido.

Se o tráfego Pub/Sub for importante e de alto volume, isole-o. Uma instância Redis dedicada para Pub/Sub protege seu cache e armazenamento de sessão de problemas de buffer de assinante e picos de transmissão. A instância dedicada pode ter configuração ajustada para Pub/Sub, preocupações de persistência mais curtas e monitoramento focado em clientes e saída de rede.

Mantenha os manipuladores de mensagens curtos

Um assinante deve ler a mensagem, fazer validação mínima e passar o trabalho para outra coisa. Se o manipulador chamar um banco de dados, fizer uma requisição HTTP, renderizar um template ou realizar uma computação longa antes de ler a próxima mensagem, ele pode se tornar o consumidor lento que você está tentando evitar.

Forma melhor:

Mensagem Redis -> manipulador leve -> fila local -> pool de workers

A fila local pode ser uma fila em processo, um pool de threads, uma fila de tarefas assíncronas ou um broker durável se o trabalho precisar sobreviver a reinicializações do processo. A ideia chave é que a leitura do Redis não deve esperar pela dependência downstream mais lenta.

Se o trabalho precisa ser confiável, Pub/Sub é o primitivo errado. Redis Streams com grupos de consumidores fornece entradas pendentes e confirmações. Um broker dedicado pode ser melhor se você precisar de filas de mensagens mortas, repetições atrasadas, prioridades ou retenção longa.

Uma lista de verificação prática para solução de problemas

Quando o Pub/Sub parece não saudável, geralmente trabalho nesta ordem:

  1. Confirme o canal manualmente com SUBSCRIBE e PUBLISH.
  2. Verifique CLIENT LIST para clientes Pub/Sub com omem alto.
  3. Inspecione client-output-buffer-limit pubsub.
  4. Verifique autenticação e permissões de canal ACL.
  5. Confirme que os assinantes usam conexões dedicadas.
  6. Mate uma conexão de assinante e verifique o comportamento de reconexão e reassinatura.
  7. Verifique PUBSUB NUMSUB e PUBSUB NUMPAT para fan-out inesperado.
  8. Decida se a carga de trabalho realmente precisa de Streams ou um broker dedicado.

O Redis Pub/Sub é confiável apenas no sentido de que faz exatamente o que promete: transmissão de mensagens ao vivo para assinantes atualmente conectados. A maioria dos incidentes de produção vem de esperar que ele se comporte como uma fila durável. Configure limites de buffer, isole tráfego pesado e projete assinantes como se as mensagens pudessem ser perdidas, porque às vezes podem.

Exemplo de incidente: um dashboard desacelera o cache

Imagine uma instância Redis usada para chaves de cache comuns e um pequeno canal Pub/Sub chamado metrics:live. No início, apenas um dashboard interno assina. Meses depois, vários processos de gateway de navegador também assinam, e um deles começa a enviar atualizações através de conexões WebSocket lentas. O Redis não sabe que o navegador downstream é lento. Ele apenas vê uma conexão de assinante que não está lendo rápido o suficiente.

O primeiro sintoma pode não mencionar Pub/Sub. Requisições da aplicação que usam chamadas normais GET e SET começam a expirar. A memória aumenta. O host Redis parece ocupado, mas SLOWLOG não mostra um comando caro óbvio.

A pista útil está em CLIENT LIST:

flags=P sub=1 omem=25165824 cmd=subscribe

Isso indica que um cliente Pub/Sub tem um buffer de saída grande. Se o limite rígido configurado for 32 MB, esse cliente está perto de ser desconectado. Se o limite estiver desabilitado, o Redis pode continuar armazenando em buffer até que toda a instância esteja sob pressão de memória.

A correção não é apenas aumentar o limite. Aumentá-lo pode esconder o problema e permitir um pior pico de memória na próxima vez. Uma resposta melhor é:

  1. Identifique o processo assinante a partir de addr, name ou metadados do cliente.
  2. Mate o pior assinante se a memória estiver em risco.
  3. Adicione ou aperte client-output-buffer-limit pubsub.
  4. Mova a entrega downstream lenta para uma fila interna fora do loop de leitura do Redis.
  5. Considere mover Pub/Sub de alto volume para uma instância Redis dedicada.

Nomes de cliente tornam isso muito mais fácil. Muitas bibliotecas Redis permitem definir um nome de conexão. Use nomes como web-1:pubsub-cache-invalidations ou dashboard:metrics-live para que CLIENT LIST aponte para o proprietário em vez de mostrar apenas um IP e porta.

CLIENT SETNAME dashboard:metrics-live

Esse pequeno hábito transforma um incidente de memória vago em uma conversa direta com a equipe que possui o assinante.