Diseño resistente de Event Hubs y Functions

El control de errores, el diseño para la idempotencia y la administración del comportamiento de reintentos son algunas de las medidas críticas que puede tomar para asegurarse de que las funciones desencadenadas de Event Hubs son resistentes y capaces de controlar grandes volúmenes de datos. En este artículo se tratan estos conceptos fundamentales y se ofrecen recomendaciones para soluciones de streaming de eventos sin servidor.

Azure proporciona tres servicios de mensajería principales que se pueden usar con Azure Functions para admitir una amplia gama de escenarios únicos controlados por eventos. Debido a su modelo de consumidor con particiones y a la capacidad de ingerir datos a una velocidad elevada, Azure Event Hubs se usa normalmente para escenarios de streaming de eventos y de macrodatos. Para obtener una comparación detallada de los servicios de mensajería de Azure, consulte Elección entre servicios de mensajería de Azure: Event Grid, Event Hubs y Service Bus.

Ventajas y desafíos del streaming

Comprender las ventajas y los inconvenientes de los flujos le ayuda a apreciar cómo funciona un servicio cómo Event Hubs. También necesita este contexto al tomar decisiones arquitectónicas de impacto notable, solucionar problemas y optimizar el rendimiento. Tenga en cuenta los siguientes conceptos clave sobre las soluciones que incluyen Event Hubs y Functions:

  • Las secuencias no son colas: Event Hubs, Kafka y otras ofertas similares creadas en el modelo de consumidor con particiones no admiten intrínsecamente algunas de las características principales de un agente de mensajes como Service Bus. Quizás el mayor indicador de esto es el hecho de que las lecturas son no destructivas. Esto significa que los datos leídos por el host de Functions no se eliminan posteriormente. En su lugar, los mensajes son inmutables y se conservan para que otros consumidores los lean, lo que incluye la posibilidad que el mismo cliente lo vuelva a leer. Por esta razón, las soluciones que implementan patrones como consumidores paralelos son más adecuadas para un agente de mensajes tradicional.

  • Falta compatibilidad con mensajes fallidos heredados: un canal de mensajes fallidos no es una característica nativa de Event Hubs o Kafka. A menudo, el concepto de mensajes fallidos se integra en una solución de streaming para tener en cuenta los datos que no se pueden procesar. Esta funcionalidad no es intencionadamente un elemento innato de Event Hubs y solo se agrega en el lado del consumidor para producir un comportamiento o efecto similares. Si necesita compatibilidad con los mensajes falldos, podría revisar la elección del servicio de mensajes de streaming.

  • Una unidad de trabajo es una partición: en un agente de mensajes tradicional, una unidad de trabajo es un único mensaje. En una solución de streaming, una partición se suele considerar la unidad de trabajo. Si cada evento de un centro de eventos se considera un mensaje discreto que requiere ser tratado como una operación de procesamiento de pedidos o una transacción financiera, lo más probable es que sea un indicio de que se está utilizando el servicio de mensajería incorrecto.

  • Sin filtrado en el lado del servidor: una de las razones por las que Event Hubs es capaz de ofrecer una escala y un rendimiento enormes se debe a la baja sobrecarga en el propio servicio. Características como el filtrado en el lado del servidor, los índices y la coordinación entre agentes no forman parte de la arquitectura de Event Hubs. En ocasiones, las funciones se usan para filtrar eventos enrutándolos a otras instancias de Event Hubs en función del contenido del cuerpo o el encabezado. Este enfoque es común en el streaming de eventos, pero incluye la advertencia de que la función inicial lee y evalúa cada evento.

  • Cada lector debe leer todos los datos: dado que el filtrado en el lado del servidor no está disponible, un consumidor lee secuencialmente todos los datos de una partición. Esto incluye datos que pueden no ser pertinentes o incluso podrían tener un formato incorrecto. Hay varias opciones e incluso estrategias que se pueden usar para compensar estos desafíos y que se tratarán más adelante en esta sección.

Estas decisiones de diseño significativas permiten a Event Hubs hacer lo que mejor hace: admitir una gran afluencia de eventos y proporcionar un servicio sólido y resistente para que los consumidores puedan leer de él. Cada aplicación consumidora tiene la responsabilidad de mantener sus propios desplazamientos del lado cliente o el cursor para esos eventos. La baja sobrecarga hace que Event Hubs sea una opción asequible y eficaz para el streaming de eventos.

Idempotencia

Uno de los principios básicos de Azure Event Hubs es el concepto de entrega al menos una vez. Este enfoque garantiza que los eventos siempre se entreguen. También significa que los consumidores, como una función, pueden recibir eventos más de una vez, incluso varias veces. Por esta razón, es importante que una función desencadenada por el centro de eventos admita el patrón de consumidor idempotente.

Trabajar bajo la suposición de entrega al menos una vez, especialmente en el contexto de una arquitectura controlada por eventos, es un enfoque responsable para procesar eventos de forma confiable. La función debe ser idempotente para que el resultado de procesar el mismo evento varias veces sea el mismo que el de procesarlo una vez.

Eventos duplicados

Hay varios escenarios diferentes que podrían dar lugar a la entrega de eventos duplicados a una función:

  • Puntos de comprobación: si el host de Azure Functions se bloquea o no se cumple el umbral establecido para la frecuencia del punto de comprobación por lotes, no se creará un punto de comprobación. Como resultado, el desplazamiento del consumidor no está avanzado y la próxima vez que se invoque la función, se reanudará desde el último punto de comprobación. Es importante tener en cuenta que los puntos de comprobación se producen en el nivel de la partición para cada consumidor.

  • Eventos duplicados publicados: hay muchas técnicas que podrían mitigar la posibilidad de que el mismo evento se publique en una secuencia; sin embargo, sigue siendo responsabilidad del consumidor controlar los duplicados de forma idempotente.

  • Ausencia de confirmaciones: en algunas situaciones, una solicitud saliente a un servicio puede ser correcta; sin embargo, nunca se recibe una confirmación (ACK) del servicio. Esto puede dar lugar a la percepción de que la llamada saliente ha generado un error y a iniciar una serie, reintentos u otros resultados de la función. Al final, se podrían publicar eventos duplicados o bien podría no crearse un punto de comprobación.

Técnicas de desduplicación

El diseño de las funciones para una entrada idéntica debe ser el enfoque predeterminado que se toma cuando se empareja con el enlace de desencadenador de Event Hubs. Debe tener en cuenta las siguientes técnicas:

  • Búsqueda de duplicados: antes de procesarlo, siga los pasos necesarios para validar que se debe procesar el evento. En algunos casos, esto requerirá una investigación para confirmar que sigue siendo válido. También podría ser posible que el control del evento ya no sea necesario debido a la actualización de datos o a la lógica que invalida el evento.

  • Diseño de eventos para la idempotencia: al proporcionar información adicional dentro de la carga del evento, puede que sea posible asegurarse de que procesarlo varias veces no tendrá ningún efecto perjudicial. Tome el ejemplo de un evento que incluye una cantidad para retirar de una cuenta bancaria. Si no se controla de forma responsable, es posible que pueda disminuir el saldo de una cuenta varias veces. Sin embargo, si el mismo evento incluye el saldo actualizado de la cuenta, podría usarse para realizar una operación upsert en el saldo de la cuenta bancaria. En ocasiones, este enfoque de transferencia de estado controlada por eventos requiere una coordinación entre productores y consumidores y debe usarse cuando tenga sentido para los servicios participantes.

Control de errores y reintentos

El control de errores y los reintentos son algunas de las calidades más importantes de las aplicaciones distribuidas controladas por eventos y Functions no es ninguna excepción. Para las soluciones de streaming de eventos, la necesidad de una compatibilidad adecuada con el control de errores es fundamental, ya que miles de eventos pueden convertirse rápidamente en un número igual de errores si no se controlan correctamente.

Instrucciones de control de errores

Sin el control de errores, puede ser complicado implementar reintentos, detectar excepciones en el entorno de ejecución e investigar problemas. Cada función debe tener al menos algún control de errores o de nivel. Algunas directrices recomendadas son las siguientes:

  • Use Application Insights: habilite y use Application Insights para registrar los errores y supervisar el estado de las funciones. Tenga en cuenta las opciones de muestreo configurables para los escenarios en los que se procesa un gran volumen de eventos.

  • Agregue un control de errores estructurado: aplique las construcciones de control de errores adecuadas para que cada lenguaje de programación capture, registre y detecte las excepciones previstas y las no controladas en el código de la función. Por ejemplo, use un bloque try/catch en C#, Java y JavaScript y aproveche los bloques try y except de Python para controlar las excepciones.

  • Registro: capturar una excepción durante la ejecución ofrece una oportunidad para registrar información crítica que podría usarse para detectar, reproducir y corregir problemas de forma confiable. Registre la excepción, no solo el mensaje, sino el cuerpo, la excepción interna y otros artefactos que le resultarán útiles más adelante.

  • No capture y omita excepciones: una de las peores cosas que puede hacer es capturar una excepción y no hacer nada con ella. Si captura una excepción genérica, regístrela en algún lugar. Si no los registra, es difícil investigar los errores y problemas notificados.

Reintentos

La implementación de la lógica de reintentos en una arquitectura de streaming de eventos puede ser compleja. La compatibilidad con los tokens de cancelación, los recuentos de reintentos y las estrategias de retroceso exponencial son solo algunas de las consideraciones que lo convierten en un desafío. Afortunadamente, Functions proporciona directivas de reintento que pueden realizar muchas de estas tareas que normalmente codificaría usted mismo.

Entre los factores importantes que se deben tener en cuenta al usar directivas de reintento con el enlace de Event Hubs, se incluyen los siguientes:

  • Evite los reintentos indefinidos: cuando la configuración del número máximo de reintentos se establece en un valor de -1, la función hará reintentos indefinidamente. En general, los reintentos indefinidos se deben usar con moderación con Functions y casi nunca con el enlace de desencadenador de Event Hubs.

  • Elija la estrategia de reintentos adecuada: una estrategia de retraso fijo puede ser óptima para escenarios que reciben presión de vuelta de otros servicios de Azure. En estos casos, el retraso puede ayudar a evitar la limitación y otras restricciones detectadas en esos servicios. La estrategia de retroceso exponencial ofrece más flexibilidad para los intervalos de retraso de reintentos y se usa normalmente en la integración con servicios de terceros, puntos de conexión REST y otros servicios de Azure.

  • Mantenga los intervalos y los recuentos de reintentos bajos: cuando sea posible, intente mantener un intervalo de reintentos inferior a un minuto. Además, mantenga el número máximo de reintentos en un valor razonablemente bajo. Esta configuración es especialmente pertinente cuando la ejecución se realiza en el plan de Consumo de Functions.

  • Patrón de interruptor: se espera un error transitorio de vez en cuando y un caso de uso natural para los reintentos. Sin embargo, si se produce un número significativo de errores o problemas durante el procesamiento de la función, puede que tenga sentido detener la función, solucionar los problemas y volver a empezar más adelante.

Un punto importante para las directivas de reintento en Functions es que es una característica de mejor esfuerzo para volver a procesar eventos. No sustituye la necesidad del control de errores, el registro y otros patrones importantes que proporcionan resistencia al código.

Estrategias para errores y datos dañados

Hay varios enfoques significativos que puede usar para compensar los problemas que surgen debido a errores o a datos no fiables en un flujo de eventos. Algunas estrategias fundamentales son las siguientes:

  • Detener el envío y la lectura: pausar la lectura y escritura de eventos para corregir el problema subyacente. La ventaja de este enfoque es que los datos no se perderán y las operaciones se podrán reanudar una vez que se haya implantado una corrección. Este enfoque puede requerir un componente de interruptor en la arquitectura y, posiblemente, una notificación a los servicios afectados para lograr una pausa. En algunos casos, puede que sea necesario detener una función hasta que se resuelvan los problemas.

  • Quitar mensajes: si los mensajes no son importantes o se consideran no críticos, considere la posibilidad de seguir adelante y no procesarlos. Esto no funciona en escenarios que requieren una fuerte coherencia, como la grabación de movimientos de una partida de ajedrez o las transacciones basadas en finanzas. Se recomienda establecer un control de errores dentro de una función para capturar y quitar mensajes que no se pueden procesar.

  • Reintentos: hay muchas situaciones que pueden garantizar el reprocesamiento de un evento. El escenario más común sería un error transitorio al llamar a otro servicio o dependencia. Los errores de red, los límites y disponibilidad del servicio y la fuerte coherencia son quizás los casos de uso más frecuentes que justifican los intentos de reprocesamiento.

  • Mensajes fallidos: la idea aquí es publicar el evento en un centro de eventos diferente para que el flujo existente no se interrumpa. La percepción es que se ha movido fuera de la ruta de acceso activa y podría abordarse más adelante o mediante un proceso diferente. Esta solución se usa con frecuencia para controlar eventos o mensajes dudosos. Debe tenerse en cuenta que cada función, que está configurada con un grupo de consumidores diferente, seguirá encontrando los datos dañados o de mala calidad en su flujo y debe controlarlo de forma responsable.

  • Reintentos y mensajes fallidos: otro método conocido es la combinación de numerosos reintentos antes de publicar en última instancia en un flujo de mensajes fallidos una vez que se alcanza un umbral.

  • Uso de un registro de esquema: un registro de esquema se puede usar como una herramienta proactiva para ayudar a mejorar la coherencia y la calidad de los datos. El registro de esquema de Azure puede admitir la transición de esquemas junto con el control de versiones y diferentes modos de compatibilidad a medida que evolucionan los esquemas. En esencia, el esquema servirá como contrato entre los productores y los consumidores, lo que podría reducir la posibilidad de que se publiquen datos no válidos o dañados en el flujo.

Al final, no hay una solución perfecta y es necesario examinar exhaustivamente las consecuencias y contrapartidas de cada una de las estrategias. En función de los requisitos, puede que el mejor enfoque sea el uso conjunto de varias de estas técnicas.

Colaboradores

Microsoft mantiene este artículo. Originalmente lo escribieron los siguientes colaboradores.

Autor principal:

Para ver los perfiles no públicos de LinkedIn, inicie sesión en LinkedIn.

Pasos siguientes

Antes de continuar, considere la posibilidad de revisar estos artículos relacionados: