Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Diseñe consumidores de mensajes para que el procesamiento del mismo mensaje más de una vez tenga el mismo efecto que procesarlo una vez. Los sistemas de mensajería que garantizan la entrega al menos una vez pueden entregar el mismo mensaje varias veces. Sin resistencia frente a duplicados, volver a procesar un mensaje puede crear registros duplicados, cargar doblemente un cliente o tener otros efectos no deseados.
Contexto y problema
Normalmente, las aplicaciones distribuidas intercambian trabajo a través de un agente de mensajes en lugar de llamadas sincrónicas directas. La mayoría de los agentes, incluidos Azure Service Bus, Azure Event Hubs, Apache Kafka y RabbitMQ, proporcionan una entrega al menos una vez. Esta garantía garantiza que un mensaje llegue a un consumidor incluso cuando se produzcan errores, pero también significa que el agente puede entregar el mismo mensaje más de una vez.
Los duplicados surgen de varios orígenes:
Reintentos del productor
Un productor envía un mensaje, no recibe una confirmación debido a un error de red transitorio o tiempo de espera y envía el mensaje de nuevo. El agente ahora tiene dos copias, aunque el envío se realizó correctamente a la primera.
Reentrega tras la falta de acuse de recibo
Un consumidor recibe y procesa un mensaje, pero no confirma su recepción porque el consumidor se bloquea, el bloqueo caduca o el acuse de recibo se pierde. El agente supone que el mensaje no se procesó y lo entrega de nuevo.
Fallos del consumidor durante el procesamiento
Un consumidor completa una escritura de base de datos, pero se bloquea antes de confirmar el mensaje. Cuando otra instancia recibe el mensaje reenviado, repite la operación de escritura.
La entrega única en un sistema distribuido es imposible de garantizar en la práctica. Incluso los agentes que afirman ofrecer semántica de exactamente una vez solo garantizan las operaciones que controlan directamente, como entregar mensajes a los consumidores o volver a escribir datos en el agente. No pueden garantizar los efectos colaterales externos que llevan a cabo los consumidores en otros sistemas. La solución duradera no es eliminar la entrega duplicada. Es para hacer que el consumidor lo tolere. Cuando se combina la entrega al menos una vez con un consumidor que omite duplicados, se logra un procesamiento exactamente una vez de forma eficaz.
Solución
Haga que el consumidor sea idempotente manteniendo un registro de los mensajes procesados y omitiendo cualquier mensaje que ya haya visto. El consumidor basa esta decisión en un identificador estable que sobrevive a la reentrega, consulta un almacenamiento persistente para determinar si ese identificador ya se ha procesado y, o bien procesa el mensaje o bien lo descarta como duplicado.
En los pasos siguientes se describe el flujo principal:
- Lea el mensaje y extraiga su clave de desduplicación.
- Compruebe el almacén de desduplicación para esa clave.
- Si la clave existe, trate el mensaje como duplicado. Confírmelo y detenga y, opcionalmente, devuelva el resultado registrado anteriormente.
- Si la clave no existe, procese el mensaje y registre la clave en una sola operación atómica, confirme el mensaje.
Elección de una clave de desduplicación estable
La clave debe identificar de forma única y de manera consistente el mensaje lógico en cada reentrega. Utilice un identificador de mensaje asignado por el productor o una clave de idempotencia de nivel de negocio que identifique la operación lógica específica, no un contexto de correlación compartido que varios mensajes pueden contener. En Azure Service Bus, la MessageId propiedad sirve para este propósito porque identifica de forma única el mensaje y su carga. No use CorrelationId como clave, ya que agrupa mensajes relacionados, como una solicitud y sus respuestas. Para los eventos que siguen la especificación CloudEvents, la combinación de los atributos source y id identifica de forma unívoca un evento y se mantiene estable en los reenvíos.
No se base en identificadores de nivel de transporte que el agente regenera en la reentrega ni en valores derivados de los intentos de entrega, porque esos valores cambian de un duplicado a otro e impiden la detección. Evite también derivar la clave de campos volátiles, como las marcas de tiempo de recepción.
Cuando más de un consumidor independiente procesa el mismo canal, como varios suscriptores en un diseño de publicación-suscripción, cada consumidor procesa legítimamente su propia copia de un mensaje y necesita realizar un seguimiento independiente de la finalización del procesamiento de mensajes. Si esos consumidores comparten un almacén de desduplicación, use como clave del registro una clave compuesta por el identificador del consumidor y el identificador del mensaje. Un almacén indizado únicamente por la identidad del mensaje permite que el primer consumidor impida el procesamiento por parte de todos los demás.
Decidir dónde almacenar las claves procesadas
Tiene dos opciones comunes:
Una tabla de desduplicación dedicada. El consumidor mantiene una tabla independiente, a veces denominada bandeja de entrada, que contiene una fila por clave procesada. Este enfoque mantiene los problemas de desduplicación separados de los datos empresariales y funciona bien cuando muchos tipos de mensajes comparten un mecanismo.
La propia entidad empresarial. El consumidor almacena la clave en el registro que el mensaje crea o actualiza. Este enfoque evita una tabla independiente, pero vincula la deduplicación con la estructura de los datos de negocio.
Confirmar el marcador y los efectos secundarios de forma atómica
El flujo de comprobación y posterior procesamiento tiene una ventana de fallo. Si el consumidor procesa el mensaje y, a continuación, registra la clave en un paso independiente, un fallo entre las dos operaciones deja aplicados los efectos secundarios, pero la clave sin registrar, por lo que la siguiente entrega del mensaje hace que se procese de nuevo.
Solucione esta ventana de error escribiendo el marcador de desduplicación y los efectos secundarios empresariales en la misma transacción. Cuando ambas cosas se confirman a la vez o no se confirma ninguna, una reentrega encuentra el marcador de confirmación y omite el reprocesamiento, o bien no encuentra ningún marcador porque la transacción se revirtió y vuelve a procesarlo de forma segura. Esta variante transaccional es el patrón de bandeja de entrada, y es la contraparte del lado del consumo del patrón de bandeja de salida transaccional del lado del productor.
Protección contra duplicados simultáneos
En la entrega al menos una vez con varios consumidores de la competencia, dos instancias pueden recibir copias del mismo mensaje al mismo tiempo. Ambos pueden superar la comprobación de existencia antes de que cualquiera de los dos confirme, por lo que la comprobación por sí sola no impide el procesamiento duplicado.
Aplique la corrección en el almacén de datos en lugar de en la lógica de la aplicación:
Use una restricción única en la clave de desduplicación. Ambas transacciones intentan insertar la clave, pero solo una lo consigue. El otro produce un error en la restricción y trata el mensaje como duplicado. Este enfoque hace que la base de datos sea el único árbitro de la carrera.
Evite las condiciones de carrera de tipo check-then-set en las cachés. Un patrón que comprueba una clave y luego la establece mediante dos operaciones independientes deja una ventana que permite que los reintentos simultáneos reclamen la clave. Utilice una escritura condicional atómica, como una inserción que falle en caso de conflicto o una operación set-if-absent, de modo que adquirir la clave sea un único paso atómico.
Gestionar los efectos secundarios que no pueden integrarse en la transacción
Algunos procesos no pueden participar en la transacción de base de datos del consumidor, como llamar a una API de terceros o escribir en un almacén externo. Para estos procesos, use un enfoque de dos fases:
- Registre la clave con un estado en curso antes de realizar la acción externa.
- Realice el proceso.
- Actualice el registro a completado y almacene el resultado.
En redelivery, un registro completado le permite omitir la repetición de la llamada. Un registro en progreso indica que un intento anterior podría haberse completado parcialmente o que otro consumidor está trabajando en él.
Problemas y consideraciones
Tenga en cuenta los siguientes puntos a medida que decida cómo implementar este patrón:
Prefiere operaciones naturalmente idempotentes. Algunas operaciones son intrínsecamente idempotentes y no necesitan contabilidad de desduplicación. Una operación de upsert basada en un identificador empresarial, una operación de escritura que establece un valor absoluto en lugar de un incremento, o una solicitud HTTP
PUTa un identificador de recurso produce el mismo resultado tanto si se ejecuta una vez como muchas veces.A veces, puede hacer que una operación sea idempotente de forma natural mediante la transferencia de estado transportado por eventos, donde el mensaje incluye el estado absoluto resultante, como el nuevo estado de un pedido, de modo que el consumidor lo aplica como una operación de inserción o actualización en lugar de un cambio relativo.
Tip
Priorice la idempotencia natural en el diseño y añada técnicas de desduplicación solo para las operaciones que no puedan ser idempotentes de forma natural.
Administrar el ciclo de vida de los registros de desduplicación. Los registros de desduplicación se acumulan a menos que se eliminen. Conserve cada registro al menos siempre que el agente pueda volver a entregar el mensaje original. Esta ventana depende del número máximo de intentos de entrega del agente, del tiempo de bloqueo o de espera de visibilidad, y del período de vida del mensaje. Establezca un período de vida para los registros de desduplicación que supere esta ventana, para que una reentrega tardía aún encuentre su marcador. La eliminación de registros demasiado pronto vuelve a abrir la ventana para los duplicados. Considere los mensajes que un operador reenvía desde una cola de mensajes no entregados, ya que un reenvío de este tipo puede producirse mucho después de la ventana normal de nueva entrega.
Use un marco de mensajería en lugar de implementar la desduplicación manualmente. Implementar correctamente el almacén de desduplicación, la confirmación atómica y la limpieza de registros es una operación propensa a errores. Los marcos basados en mensajes proporcionan este patrón como una característica integrada.
Por ejemplo, NServiceBus desduplica los mensajes entrantes por su identificador de mensaje y proporciona retención y limpieza configurables para los datos de desduplicación. La bandeja de entrada del consumidor MassTransit realiza un seguimiento de los mensajes recibidos por su identificador de mensaje para proporcionar un comportamiento de consumidor exactamente una vez.
La eliminación de duplicados del agente reduce, pero no elimina, la necesidad de que exista una lógica del consumidor idempotente. Algunas plataformas filtran duplicados en la capa de transporte. La detección de duplicados de Azure Service Bus descarta los mensajes que contienen un
MessageIdrepetido dentro de una ventana de tiempo configurada, lo que suprime los duplicados causados por reintentos de envío del productor. Esta característica funciona en el lado de envío y dentro de una ventana limitada. No impide que un consumidor procese el mismo mensaje dos veces tras una reentrega, por lo que sigue siendo necesaria una lógica idempotente en el consumidor. Considere las funcionalidades de la plataforma como una primera capa de defensa que reduce el volumen de duplicados, no como un sustituto del patrón.Ten en cuenta el orden de los mensajes. La desduplicación quita duplicados, pero no garantiza el orden. Si el consumidor depende del orden de procesamiento, combine este patrón con un mecanismo de ordenación, como Azure Service Bus sesiones de mensajes, o incluya datos de secuencia o versión que permita al consumidor rechazar mensajes obsoletos.
Instrumento para la observabilidad. Emita la clave de desduplicación y un identificador de correlación en los registros estructurados y realice un seguimiento de una métrica para los duplicados detectados. Una tasa creciente de duplicados puede indicar una mala configuración del productor, un tamaño insuficiente de la ventana de confirmación o de bloqueo, o consumidores con problemas. Utilice el seguimiento y la correlación de extremo a extremo para seguir un mensaje a través de varios servicios.
Propague la idempoencia a las llamadas de nivel inferior. Hacer idempotente un consumidor no protege a los servicios que invoca. Cuando un consumidor invoca servicios posteriores durante el procesamiento, debe propagar la clave de idempotencia para que cada nivel pueda desduplicar su propio trabajo.
Cuándo usar este patrón
Use este patrón cuando:
Consumes mensajes de un agente que ofrece entrega al menos una vez, que es el valor predeterminado para la mayoría de los agentes.
El reprocesamiento de un mensaje genera resultados incorrectos, como transacciones financieras duplicadas, creación de recursos duplicados o notificaciones repetidas.
Varios consumidores competidores procesan el mismo canal, lo que hace que la entrega duplicada simultánea sea probable.
Este patrón podría no ser adecuado cuando:
Todas las operaciones que realiza el consumidor ya son idempotentes naturalmente, por lo que el reprocesamiento es inofensivo y la contabilidad de desduplicación agrega costo sin beneficio.
La carga de trabajo puede tolerar los efectos del procesamiento duplicado ocasional, y el coste de mantener un almacén de desduplicación es mayor que el impacto de un duplicado.
Procesamiento idempotente más allá de la mensajería
Este patrón aplica el principio de idempotencia a los consumidores de mensajes, pero el procesamiento idempotente es un principio de fiabilidad más amplio. Cualquier operación que pueda ejecutarse más de una vez sobre una tarea idéntica se beneficia de ella. Este principio incluye transformaciones de extracción, transformación y carga (ETL) que reprocesan datos repetidos, procesamiento en flujo que se reanuda desde un punto de control, trabajos programados que se solapan o se reinician, y webhooks o puntos de conexión HTTP que reciben entregas duplicadas.
En cada caso, se aplica la misma técnica básica:
- Identifique la unidad de trabajo con una clave estable.
- Registre lo que ya ha procesado.
- Omita o absorba duplicados para que la repetición del trabajo no cambie el resultado.
Los mecanismos de este artículo, como claves estables, marcadores atómicos y restricciones únicas, se transfieren a esos contextos incluso cuando no hay ningún agente de mensajes implicado.
Diseño de cargas de trabajo
Evalúe cómo usar el patrón Idempotent Consumer en el diseño de una carga de trabajo para abordar los objetivos y principios descritos en los pilares de Azure Well-Architected Framework. En la tabla siguiente se proporciona una guía sobre cómo este patrón apoya los objetivos de cada pilar.
| Fundamento | Cómo apoya este patrón los objetivos de los pilares |
|---|---|
| Las decisiones de diseño de fiabilidad ayudan a que su carga de trabajo sea resiliente a fallos y garantizan que se recupere a un estado de pleno funcionamiento después de que se produzca un fallo. | Este patrón permite que una carga de trabajo use al menos una entrega y reintentos seguros sin dañar los datos, lo que convierte la entrega duplicada de un riesgo de corrección en una condición tolerada. - RE:07 Autopreservación - Controlar errores transitorios |
Si este patrón introduce concesiones dentro de un pilar, considérelas en relación con los objetivos de los otros pilares.
Example
En el ejemplo siguiente se muestra un consumidor idempotente que procesa los pedidos de Azure Service Bus y conserva el estado en Azure Cosmos DB for NoSQL.
Un productor establece el Service Bus MessageId como un identificador de pedido a nivel empresarial. El consumidor recibe mensajes en el modo PeekLock , que vuelve a entregar un mensaje si el consumidor no lo completa dentro de la duración del bloqueo. El contenedor de Azure Cosmos DB del consumidor crea particiones en el identificador de pedido (/orderId) y establece el documento id en el mismo identificador de pedido, por lo que cada copia de un orden determinado se resuelve en la misma partición lógica y el propio registro de pedido actúa como marcador de desduplicación.
El consumidor procesa cada mensaje de la siguiente manera:
- Lea el mensaje y úselo
MessageIdcomo clave de desduplicación. - Cree el documento de pedido con
idy la clave de partición establecida en el identificador de pedido. - Si la creación se realiza con éxito, complete el mensaje para que Service Bus lo elimine de la cola.
- Si la creación falla con el estado HTTP 409 (Conflict) porque ya existe un documento con ese
id, lea el documento existente y compárelo con el mensaje actual. Si coinciden el hash de una solicitud almacenada o los campos de negocio inmutables, tratar el mensaje como duplicado, marcarlo como completado y omitir su procesamiento. Si no coinciden, es posible que el productor haya reutilizado el identificador para contenido diferente o que los detalles del pedido hayan cambiado desde que se procesó por primera vez, así que envía el mensaje a la cola de mensajes fallidos o genera una alerta en lugar de descartarlo silenciosamente. - Si se produce un error en el procesamiento por un motivo transitorio, abandone el mensaje para que Service Bus vuelva a entregarlo o deje que el bloqueo expire para que otro consumidor lo reciba.
La operación de creación es atómica, por lo que actúa como comprobación de desduplicación y escritura. Dos consumidores que reciben copias del mismo mensaje no pueden crear el pedido. Una creación gana y la otra devuelve un conflicto y descarta de forma segura su duplicado.
Cuando el procesamiento debe escribir más de un documento, use un lote transaccional que incluya tanto el documento de desduplicación como los documentos empresariales dentro de la misma clave de partición. Dado que un lote transaccional se ejecuta dentro de una sola partición lógica, elija una clave de partición que compartan todos los documentos de un mismo mensaje. El lote confirma todos los documentos a la vez, o ninguno, por lo que un fallo entre el procesamiento y el acuse de recibo no puede dejar desincronizados el marcador de desduplicación y los datos de negocio. Un lote que intenta crear un documento que ya existe devuelve un estado 409 (Conflict), que identifica el documento duplicado.
Para que este consumidor resista también los reintentos de envío duplicados, habilite la detección de duplicados en la cola. La detección de duplicados suprime los envíos repetidos dentro de su ventana de historial, y el consumidor idempotente gestiona cualquier duplicado que quede fuera de esa ventana o que resulte de la reentrega.
Paso siguiente
- Las opciones de mensajería asincrónica en Azure describen las opciones de infraestructura de mensajería que determinan las garantías de entrega y los requisitos de control de duplicados.
Recursos relacionados
El patrón de bandeja de salida transaccional es el lado del publicador de este patrón. Publica mensajes de forma fiable al confirmarlos en la misma transacción que los datos de negocio.
El patrón de reintento permite a las aplicaciones controlar errores transitorios mediante operaciones de reintento, lo que hace que el procesamiento idempotente sea necesario porque los reintentos pueden provocar la entrega duplicada.
El diseño resistente de Azure Event Hubs y Azure Functions aplica este patrón a las funciones que desencadena Azure Event Hubs, incluidas técnicas de deduplicación para secuencias de eventos.
El diseño de Azure Functions para entradas idénticas proporciona instrucciones para crear funciones idempotentes que toleran invocaciones duplicadas.