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 al menos una entrega 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 de 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 contiene dos copias aunque el envío se realizó correctamente la primera vez.
Redelivery después de una confirmación que falta
Un consumidor recibe y procesa un mensaje, pero no puede confirmarlo porque el consumidor se bloquea, el bloqueo expira o se pierde la confirmación. El agente supone que el mensaje no se procesó y lo entrega de nuevo.
Errores de consumidor en el procesamiento intermedio
Un consumidor completa una escritura de base de datos, pero se bloquea antes de confirmar el mensaje. Cuando otra instancia recoge el mensaje redelivered, repite la escritura.
La entrega exactamente una vez a través de un sistema distribuido no es práctica para garantizar. Incluso los agentes que reclaman la semántica exactamente una vez solo garantizan las operaciones que controlan directamente, como entregar mensajes a los consumidores o escribir datos de nuevo en el agente. No pueden garantizar los efectos secundarios externos que realizan los consumidores en otros sistemas. La solución duradera no es eliminar la entrega duplicada. Es hacer que el consumidor lo tolera. 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 omita cualquier mensaje que haya visto antes. El consumidor claves esta decisión sobre un identificador estable que sobrevive a la entrega, comprueba un almacén persistente para determinar si ese identificador ya se procesó y procesa el mensaje o 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. Reconócelo 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 coherente el mensaje lógico en cada entrega. Use un identificador de mensaje asignado por el productor o una clave de idempoencia de nivel empresarial que identifique la operación lógica específica, no un contexto de correlación compartido que puedan llevar varios mensajes. 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 source atributos y id identifica de forma única un evento y permanece estable en redeliveries.
No clave en los identificadores de nivel de transporte que el agente vuelve a generar en redelivery o en los valores derivados de los intentos de entrega, ya que esos valores cambian entre duplicados y la detección de derrotas. 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, clave el registro en una composición de la identidad del consumidor y la identidad del mensaje. Un almacén con clave en la identidad del mensaje permite que el primer consumidor suprima el procesamiento 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 la desduplicación de parejas a la forma de los datos empresariales.
Confirmar el marcador y los efectos secundarios de forma atómica
El flujo check-then-process tiene una ventana de error. Si el consumidor procesa el mensaje y, a continuación, registra la clave en un paso independiente, un bloqueo entre las dos operaciones deja aplicados los efectos secundarios, pero la clave no se registra, por lo que la siguiente entrega vuelve a procesar el mensaje.
Solucione esta ventana de error escribiendo el marcador de desduplicación y los efectos secundarios empresariales en la misma transacción. Cuando ambos se confirman juntos o no, un reenvío encuentra el marcador confirmado y omite, o no encuentra ningún marcador porque la transacción se revierte y vuelve a procesar de forma segura. Esta variante transaccional es el patrón de bandeja de entrada y es el complemento del lado de consumo con el patrón de bandeja de salida transaccional en el lado de producción.
Protección contra duplicados simultáneos
En la entrega al menos una vez con varios consumidores competidores, dos instancias pueden recibir copias del mismo mensaje al mismo tiempo. Ambos pueden pasar la comprobación de existencia antes de ambas confirmaciones, por lo que la comprobación por sí sola no impide el procesamiento doble.
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 se realiza correctamente. 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 carreras de check-then-set en las memorias caché. Un patrón que comprueba una clave y, a continuación, lo establece en dos operaciones independientes tiene una ventana que permite que los reintentos simultáneos reclaman la clave. Use una escritura condicional atómica, como una inserción que produce un error en conflicto o una operación set-if-absent, de modo que la notificación de la clave sea un único paso atómico.
Controlar los efectos secundarios que no pueden unirse a 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 para que se complete y almacene el resultado.
En redelivery, un registro completado le permite omitir la repetición de la llamada. Un registro en curso indica que otro consumidor podría haber completado parcialmente un intento anterior o está trabajando en ellos.
Problemas y consideraciones
Tenga en cuenta los siguientes puntos a medida que decida cómo implementar este patrón:
Prefiere operaciones idempotentes naturalmente. Algunas operaciones son intrínsecamente idempotentes y no necesitan contabilidad de desduplicación. Una clave upsert en un identificador de negocio, una escritura que establece un valor absoluto en lugar de un incremento, o un HTTP
PUTen un identificador de recurso genera el mismo resultado si se ejecuta una o varias veces.A veces, puede realizar una operación idempotente a través de la transferencia de estado de transporte de eventos, donde el mensaje lleva el estado absoluto resultante, como el nuevo estado de un pedido, por lo que el consumidor lo aplica como upsert en lugar de un cambio relativo.
Tip
Diseñe primero la idempoencia natural y agregue técnicas de desduplicación solo para las operaciones que no se pueden hacer naturalmente idempotentes.
Administrar el ciclo de vida de los registros de desduplicación. Los registros de desduplicación se acumulan a menos que expiren. Conserve cada registro al menos siempre que el agente pueda volver a entregar el mensaje original. Esta ventana depende de los intentos de entrega máximos del agente, su tiempo de espera de bloqueo o visibilidad y el tiempo de vida del mensaje. Establezca un período de vida en los registros de desduplicación que supere esta ventana para que una entrega tardía todavía encuentre su marcador. La eliminación de registros demasiado pronto vuelve a abrir la ventana para los duplicados. Tenga en cuenta los mensajes que un operador vuelve a enviar desde una cola de mensajes fallidos, ya que una reenvío puede producirse mucho después de la ventana normal de reenvío.
Use un marco de mensajería en lugar de desduplicación gradual a mano. La implementación del almacén de desduplicación, la confirmación atómica y la limpieza de registros correctamente son propensas 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 desduplicación del agente reduce pero no elimina la necesidad de lógica de consumidor idempotente. Algunas plataformas filtran duplicados en la capa de transporte. Azure Service Bus detección de duplicados descarta los mensajes que llevan un repetido
MessageIddentro de un período de tiempo configurado, 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 después de una entrega, por lo que todavía necesita lógica de consumidor idempotente. Trate las características de la plataforma como una primera capa de defensa que reduce el volumen duplicado, no como reemplazo del patrón.Cuenta para el orden de 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 errores de configuración del productor, una confirmación o ventana de bloqueo infradimensionada o consumidores incorrectos. Use el seguimiento y la correlación de un extremo a otro para seguir un mensaje entre servicios.
Propague la idempoencia a las llamadas de nivel inferior. La realización de un idempotente de consumidor no protege los servicios a los que llama. Cuando un consumidor invoca servicios de bajada como parte del procesamiento, propague la clave de idempoencia para que cada nivel pueda desduplicar su propio trabajo.
Cuándo usar este patrón
Use este patrón cuando:
Consume mensajes de un agente que proporciona al menos una entrega 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 costo de un almacén de desduplicación supera el impacto de un duplicado.
Procesamiento idempotente más allá de la mensajería
Este patrón aplica la idempoencia a los consumidores de mensajes, pero el procesamiento idempotente es un principio de confiabilidad 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, carga (ETL) que reprocesan datos reproducidos, procesamiento de flujos que se reanuda desde un punto de control, trabajos programados que se superponen o reinician, y webhook 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 para NoSQL.
Un productor establece el Service Bus MessageId en un identificador de pedido de nivel de negocio. 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 Azure Cosmos DB del consumidor crea particiones de contenedor 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 correctamente, complete el mensaje para que Service Bus lo quite de la cola.
- Si se produce un error en la creación con un estado HTTP 409 (conflicto) porque ya existe un documento con el que
idya existe, lea el documento existente y compárelo con el mensaje actual. Si un hash de solicitud almacenada o los campos empresariales inmutables coinciden, trate el mensaje como duplicado, recompálelo y omita el procesamiento. Si no coinciden, es posible que el productor haya reutilizado el identificador de contenido diferente o que los detalles del pedido hayan cambiado desde que se procesó por primera vez, por lo que el mensaje no enviados o generar una alerta en lugar de descartarlo de forma silenciosa. - 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 funciona dentro de una sola partición lógica, elija una clave de partición que todos los documentos de un recurso compartido de mensajes. El lote confirma todos los documentos juntos o ninguno en absoluto, por lo que un bloqueo entre el procesamiento y la confirmación no puede dejar el marcador de desduplicación y los datos empresariales fuera de sincronización. Un lote que intenta crear un documento que ya existe devuelve un estado 409 (conflicto), que identifica el duplicado.
Para que este consumidor sea resistente a 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 controla los duplicados que se encuentran fuera de esa ventana o que resultan de la reeplicación.
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 confiable al confirmarlos en la misma transacción que los datos empresariales.
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 de Azure Event Hubs y Azure Functions resistentes aplica este patrón a las funciones que Azure Event Hubs desencadenadores, incluidas las técnicas de desduplicación para flujos de eventos.
El diseño de Azure Functions para entradas idénticas proporciona instrucciones para crear funciones idempotentes que toleran invocaciones duplicadas.