Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Projetar os consumidores de mensagens para que processar a mesma mensagem mais do que uma vez tenha o mesmo efeito que processá-la uma vez. Sistemas de mensagens que garantem pelo menos uma entrega podem entregar a mesma mensagem várias vezes. Sem resiliência contra duplicados, reprocessar uma mensagem pode criar registos duplicados, cobrar duas vezes a um cliente ou ter outros efeitos indesejados.
Contexto e problema
Aplicações distribuídas normalmente trocam trabalho através de um corretor de mensagens em vez de chamadas síncronas diretas. A maioria dos brokers, incluindo Azure Service Bus, Hubs de Eventos do Azure, Apache Kafka e RabbitMQ, fornece entrega pelo menos uma vez. Esta garantia garante que uma mensagem chegue ao consumidor mesmo quando ocorrem falhas, mas também significa que o corretor pode entregar a mesma mensagem mais do que uma vez.
Os duplicados surgem de várias fontes:
Novas tentativas do produtor
Um produtor envia uma mensagem, não recebe confirmação devido a uma falha de rede transitória ou timeout, e envia a mensagem novamente. O corretor agora detém duas cópias, mesmo que o envio tenha sido bem-sucedido da primeira vez.
Reentrega após falta de confirmação de receção
Um consumidor recebe e processa uma mensagem, mas não a reconhece porque o consumidor crasha, o bloqueio expira ou o reconhecimento é perdido. O corretor assume que a mensagem não foi processada e entrega-a novamente.
Falhas do consumidor a meio do processamento
Um consumidor efetua uma escrita na base de dados, mas falha antes de confirmar a receção da mensagem. Quando outra instância apanha a mensagem reentregue, repete a operação de escrita.
A entrega exata de uma vez num sistema distribuído é impraticável de garantir. Mesmo os corretores que afirmam uma semântica exata uma vez só garantem operações que controlam diretamente, como entregar mensagens aos consumidores ou escrever dados de volta ao corretor. Não podem garantir os efeitos laterais externos que os consumidores realizam noutros sistemas. A solução duradoura não é eliminar entregas duplicadas. É para levar o consumidor a tolerá-lo. Quando se combina pelo menos uma entrega com um cliente que ignora duplicados, obtém-se um processamento eficaz de uma única vez.
Solução
Torne o consumidor idempotente, mantendo um registo das mensagens processadas e ignorando qualquer mensagem que já tenha visto anteriormente. O consumidor coloca esta decisão num identificador estável que sobrevive à reentrega, verifica um armazenamento persistente para determinar se esse identificador já foi processado, e processa a mensagem ou descarta-a como duplicada.
Os passos seguintes descrevem o fluxo principal:
- Leia a mensagem e extraia a sua chave de deduplicação.
- Verifique o armazenamento de deduplicação para essa chave.
- Se a chave existir, trate a mensagem como um duplicado. Confirme-o e pare, devolvendo opcionalmente o resultado previamente registado.
- Se a chave não existir, processa a mensagem e regista a chave numa única operação atómica, depois confirma a mensagem.
Escolha uma chave de deduplicação estável
A chave deve identificar de forma única e consistente a mensagem lógica em cada reentrega. Use um identificador de mensagem atribuído pelo produtor ou uma chave de idempotência ao nível do negócio que identifique a operação lógica específica, e não um contexto de correlação partilhado que várias mensagens possam transportar. No Azure Service Bus, a MessageId propriedade serve este propósito porque identifica de forma única a mensagem e o seu payload. Não uses CorrelationId como chave, porque agrupa mensagens relacionadas, como um pedido e as suas respostas. Para eventos que seguem a especificação CloudEvents, a combinação dos source atributos e id identifica de forma única um evento e mantém-se estável ao longo das reentregas.
Não baseie a deteção em identificadores ao nível do transporte que o intermediário de mensagens regenera numa nova entrega, nem em valores derivados de tentativas de entrega, porque esses valores mudam entre mensagens duplicadas e inviabilizam a deteção de duplicados. Também evite derivar a chave de campos voláteis, como marcas temporais de receção.
Quando mais do que um consumidor independente processa o mesmo canal, como vários assinantes num design de publicação-subscrição, cada consumidor processa legitimamente a sua própria cópia de uma mensagem e precisa de acompanhar de forma independente a conclusão do processamento da mensagem. Se esses consumidores partilharem um armazenamento de desduplicação, utilize para o registo uma chave composta pelo identificador do consumidor e pelo identificador da mensagem. Um armazenamento baseado apenas na identidade da mensagem permite ao primeiro consumidor suprimir o processamento de todos os outros.
Decida onde guardar as chaves processadas
Tens duas opções comuns:
Uma tabela de deduplicação dedicada. O consumidor mantém uma tabela separada, por vezes chamada caixa de entrada, que contém uma linha por cada chave processada. Esta abordagem mantém as preocupações de deduplicação separadas dos dados empresariais e funciona bem quando muitos tipos de mensagens partilham um mesmo mecanismo.
A própria entidade empresarial. O consumidor armazena a chave no registo que a mensagem cria ou atualiza. Esta abordagem evita uma tabela separada, mas associa a deduplicação à forma dos dados do negócio.
Confirme o marcador e os efeitos secundários de forma atómica
O fluxo de verificar primeiro e só depois processar tem uma janela de vulnerabilidade. Se o consumidor processar a mensagem e depois registar a chave numa etapa separada, uma falha entre as duas operações faz com que os efeitos secundários sejam aplicados, mas a chave fique por registar, pelo que a entrega seguinte volta a processar a mensagem.
Resolva esta janela de falha escrevendo o marcador de deduplicação e os efeitos secundários do negócio na mesma transação. Quando ambos confirmam em conjunto ou não confirmam de todo, uma reentrega encontra o marcador de confirmação e ignora-a, ou não encontra nenhum marcador porque a transação foi revertida e volta a processar em segurança. Esta variante transacional é o padrão da caixa de entrada e é a contraparte, no lado do consumo, do padrão da caixa de saída transacional no lado da produção.
Evitar duplicados simultâneos
Numa entrega pelo menos uma vez com múltiplos consumidores concorrentes, duas instâncias podem receber cópias da mesma mensagem ao mesmo tempo. Ambos podem passar na verificação da existência antes de qualquer um deles confirmar, pelo que a verificação, por si só, não impede o processamento em duplicado.
Garantir a integridade no armazenamento de dados em vez de na lógica da aplicação:
Use uma restrição única na chave de deduplicação. Ambas as transações tentam inserir a chave, mas apenas uma consegue. O outro falha na restrição e trata a mensagem como duplicada. Esta abordagem faz da base de dados o único árbitro da raça.
Evita corridas de check-then-set nos caches. Um padrão que verifica uma chave e depois a define em duas operações separadas tem uma janela que permite que tentativas simultâneas reclamem a chave. Utilize uma operação de escrita condicional atómica, como uma inserção que falha em caso de conflito ou uma operação de definição se ausente, para que a obtenção da chave seja uma única operação atómica.
Trate os efeitos secundários que não podem fazer parte da transação
Alguns processos não podem participar na transação da base de dados do consumidor, como chamar uma API de terceiros ou escrever para uma loja externa. Para estes processos, utilize-se uma abordagem em duas fases:
- Grava a chave com um estado em curso antes de realizares a ação externa.
- Realiza o processo.
- Atualize o registo para concluído e guarde o resultado.
Numa nova entrega, um registo concluído permite-lhe evitar repetir a chamada. Um registo em andamento indica que uma tentativa anterior pode ter sido parcialmente concluída ou está a ser trabalhada por outro consumidor.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar este padrão:
Prefiro operações naturalmente idempotentes. Algumas operações são inerentemente idempotentes e não necessitam de contabilidade por deduplicação. Um upsert baseado num identificador de negócio, uma escrita que define um valor absoluto em vez de um incremento, ou um HTTP
PUTpara um identificador de recurso produz o mesmo resultado quer seja executado uma ou várias vezes.Por vezes, é possível tornar uma operação naturalmente idempotente por meio da transferência de estado veiculado pelo evento, em que a mensagem transporta o estado absoluto resultante, como o novo estado de um pedido, de modo que o consumidor o trata como uma operação de upsert em vez de uma alteração relativa.
Sugestão
Projete primeiro tendo em vista a idempotência natural e adicione técnicas de eliminação de duplicados apenas às operações que não possam ser tornadas naturalmente idempotentes.
Gerir o ciclo de vida dos registos de deduplicação. Os registos de deduplicação acumulam-se, a menos que os elimine. Guarde cada registo pelo menos enquanto o corretor conseguir reentregar a mensagem original. Esta janela depende do número máximo de tentativas de entrega do mediador, do tempo limite de bloqueio ou de visibilidade e do tempo de vida da mensagem. Defina um tempo de vida para os registos de deduplicação que seja superior a essa janela, para que uma reentrega tardia ainda encontre o respetivo marcador. Eliminar registos demasiado cedo volta a criar a possibilidade de surgirem duplicados. Tenha em conta as mensagens que um operador reenvia a partir de uma fila de mensagens não entregues, porque um novo envio pode ocorrer muito depois do período normal de nova entrega.
Use um framework de mensagens em vez de rolar manualmente a deduplicação. Implementar corretamente o armazenamento de deduplicação, o commit atómico e a limpeza de registos é propenso a erros. Frameworks baseados em mensagens fornecem este padrão como uma funcionalidade incorporada.
Por exemplo, o NServiceBus desduplica as mensagens recebidas pelo seu identificador de mensagem e fornece retenção e limpeza configuráveis para os dados de deduplicação. A caixa de entrada do consumidor do MassTransit acompanha as mensagens recebidas pelo seu identificador de mensagem para fornecer o comportamento do consumidor exatamente uma vez.
A deduplicação por corretores reduz, mas não elimina, a necessidade de lógica de consumo idempotente. Algumas plataformas filtram duplicados na camada de transporte. A deteção de duplicados do Azure Service Bus descarta mensagens que contêm um
MessageIdrepetido dentro de uma janela temporal configurada, o que evita duplicados causados por novas tentativas de envio do produtor. Esta funcionalidade opera do lado de envio e dentro de uma janela limitada. Isto não impede que um consumidor processe a mesma mensagem duas vezes após uma nova entrega, pelo que continua a ser necessária lógica idempotente no consumidor. Considere as funcionalidades da plataforma como uma primeira camada de defesa que reduz o volume de duplicados, e não como um substituto do padrão.Tenha em conta a ordem das mensagens. A deduplicação remove duplicados, mas não garante a ordem. Se o consumidor depende da ordem de processamento, combine este padrão com um mecanismo de ordenação, como sessões de mensagens do Azure Service Bus, ou inclua dados de sequência ou versão que permitam ao consumidor rejeitar mensagens obsoletas.
Ferramenta de observabilidade. Emita a chave de deduplicação e um identificador de correlação em registos estruturados, e acompanhe uma métrica para duplicados detetados. Um aumento da taxa de duplicados pode indicar uma configuração incorreta do produtor, uma janela de confirmação ou de bloqueio demasiado pequena, ou consumidores com problemas. Use rastreio e correlação de ponta a ponta para seguir uma mensagem entre serviços.
Propagar idempotência para chamadas a jusante. Tornar um consumidor idempotente não protege os serviços que ele chama. Quando um consumidor invoca serviços a jusante como parte do processamento, propaga a chave de idempotência para que cada nível possa desduplicar o seu próprio trabalho.
Quando utilizar este padrão
Utilize este padrão quando:
Consomes mensagens de um corretor que fornece pelo menos uma entrega, que é o padrão para a maioria dos corretores.
Reprocessar uma mensagem produz resultados incorretos, como transações financeiras duplicadas, criação de recursos duplicados ou notificações repetidas.
Múltiplos consumidores concorrentes processam o mesmo canal, o que torna provável a entrega duplicada simultânea.
Este padrão pode não ser adequado quando:
Cada operação que o consumidor realiza já é naturalmente idempotente, pelo que o reprocessamento é inofensivo e a contabilidade de deduplicação acrescenta custos sem benefício.
A carga de trabalho pode tolerar os efeitos do processamento ocasional de duplicados, e o custo de um armazenamento de deduplicação supera o impacto de um duplicado.
Processamento idempotente para além da mensagem
Este padrão aplica o princípio da idempotência aos consumidores de mensagens, mas o processamento idempotente é um princípio de fiabilidade mais amplo. Qualquer operação que possa ser executada mais do que uma vez sobre uma tarefa idêntica beneficia disso. Este princípio inclui transformações de extração, transformação e carregamento (ETL) que reprocessam dados reproduzidos, processamento em fluxo que retoma a partir de um ponto de controlo, tarefas agendadas que se sobrepõem ou reiniciam, e pontos finais Webhook ou HTTP que recebem envios duplicados.
Em cada caso, aplica-se a mesma técnica principal:
- Identifique a unidade de trabalho com uma chave estável.
- Regista o que já processaste.
- Ignora ou absorve duplicações, de modo que repetir a operação não altere o resultado.
Os mecanismos deste artigo, como chaves estáveis, marcadores atómicos e restrições únicas, transferem-se para esses contextos mesmo quando não está envolvido um broker de mensagens.
Design da carga de trabalho
Avalie como utilizar o padrão Consumidor Idempotente na conceção de uma carga de trabalho para cumprir os objetivos e os princípios definidos nos pilares do Azure Well-Architected Framework. A tabela a seguir fornece orientação sobre como esse padrão suporta as metas de cada pilar.
| Pilar | Como esse padrão suporta os objetivos do pilar |
|---|---|
| As decisões de projeto de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e garantem que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | Este padrão permite que uma carga de trabalho utilize um modelo de entrega pelo menos uma vez e repetições seguras sem corromper os dados, o que transforma a entrega duplicada de um risco para a integridade dos dados numa condição tolerada. - RE:07 Autopreservação - Lidar com falhas transitórias |
Se este padrão introduzir compensações dentro de um pilar, considere-as em relação aos objetivos dos outros pilares.
Exemplo
O exemplo seguinte mostra um consumidor idempotente que processa encomendas do Azure Service Bus e mantém o estado no Azure Cosmos DB para NoSQL.
Um produtor define o Service Bus MessageId como um identificador de encomenda ao nível empresarial. O consumidor recebe mensagens no modo PeekLock , que reentrega uma mensagem se o consumidor não a completar dentro do tempo do bloqueio. O contentor do Azure Cosmos DB do consumidor particiona com base no identificador da encomenda (/orderId) e define o documento id com esse mesmo identificador da encomenda, para que cada cópia de uma dada encomenda seja associada à mesma partição lógica e o próprio registo da encomenda sirva de marcador de deduplicação.
O consumidor processa cada mensagem da seguinte forma:
- Lê a mensagem e usa-a
MessageIdcomo chave de deduplicação. - Crie o documento da encomenda com
ide a chave de partição definidos para o identificador da encomenda. - Se a criação for bem-sucedida, completa a mensagem para que o Service Bus a remova da fila.
- Se a criação falhar com um código de estado HTTP 409 (Conflito) porque já existe um documento com o mesmo
id, leia o documento existente e compare-o com a mensagem atual. Se um hash de pedido armazenado ou campos de negócio imutáveis coincidirem, trate a mensagem como duplicada, complete-a e ignore o processamento. Se não corresponderem, o produtor pode ter reutilizado o identificador para conteúdos diferentes, ou os detalhes da encomenda podem ter mudado desde o primeiro processamento, pelo que deves enviar a mensagem para a fila de mensagens não entregues ou gerar um alerta, em vez de a descartar silenciosamente. - Se o processamento falhar por um motivo transitório, abandone a mensagem para que o Service Bus a volte a entregar, ou deixe o bloqueio expirar para que outro consumidor a receba.
A operação de criação é atómica, por isso serve tanto como verificação de deduplicação como de escrita. Dois consumidores que recebem cópias da mesma mensagem não podem ambos criar a encomenda. Um criar vence, e o outro devolve um conflito e descarta o seu duplicado em segurança.
Quando o processamento tiver de escrever mais do que um documento, use um lote transacional que inclua tanto o documento de deduplicação como os documentos empresariais dentro da mesma chave de partição. Como um lote transacional opera dentro de uma única partição lógica, escolha uma chave de partição que todos os documentos de uma mensagem partilhem. O lote grava todos os documentos em conjunto, ou nenhum deles, pelo que uma falha entre o processamento e a confirmação de receção não pode deixar o marcador de deduplicação e os dados de negócio dessincronizados. Um lote que tenta criar um documento que já existe devolve o código de estado 409 (Conflito), que identifica o documento duplicado.
Para tornar este consumidor resiliente também a novas tentativas de envio duplicado, ative a deteção de duplicados na fila. A deteção de duplicados impede envios repetidos dentro da respetiva janela de histórico, e o consumidor idempotente trata quaisquer duplicados que fiquem fora dessa janela ou que resultem da reentrega.
Passo seguinte
- As opções de mensagens assíncronas no Azure descrevem as escolhas da infraestrutura de mensagens que determinam as suas garantias de entrega e os requisitos de tratamento de duplicados.
Recursos relacionados
O padrão Transactional Outbox corresponde ao lado publicador deste padrão. Publica mensagens de forma fiável, confirmando-as na mesma transação que os dados empresariais.
O padrão de repetição permite que as aplicações lidem com falhas transitórias ao repetir operações, o que torna necessário o processamento idempotente, porque novas tentativas podem causar entregas duplicadas.
Resilient Hubs de Eventos do Azure e Funções do Azure design aplicam este padrão a funções que o Hubs de Eventos do Azure desencadeia, incluindo técnicas de deduplicação para fluxos de eventos.
Desenhar Funções do Azure para entrada idêntica fornece orientações para construir funções idempotentes que toleram invocações duplicadas.