Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Projete os consumidores de mensagens para que o processamento da mesma mensagem mais de uma vez tenha o mesmo efeito que processá-la uma vez. Sistemas de mensagens que garantem a entrega pelo menos uma vez podem entregar a mesma mensagem várias vezes. Sem resiliência contra duplicatas, o reprocessamento de uma mensagem pode criar registros duplicados, cobrar duas vezes um cliente ou ter outros efeitos indesejados.
Contexto e problema
Aplicações distribuídas normalmente trocam trabalho por meio de um broker de mensagens, em vez de chamadas síncronas diretas. A maioria dos agentes, incluindo Barramento de Serviço do Azure, Hubs de Eventos do Azure, Apache Kafka e RabbitMQ, fornecem entrega pelo menos uma vez. Essa garantia garante que uma mensagem chegue a um consumidor mesmo quando ocorrerem falhas, mas também significa que o agente pode entregar a mesma mensagem mais de uma vez.
As duplicatas surgem de várias fontes:
Tentativas do produtor
Um produtor envia uma mensagem, não recebe uma confirmação devido a uma falha de rede transitória ou tempo limite e envia a mensagem novamente. O broker agora mantém duas cópias, mesmo que o envio tenha sido bem-sucedido na primeira tentativa.
Reentrega após ausência de confirmação de recebimento
Um consumidor recebe e processa uma mensagem, mas não envia a confirmação de recebimento porque sofre uma falha, o bloqueio expira ou a confirmação é perdida. O agente pressupõe que a mensagem não foi processada e a entrega novamente.
Falhas do consumidor durante o processamento
Um consumidor conclui uma gravação de banco de dados, mas falha antes de reconhecer a mensagem. Quando outra instância seleciona a mensagem reenviada, ela repete a gravação.
É inviável garantir a entrega exatamente uma vez em um sistema distribuído. Mesmo os agentes que afirmam oferecer semântica exatamente uma vez garantem apenas as operações que controlam diretamente, como a entrega de mensagens aos consumidores ou a gravação de dados no próprio agente. Eles não podem garantir os efeitos colaterais externos que os consumidores produzem em outros sistemas. A solução durável não é eliminar a entrega duplicada. É para levar o consumidor a tolerar isso. Ao combinar a entrega pelo menos uma vez com um consumidor que ignora duplicatas, você obtém, na prática, processamento exatamente uma vez.
Solução
Torne o consumidor idempotente mantendo um registro das mensagens processadas e ignorando qualquer mensagem que já tenha sido vista anteriormente. O consumidor baseia essa decisão em um identificador estável que sobrevive à reentrega, consulta um armazenamento persistente para determinar se esse identificador já foi processado e processa a mensagem ou a descarta como duplicata.
As etapas a seguir descrevem o fluxo principal:
- Leia a mensagem e extraia sua chave de deduplicação.
- Verifique o armazenamento de eliminação de duplicação para essa chave.
- Se a chave existir, trate a mensagem como duplicada. Confirme e pare, retornando opcionalmente o resultado previamente registrado.
- Se a chave não existir, processe a mensagem e registre a chave em uma única operação atômica e reconheça a mensagem.
Escolha uma chave de deduplicação estável
A chave deve identificar de forma exclusiva e consistente a mensagem lógica ao longo de todas as reentregas. Use um identificador de mensagem atribuído pelo produtor ou uma chave de idempotência no nível de negócio que identifique a operação lógica específica, não um contexto de correlação compartilhado que pode estar presente em várias mensagens. No Barramento de Serviço do Azure, a propriedade MessageId serve para essa finalidade porque identifica exclusivamente a mensagem e sua carga útil. Não use CorrelationId como chave, pois ela agrupa mensagens relacionadas, como uma solicitação e suas respostas. Para eventos que seguem a especificação CloudEvents, a combinação dos atributos id e source identifica de forma única um evento e permanece estável ao longo das reentregas.
Não se baseie em identificadores de nível de transporte que o agente regenera na reentrega, nem em valores derivados de tentativas de entrega, pois esses valores mudam entre duplicatas e inviabilizam a detecção. Evite também derivar a chave de campos voláteis, como carimbos de data/hora de recebimento.
Quando mais de um consumidor independente processa o mesmo canal, como vários assinantes em um design de publicação e assinatura, cada consumidor processa legitimamente sua própria cópia de uma mensagem e precisa acompanhar independentemente a conclusão do processamento de mensagens. Se esses consumidores compartilharem um único armazenamento de eliminação de duplicação, defina a chave do registro com base em uma combinação da identidade do consumidor e da identidade da mensagem. Um armazenamento com chave apenas na identidade da mensagem permite que o primeiro consumidor suprima o processamento para todos os demais.
Decidir onde armazenar chaves processadas
Você tem duas opções comuns:
Uma tabela de eliminação de duplicação dedicada. O consumidor mantém uma tabela separada, às vezes chamada de caixa de entrada, que contém uma linha por chave processada. Essa abordagem mantém as preocupações de eliminação de duplicação separadas dos dados corporativos e funciona bem quando muitos tipos de mensagens compartilham um mecanismo.
A entidade de negócios em si. O consumidor armazena a chave no registro que a mensagem cria ou atualiza. Essa abordagem evita uma tabela separada, mas vincula a deduplicação à estrutura dos dados de negócio.
Confirme o marcador e os efeitos colaterais de forma atômica
O fluxo de verificar e depois processar tem uma janela de falha. Se o consumidor processar a mensagem e depois registrar a chave em uma etapa separada, uma falha entre as duas operações deixará os efeitos colaterais aplicados, mas a chave não será registrada; assim, a próxima entrega reprocessará a mensagem.
Trate essa janela de falha gravando o marcador de eliminação de duplicação e os efeitos colaterais de negócio na mesma transação. Quando ambos realizam o commit em conjunto, ou nenhum deles o faz, uma reentrega encontra o marcador de commit e o ignora, ou não encontra marcador algum (porque a transação sofreu rollback) e realiza o reprocessamento de forma segura. Essa variante transacional é o padrão de caixa de entrada, sendo o equivalente, no lado do consumo, ao padrão de caixa de saída transacional do lado da produção.
Evitar duplicatas simultâneas
Com a entrega pelo menos uma vez e consumidores concorrentes, duas instâncias podem receber cópias da mesma mensagem simultaneamente. Ambas podem passar pela verificação de existência antes que qualquer uma delas faça a confirmação; portanto, a verificação, por si só, não impede o processamento duplicado.
Imponha a correção no armazenamento de dados em vez de na lógica do aplicativo:
Use uma restrição de unicidade na chave de desduplicação. Ambas as transações tentam inserir a chave, mas apenas uma é bem-sucedida. O outro falha na restrição e trata a mensagem como duplicada. Essa abordagem torna o banco de dados o único árbitro da corrida.
Evite condições de corrida do tipo verificar e definir em caches. Um padrão que verifica uma chave e depois a define em duas operações distintas apresenta uma janela de tempo que permite que tentativas de reexecução concorrentes reivindiquem a mesma chave. Utilize uma operação de escrita condicional atômica — como uma inserção que falha em caso de conflito ou uma operação do tipo definir se ausente — para que a aquisição da chave seja uma única etapa atômica.
Lidar com efeitos colaterais que não podem participar da transação
Alguns processos não podem participar da transação de banco de dados do consumidor, como chamar uma API de terceiros ou gravar em um repositório externo. Para esses processos, use uma abordagem de duas fases:
- Registre a chave com um estado em andamento antes de realizar a ação externa.
- Execute o processo.
- Atualize o registro para ser concluído e armazene o resultado.
Na reentrega, um registro concluído permite que você evite repetir a chamada. Um registro em andamento sinaliza que uma tentativa anterior pode ter sido parcialmente concluída ou está sendo trabalhada por outro consumidor.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar esse padrão:
Prefira operações naturalmente idempotentes. Algumas operações são inerentemente idempotentes e não requerem escrituração de eliminação de duplicação. Uma upsert com chave em um identificador de negócio, uma operação de gravação que define um valor absoluto em vez de um incremento, ou uma requisição HTTP
PUTdirecionada a um identificador de recurso produz o mesmo resultado, independentemente de ser executada uma ou várias vezes.Às vezes, é possível tornar uma operação naturalmente idempotente por meio da transferência de estado via evento, na qual a mensagem carrega o estado absoluto resultante — como o novo status de um pedido —, permitindo que o consumidor a aplique como um upsert em vez de uma alteração relativa.
Tip
Projete priorizando a idempotência natural e adicione técnicas de desduplicação apenas para operações que não possam ser projetadas para serem naturalmente idempotentes.
Gerenciar o ciclo de vida dos registros de deduplicação. Os registros de deduplicação se acumulam, a menos que você defina um prazo de expiração para eles. Mantenha cada registro pelo menos pelo tempo em que o broker puder reenviar a mensagem original. Esta janela depende das tentativas máximas de entrega do agente, do seu tempo limite de bloqueio ou de visibilidade e do tempo de vida da mensagem. Defina um tempo de vida para os registros de eliminação de duplicação que exceda essa janela, de modo que uma reentrega tardia ainda encontre seu marcador. Excluir registros muito cedo reabre a janela para duplicatas. Leve em consideração as mensagens que um operador reenvia por meio de uma fila de mensagens mortas, pois um reenvio pode ocorrer muito tempo após a janela normal de reentrega.
Use uma estrutura de sistema de mensagens em vez de implementar a eliminação de duplicação manualmente. Implementar corretamente o armazenamento de eliminação de duplicação, a confirmação atômica e a limpeza de registros é algo sujeito a erros. As estruturas baseadas em mensagens fornecem esse padrão como um recurso interno.
Por exemplo, NServiceBus remove mensagens duplicadas recebidas com base no identificador da mensagem e fornece retenção e limpeza configuráveis para os dados de deduplicação. A caixa de entrada do consumidor do MassTransit rastreia as mensagens recebidas por meio de seus identificadores para garantir um comportamento de processamento exatamente uma vez.
A eliminação de duplicação no agente reduz, mas não elimina, a necessidade de lógica de consumidor idempotente. Algumas plataformas filtram duplicatas na camada de transporte. O recurso de detecção de duplicata do Barramento de Serviço do Azure descarta mensagens que apresentam um
MessageIdrepetido dentro de uma janela de tempo configurada, o que elimina duplicatas causadas por tentativas de reenvio do produtor. Esse recurso opera no lado de envio e dentro de uma janela limitada. Isso não impede que um consumidor processe a mesma mensagem duas vezes após uma reentrega, portanto você ainda precisa de lógica idempotente no consumidor. Trate os recursos da plataforma como uma primeira camada de defesa que reduz o volume de duplicatas, e não como substituto do padrão.Leve em consideração a ordem das mensagens. A eliminação de duplicação remove duplicatas, mas não garante a ordem. Se o consumidor depender da ordem de processamento, combine esse padrão com um mecanismo de ordenação, como Barramento de Serviço do Azure sessões de mensagem, ou inclua dados de sequência ou de versão que permitem ao consumidor rejeitar mensagens obsoletas.
Instrumento para observabilidade. Emita a chave de desduplicação e um identificador de correlação em logs estruturados e registre uma métrica para duplicatas detectadas. Uma taxa crescente de duplicatas pode indicar uma configuração incorreta do produtor, uma janela de confirmação ou de bloqueio subdimensionada, ou consumidores em estado inadequado. Use rastreamento de ponta a ponta e correlação para acompanhar uma mensagem entre serviços.
Propague a idempotência para chamadas subsequentes. Tornar um consumidor idempotente não protege os serviços que ele chama. Quando um consumidor invoca serviços downstream como parte do processamento, propague a chave de idempotência para que cada camada possa eliminar duplicatas de seu próprio trabalho.
Quando usar esse padrão
Use esse padrão quando:
Você consome mensagens de um agente que oferece entrega pelo menos uma vez, o que é o padrão para a maioria dos agentes.
O reprocessamento de uma mensagem produz resultados incorretos, como transações financeiras duplicadas, criação de recursos duplicados ou notificações repetidas.
Vários consumidores concorrentes processam o mesmo canal, o que torna provável a entrega simultânea duplicada.
Esse padrão pode não ser adequado quando:
Cada operação realizada pelo consumidor já é naturalmente idempotente; portanto, o reprocessamento é inofensivo, e a escrituração de eliminação de duplicação gera custos sem trazer benefícios.
A carga de trabalho pode tolerar os efeitos de um processamento duplicado ocasional, e o custo de um armazenamento de desduplicação é maior do que o impacto de uma duplicação.
Processamento idempotente além do sistema de mensagens
Esse padrão aplica a idempotência aos consumidores de mensagens, mas o processamento idempotente é um princípio de confiabilidade mais amplo. Qualquer operação que possa ser executada mais de uma vez em uma tarefa idêntica se beneficia dela. Esse princípio abrange transformações ETL (extração, transformação e carga) que reprocessam dados reexecutados, processamento de fluxo que retoma a partir de um ponto de verificação, tarefas agendadas que se sobrepõem ou são reiniciadas, e pontos de extremidade de webhook ou HTTP que recebem entregas duplicadas.
Em cada caso, a mesma técnica principal se aplica:
- Identifique a unidade de trabalho com uma chave estável.
- Registre o que você já processou.
- Ignore ou absorva duplicatas para que repetir o trabalho não altere o resultado.
Os mecanismos neste artigo, como chaves estáveis, marcadores atômicos e restrições exclusivas, são transferidos para esses contextos mesmo quando nenhum agente de mensagens está envolvido.
Design de carga de trabalho
Avalie como utilizar o padrão Consumidor Idempotente no design de uma carga de trabalho para atender aos objetivos e princípios abordados nos pilares da Estrutura bem arquitetada do Azure. A tabela a seguir fornece diretrizes sobre como esse padrão dá suporte às metas de cada pilar.
| Pilar | Como esse padrão apoia os objetivos do pilar |
|---|---|
| As decisões de design de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | Esse padrão permite que uma carga de trabalho utilize entrega pelo menos uma vez e tentativas de reexecução seguras sem corromper dados, transformando a entrega duplicada de um risco à integridade em uma condição tolerada. - RE:07 Autopreservação - Manipular falhas transitórias |
Se esse padrão introduzir compensações dentro de um pilar, considere-as em relação aos objetivos dos outros pilares.
Exemplo
O exemplo a seguir mostra um consumidor idempotente que processa pedidos do Barramento de Serviço do Azure e persiste o estado no Azure Cosmos DB for NoSQL.
Um produtor define o Barramento de Serviço MessageId como um identificador de ordem de nível empresarial. O consumidor recebe mensagens no modo PeekLock , que retorna uma mensagem se o consumidor não a concluir dentro da duração do bloqueio. O contêiner do Azure Cosmos DB do consumidor é particionado pelo identificador do pedido (/orderId) e define o documento id como o mesmo identificador de pedido, de modo que cada cópia de determinado pedido é direcionada para a mesma partição lógica, e o próprio registro do pedido serve como marcador de eliminação de duplicação.
O consumidor processa cada mensagem da seguinte maneira:
- Leia a mensagem e use
MessageIdcomo chave de desduplicação. - Crie o documento do pedido com
ide a chave de partição, ambos definidos como o identificador do pedido. - Se a operação de criação for bem-sucedida, conclua a mensagem para que o Barramento de Serviço a remova da fila.
- Se a criação falhar com o status HTTP 409 (Conflito) porque já existe um documento com esse
id, leia o documento existente e compare-o à mensagem atual. Se um hash de solicitação armazenado ou campos de negócios imutáveis corresponderem, trate a mensagem como duplicada, conclua-a e ignore o processamento. Se não corresponderem, o produtor pode ter reutilizado o identificador para conteúdo diferente, ou os detalhes do pedido podem ter mudado desde o primeiro processamento; portanto, encaminhe a mensagem para a fila de mensagens mortas ou emita um alerta, em vez de simplesmente descartá-la silenciosamente. - Se o processamento falhar por um motivo transitório, abandone a mensagem para que Barramento de Serviço a reenvia ou deixe o bloqueio expirar para que outro consumidor a receba.
A operação de criação é atômica; portanto, ela serve tanto para a verificação de eliminação de duplicação quanto para a gravação. Dois consumidores que recebem cópias da mesma mensagem não podem criar o pedido. Uma operação de criação é bem-sucedida, enquanto a outra retorna um conflito e descarta com segurança a sua duplicata.
Quando o processamento precisar gravar mais de um documento, use um lote transacional que inclua tanto o documento de desduplicação quanto os documentos de negócio dentro da mesma chave de partição. Como um lote transacional opera em uma única partição lógica, escolha uma chave de partição compartilhada por todos os documentos de uma mesma mensagem. O lote confirma a gravação de todos os documentos em conjunto ou de nenhum deles; assim, uma falha ocorrida entre o processamento e a confirmação não pode deixar o marcador de desduplicação e os dados corporativos dessincronizados. Um lote que tenta criar um documento já existente retorna o status 409 (conflito), o que identifica a duplicata.
Para tornar esse consumidor resiliente também a novas tentativas de envio de mensagens duplicadas, habilite a detecção de duplicatas na fila. A detecção de duplicatas suprime envios repetidos dentro de sua janela de histórico, e o consumidor idempotente trata quaisquer duplicatas que fiquem fora dessa janela ou que resultem de reenvios.
Próximas etapas
- As opções de mensagens assíncronas no Azure descrevem as opções de infraestrutura de mensagens que determinam as garantias de entrega e os requisitos de tratamento de duplicatas.
Recursos relacionados
O padrão de caixa de saída transacional representa o lado do editor nesse padrão. Ele publica mensagens de forma confiável, confirmando-as na mesma transação que os dados corporativos.
O padrão de repetição permite que os aplicativos lidem com falhas transitórias repetindo operações, o que torna o processamento idempotente necessário porque as tentativas podem causar entrega duplicada.
O design resiliente do Hubs de Eventos do Azure e do Azure Functions aplica esse padrão a funções acionadas pelo Hubs de Eventos do Azure, incluindo técnicas de desduplicação para fluxos de eventos.
A criação de Azure Functions para entrada idêntica fornece diretrizes para a criação de funções idempotentes que toleram invocações duplicadas.