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.
Todas las aplicaciones que se comunican con los recursos y servicios remotos deben ser sensibles a errores transitorios. Esto es especialmente cierto para las aplicaciones que se ejecutan en la nube, donde, debido a la naturaleza del entorno y la conectividad a través de Internet, es probable que se produzca este tipo de fallo con más frecuencia. Los errores transitorios incluyen la pérdida momentánea de conectividad de red a componentes y servicios, la indisponibilidad temporal de un servicio y los tiempos de espera que ocurren cuando un servicio está ocupado. Estos errores a menudo se corrigen solos, por lo que si la acción se repite después de un retraso adecuado, es probable que tenga éxito.
En este artículo se proporcionan instrucciones generales para el control de errores transitorios.
¿Por qué se producen errores transitorios en la nube?
Los errores transitorios pueden ocurrir en cualquier entorno, en cualquier plataforma o sistema operativo y en cualquier tipo de aplicación. En el caso de las soluciones que se ejecutan en la infraestructura local local, el rendimiento y la disponibilidad de la aplicación y sus componentes se mantienen normalmente a través de redundancia de hardware costosa y a menudo infrautilizada, y los componentes y recursos se encuentran cerca entre sí. Este enfoque hace que los errores sean menos probables, pero los errores transitorios pueden producirse, ya que pueden producirse interrupciones causadas por eventos imprevistos, como la fuente de alimentación externa o los problemas de red, u otros escenarios de desastre.
El hospedaje en la nube, incluidos los sistemas de nube privada, puede ofrecer una mayor disponibilidad general mediante el uso de recursos compartidos, redundancia, conmutación automática por error y asignación dinámica de recursos en muchos nodos de proceso básicos. Sin embargo, debido a la naturaleza de los entornos de nube, es más probable que se produzcan errores transitorios. Hay varias razones para esto:
Muchos recursos de un entorno de nube se comparten y el acceso a estos recursos está sujeto a limitaciones para proteger los recursos. Algunos servicios rechazan las conexiones cuando la carga aumenta a un nivel específico, o cuando se alcanza una velocidad de rendimiento máxima, para permitir el procesamiento de las solicitudes existentes y mantener el rendimiento del servicio para todos los usuarios. La limitación ayuda a mantener la calidad del servicio para vecinos y otros inquilinos que usan el recurso compartido.
Los entornos en la nube usan un gran número de unidades de hardware de productos básicos. Proporcionan rendimiento mediante la distribución dinámica de la carga entre varias unidades informáticas y componentes de infraestructura. Proporcionan confiabilidad mediante el reciclaje o reemplazo automático de unidades con errores. Debido a esta naturaleza dinámica, los errores transitorios y los errores de conexión temporales pueden producirse ocasionalmente.
A menudo hay más componentes de hardware, incluida la infraestructura de red, como enrutadores y equilibradores de carga, entre la aplicación y los recursos y servicios que usa. Esta infraestructura adicional puede introducir ocasionalmente errores de conexión transitorios y latencia de conexión adicionales.
Las condiciones de red entre el cliente y el servidor pueden ser variables, especialmente cuando la comunicación cruza Internet. Incluso en ubicaciones locales, las cargas de tráfico pesadas pueden ralentizar la comunicación y provocar errores de conexión intermitentes.
Desafíos
Los errores transitorios pueden tener un gran efecto en la disponibilidad percibida de una aplicación, incluso si se ha probado exhaustivamente en todas las circunstancias previsibles. Para asegurarse de que las aplicaciones hospedadas en la nube funcionan de forma confiable, debe asegurarse de que pueden responder a los siguientes desafíos:
La aplicación debe poder detectar errores cuando ocurren y determinar si es probable que sean transitorios, duraderos o terminales. Es probable que diferentes recursos devuelvan respuestas diferentes cuando ocurre un error, y estas respuestas también pueden variar según el contexto de la operación. Por ejemplo, la respuesta para un error cuando la aplicación lee del almacenamiento puede diferir de la respuesta para un error cuando la aplicación escribe en el almacenamiento. Muchos recursos y servicios tienen contratos de errores transitorios bien documentados. Sin embargo, cuando esta información no está disponible, puede ser difícil descubrir la naturaleza del error y si es probable que sea transitoria.
La aplicación debe poder volver a intentar la operación si determina que es probable que el error sea transitorio. También debe realizar un seguimiento del número de veces que se reintenta la operación.
La aplicación debe utilizar una estrategia adecuada para los reintentos. La estrategia especifica la cantidad de veces que la aplicación debe reintentar, el retraso entre cada intento y las acciones a tomar después de un intento fallido. El número adecuado de intentos y el retraso entre cada uno suelen ser difíciles de determinar. La estrategia variará en función del tipo de recurso y de las condiciones de funcionamiento actuales del recurso y de la aplicación.
Directrices generales
Las siguientes pautas pueden ayudarle a diseñar mecanismos adecuados de gestión de errores transitorios para sus aplicaciones.
Determinar si hay un mecanismo de reintento integrado
Muchos servicios proporcionan un SDK o una biblioteca cliente que contiene un mecanismo transitorio de control de errores. La política de reintentos que utiliza se adapta normalmente a la naturaleza y los requisitos del servicio de destino. Alternativamente, las interfaces REST para servicios pueden devolver información que puede ayudarle a determinar si un reintento es apropiado y cuánto tiempo esperar antes del siguiente reintento.
Debe utilizar el mecanismo de reintento integrado cuando esté disponible, a menos que tenga requisitos específicos y bien comprendidos que hagan que un comportamiento de reintento diferente sea más apropiado.
Determinar si la operación es adecuada para volver a intentarlo
Realice operaciones de reintento solo cuando los errores sean transitorios (normalmente indicados por la naturaleza del error) y cuando exista al menos cierta probabilidad de que la operación se realice correctamente cuando se reintente. No tiene sentido en reintentar operaciones que intentan una operación no válida, como actualizar una base de datos en un elemento que no existe o una solicitud a un servicio o recurso que ha sufrido un error irrecuperable.
En general, implemente reintentos solo cuando pueda determinar el efecto completo de hacerlo y cuándo se entienden bien las condiciones y se pueden validar. De lo contrario, deje que el código que realiza la llamada implemente los reintentos. Recuerde que los errores devueltos por recursos y servicios fuera de su control pueden evolucionar con el tiempo y es posible que deba revisar su lógica de detección de fallas transitorias.
Al crear servicios o componentes, considere la posibilidad de implementar códigos de error y mensajes que ayuden a los clientes a determinar si deben reintentar operaciones con errores. En concreto, indique si el cliente debe reintentar la operación (quizás devolviendo un valor isTransient ) y sugerir un retraso adecuado antes del siguiente reintento. Si crea un servicio web, considere la posibilidad de devolver los errores personalizados que están definidos en los contratos de servicio. Aunque es posible que los clientes genéricos no puedan leer estos errores, son útiles para la creación de clientes personalizados.
Determinar un recuento y un intervalo de reintentos adecuados
Optimice el recuento de reintentos y el intervalo según el tipo de caso de uso. Si no lo vuelve a intentar suficientes veces, la aplicación no podrá completar la operación y probablemente fallará. Si vuelve a intentar demasiados veces o con un intervalo demasiado corto entre intentos, la aplicación podría contener recursos como subprocesos, conexiones y memoria durante largos períodos, lo que afecta negativamente al estado de la aplicación.
Adapte los valores del intervalo de tiempo y el número de reintentos al tipo de operación. Por ejemplo, si la operación es parte de una interacción del usuario, el intervalo debe ser corto y solo se deben intentar unos pocos reintentos. Mediante este enfoque, puede evitar que los usuarios esperen una respuesta, que contiene conexiones abiertas y puede reducir la disponibilidad de otros usuarios. Si la operación forma parte de un flujo de trabajo de larga duración o crítico, donde cancelar y reiniciar el proceso es costoso o lento, es adecuado esperar más tiempo entre los intentos y volver a intentarlo más veces.
Tenga en cuenta que determinar los intervalos adecuados entre reintentos es la parte más difícil de diseñar una estrategia exitosa. Las estrategias típicas utilizan los siguientes tipos de intervalo de reintento:
Retroceso exponencial. La aplicación espera un breve tiempo antes del primer reintento y luego aumenta exponencialmente el tiempo entre cada reintento posterior. Por ejemplo, podría volver a intentar la operación después de 3 segundos, 12 segundos, 30 segundos, etc.
Intervalos incrementales. La aplicación espera un breve tiempo antes del primer reintento y, a continuación, aumenta incrementalmente el tiempo entre cada reintento posterior. Por ejemplo, podría reintentar la operación después de 3 segundos, 7 segundos, 13 segundos, etc.
Intervalos regulares. La aplicación espera el mismo período de tiempo entre cada intento. Por ejemplo, podría reintentar la operación cada 3 segundos.
Reintento inmediato. A veces, un error transitorio es breve, posiblemente causado por un evento como una colisión de paquetes de red o un pico en un componente de hardware. En este caso, volver a intentar la operación inmediatamente es adecuado porque podría realizarse correctamente si el error se borra en el tiempo que tarda la aplicación en ensamblar y enviar la siguiente solicitud. Sin embargo, nunca debería haber más de un reintento inmediato. Si se producen errores en el reintento inmediato, debe cambiar a estrategias alternativas, como el retroceso exponencial o las acciones de reversión.
Selección aleatoria. Cualquiera de las estrategias de reintento enumeradas anteriormente puede incluir una selección aleatoria para evitar que varias instancias del cliente envíen intentos de reintento posteriores al mismo tiempo. Por ejemplo, una instancia podría reintentar la operación después de 3 segundos, 11 segundos, 28 segundos, etc., mientras que otra instancia podría reintentar la operación después de 4 segundos, 12 segundos, 26 segundos, etc. La selección aleatoria es una técnica útil que se puede combinar con otras estrategias.
Como guía general, use una estrategia de respaldo exponencial para las operaciones en segundo plano y emplee estrategias de reintento inmediato o a intervalos regulares para las operaciones interactivas. En ambos casos, debe elegir el retraso y el recuento de reintentos de modo que la latencia máxima para todos los reintentos esté dentro del requisito de latencia de extremo a extremo requerido.
Tenga en cuenta la combinación de todos los factores que contribuyen al tiempo de espera máximo global de una operación reintentada. Estos factores incluyen el tiempo necesario para que una conexión con error genere una respuesta (normalmente establecida por un valor de tiempo de espera en el cliente), el retraso entre los reintentos y el número máximo de reintentos. El total de todos estos tiempos puede resultar en tiempos de operación generales prolongados, especialmente cuando se utiliza una estrategia de retraso exponencial donde el intervalo entre reintentos crece rápidamente después de cada error. Si un proceso debe cumplir con un acuerdo de nivel de servicio (SLA) específico, el tiempo total de operación, incluidos todos los tiempos de espera y retrasos, debe estar dentro de los límites definidos en el SLA.
No implemente estrategias de reintento demasiado agresivas. Se trata de estrategias que tienen intervalos demasiado cortos o reintentos demasiado frecuentes. Pueden tener un efecto adverso en el recurso o servicio de destino. Estas estrategias pueden impedir que el recurso o servicio se recupere de su estado sobrecargado y continuará bloqueando o rechazando solicitudes. Este escenario da como resultado un círculo vicioso, donde cada vez se envían más solicitudes al recurso o servicio. En consecuencia, su capacidad de recuperación se reduce aún más.
Tenga en cuenta el tiempo de espera de las operaciones cuando elija intervalos de reintento para evitar iniciar un intento posterior inmediatamente (por ejemplo, si el período de tiempo de espera es similar al intervalo de reintento). Además, considere si necesita mantener el período total posible (el tiempo de espera más los intervalos de reintento) por debajo de un tiempo total específico. Si una operación tiene un tiempo de espera inusualmente corto o largo, el tiempo de espera puede influir en cuánto tiempo de espera y con qué frecuencia reintentar la operación.
Use el tipo de excepción y los datos que contenga, o los códigos de error y los mensajes devueltos por el servicio, para optimizar el número de reintentos y el intervalo entre ellos. Por ejemplo, algunas excepciones o códigos de error (como el código HTTP 503, Servicio no disponible, con un encabezado Reintentar-Después en la respuesta) pueden indicar cuánto tiempo podría durar el error, o que el servicio falló y no responderá a ningún intento posterior.
Evitar antipatrones
En la mayoría de los casos, evite implementaciones que incluyan capas duplicadas de código de reintento. Evite diseños que incluyan mecanismos de reintento en cascada o que implementen el reintento en cada fase de una operación que implique una jerarquía de solicitudes, a menos que tenga requisitos específicos que requieran hacerlo. En estas circunstancias excepcionales, utilice directivas que eviten un número excesivo de reintentos y períodos de retraso, y asegúrese de comprender las consecuencias. Por ejemplo, digamos que un componente realiza una solicitud a otro, que luego accede al servicio de destino. Si implementa el reintento con un recuento de tres en ambas llamadas, hay nueve intentos de reintento en total en el servicio. Muchos servicios y recursos implementan un mecanismo de reintento integrado. Si necesita implementar reintentos en un nivel superior, debe investigar cómo puede deshabilitar o modificar estos mecanismos.
Nunca implemente un mecanismo de reintento infinito. Al hacerlo, es probable que impida que el recurso o servicio se recupere de situaciones de sobrecarga y provoque que las limitaciones y las conexiones rechazadas continúen durante más tiempo. Use un número finito de reintentos o implemente un patrón como Circuit Breaker para permitir que el servicio se recupere.
Nunca realice un reintento inmediato más de una vez.
Evite usar un intervalo de reintento normal cuando acceda a servicios y recursos en Azure, especialmente cuando tenga un gran número de reintentos. El mejor enfoque en este escenario es una estrategia de retroceso exponencial con una funcionalidad de disyuntor.
Evite que varias instancias del mismo cliente o varias instancias de distintos clientes envíen reintentos al mismo tiempo. Si es probable que se produzca este escenario, introduzca la selección aleatoria en los intervalos de reintento.
Pruebe su estrategia de reintentos y su implementación
Pruebe completamente su estrategia de reintento en el mayor conjunto de circunstancias posible, especialmente cuando tanto la aplicación como los recursos o servicios de destino que utiliza se encuentran bajo una carga extrema. Para comprobar el comportamiento durante las pruebas, puede:
Insertar errores transitorios y no transitorios en el servicio. Por ejemplo, envíe solicitudes no válidas o agregue código que detecte solicitudes de prueba y responda con diferentes tipos de errores. Para obtener ejemplos que usan TestApi, consulte Pruebas de inyección de errores con TestApi e Introducción a TestApi : Parte 5: API de inyección de errores de código administrado.
Cree un prototipo del recurso o servicio que devuelva una gama de errores que el servicio real podría devolver. Cubra todos los tipos de errores que su estrategia de reintento está diseñada para detectar.
Para los servicios personalizados que crea e implementa, fuerce la aparición de errores transitorios deshabilitando o sobrecargando temporalmente el servicio. (No intente sobrecargar ningún recurso o servicio compartido en Azure).
En el caso de las API basadas en HTTP, considere la posibilidad de usar una biblioteca en las pruebas automatizadas para cambiar el resultado de las solicitudes HTTP, ya sea agregando tiempos de ida y vuelta adicionales o cambiando la respuesta (como el código de estado HTTP, los encabezados, el cuerpo u otros factores). Al hacerlo, se habilitan pruebas deterministas de un subconjunto de las condiciones de error, para errores transitorios y otros tipos de errores.
Realice pruebas simultáneas y de factor de carga elevados para asegurarse de que el mecanismo de reintento y la estrategia funcionan correctamente en estas condiciones. Estas pruebas también ayudarán a garantizar que el reintento no tenga un efecto adverso en el funcionamiento del cliente o cause contaminación cruzada entre solicitudes.
Administrar configuraciones de políticas de reintentos
Una política de reintento es una combinación de todos los elementos de su estrategia de reintento. Define el mecanismo de detección que determina si es probable que un error sea transitorio, el tipo de intervalo que se va a usar (como normal, retroceso exponencial y selección aleatoria), los valores de intervalo reales y el número de veces que se reintenta.
Implemente reintentos en muchos lugares, incluso en la aplicación más sencilla, y en cada capa de aplicaciones más complejas. En lugar de codificar de forma rígida los elementos de cada directiva en varias ubicaciones, considere la posibilidad de usar un punto central para almacenar todas las directivas. Por ejemplo, almacene valores como el intervalo y el recuento de reintentos en los archivos de configuración de la aplicación, léelos en tiempo de ejecución y compile mediante programación las directivas de reintento. Al hacerlo, resulta más fácil administrar la configuración y modificar y ajustar los valores para responder a los requisitos y escenarios cambiantes. Sin embargo, diseñe el sistema para almacenar los valores en lugar de volver a leer un archivo de configuración cada vez y use los valores predeterminados adecuados si los valores no se pueden obtener de la configuración.
En una aplicación de Azure Cloud Services, considere la posibilidad de almacenar los valores que se usan para compilar las directivas de reintento en tiempo de ejecución en el archivo de configuración del servicio para que pueda cambiarlos sin necesidad de reiniciar la aplicación.
Aproveche las estrategias de reintento integradas o predeterminadas que están disponibles en las API de cliente que use, pero solo cuando sean adecuadas para su escenario. Estas estrategias suelen ser genéricas. En algunos escenarios, pueden ser todo lo que necesita, pero en otros no ofrecen la gama completa de opciones para satisfacer sus requisitos específicos. Para determinar los valores más apropiados, debe realizar pruebas para comprender cómo la configuración afecta su aplicación.
Registre y rastree fallas transitorias y no transitorias
Como parte de su estrategia de reintento, incluya la gestión de excepciones y otra instrumentación que registre los reintentos. Se espera que se produzcan errores transitorios ocasionales y reintentos, pero no indican ningún problema. Sin embargo, un número creciente y regular de reintentos suele ser un indicador de un problema que podría provocar un error o que degrada el rendimiento y la disponibilidad de la aplicación.
Registre los errores transitorios como entradas de advertencia en lugar de entradas de error para que los sistemas de supervisión no las detecten como errores de aplicaciones que podrían desencadenar alertas falsas.
Considere almacenar un valor en sus entradas de registro que indique si los reintentos son causados por la limitación del servicio o por otros tipos de fallas, como fallas de conexión, para que pueda diferenciarlos durante el análisis de los datos. A menudo, un aumento del número de errores de limitación es un indicador de un defecto de diseño de la aplicación o la necesidad de cambiar a un servicio premium que ofrece hardware dedicado.
Considere la posibilidad de medir y registrar los tiempos transcurridos generales para las operaciones que incluyen un mecanismo de reintento. Esta métrica es un buen indicador del efecto general de los errores transitorios en los tiempos de respuesta del usuario, la latencia del proceso y la eficacia de los casos de uso de la aplicación. Registre también el número de reintentos que se producen para que pueda comprender los factores que contribuyen al tiempo de respuesta.
Considere implementar un sistema de telemetría y supervisión que pueda generar alertas cuando aumente la cantidad y la tasa de errores, la cantidad promedio de reintentos o el tiempo total transcurrido antes de que las operaciones tengan éxito.
Gestionar operaciones que fallan continuamente
Tenga en cuenta cómo va a controlar las operaciones que siguen produciendo errores en cada intento. Situaciones como esta son inevitables.
Aunque una estrategia de reintento define el número máximo de veces que se debe reintentar una operación, no impide que la aplicación repita la operación de nuevo con el mismo número de reintentos. Por ejemplo, si un servicio de procesamiento de pedidos produce un error irrecuperable que lo pone fuera de acción permanentemente, la estrategia de reintento podría detectar un tiempo de espera de conexión y considerar que es un error transitorio. El código reintenta la operación un número especificado de veces y luego abandona la operación. Sin embargo, cuando otro cliente realiza un pedido, la operación se vuelve a intentar, aunque se producirá un error cada vez.
Para evitar reintentos continuos para las operaciones que producen errores continuamente, debe considerar la posibilidad de implementar el patrón Circuit Breaker. Cuando se usa este patrón, si el número de errores dentro de un período de tiempo especificado supera un umbral, las solicitudes vuelven al autor de la llamada inmediatamente como errores y no hay ningún intento de acceder al recurso o servicio con errores.
La aplicación puede probar periódicamente el servicio, de forma intermitente y con largos intervalos entre solicitudes, para detectar cuando esté disponible. Un intervalo apropiado depende de factores como la criticidad de la operación y la naturaleza del servicio. Puede ser desde unos pocos minutos hasta varias horas. Cuando la prueba tiene éxito, la aplicación puede reanudar las operaciones normales y pasar solicitudes al servicio recién recuperado.
Mientras tanto, es posible que pueda revertir a otra instancia del servicio (quizás en otro centro de datos o aplicación), usar un servicio similar que ofrezca una funcionalidad compatible (tal vez más sencilla) o realizar algunas operaciones alternativas basadas en la esperanza de que el servicio estará disponible pronto. Por ejemplo, podría ser apropiado almacenar las solicitudes del servicio en una cola o almacén de datos y volver a intentarlas más tarde. O bien, es posible que pueda redirigir al usuario a una instancia alternativa de la aplicación, degradar el rendimiento de la aplicación, pero seguir ofreciendo una funcionalidad aceptable, o simplemente devolver un mensaje al usuario para indicar que la aplicación no está disponible actualmente.
Otras consideraciones
Cuando decida los valores para el número de reintentos y los intervalos de reintento para una directiva, considere si la operación en el servicio o recurso es parte de una operación de larga duración o de varios pasos. Puede ser difícil o costoso compensar todos los demás pasos operativos que ya se han realizado correctamente cuando se produce un error. En este caso, un intervalo muy largo y un gran número de reintentos pueden ser aceptables siempre que esa estrategia no bloquee otras operaciones manteniendo o bloqueando recursos escasos.
Considere si volver a intentar la misma operación podría causar inconsistencias en los datos. Si algunas partes de un proceso de varios pasos se repiten y las operaciones no son idempotentes, pueden ocurrir inconsistencias. Por ejemplo, si se repite una operación que incrementa un valor, genera un resultado no válido. Repetir una operación que envía un mensaje a una cola podría provocar una incoherencia en el consumidor de mensajes si el consumidor no puede detectar mensajes duplicados. Para evitar estos escenarios, diseñe cada paso como una operación idempotente. Para obtener más información, consulte Patrones de idempoencia.
Considere el ámbito de las operaciones que se reintentan. Por ejemplo, podría ser más fácil implementar un código de reintento en un nivel que abarque varias operaciones y reintentarlas todas si una falla. Sin embargo, hacerlo podría provocar problemas de idempotencia u operaciones de reversión innecesarias.
Si elige un ámbito de reintento que abarque varias operaciones, tenga en cuenta la latencia total de todos ellos al determinar los intervalos de reintento, al supervisar los tiempos transcurridos de la operación y antes de generar alertas de errores.
Tenga en cuenta cómo la estrategia de reintento podría afectar a los vecinos y otros inquilinos de una aplicación compartida y al usar recursos y servicios compartidos. Las directivas de reintento agresivas pueden provocar un número creciente de errores transitorios para estos otros usuarios y para las aplicaciones que comparten los recursos y los servicios. Del mismo modo, la aplicación podría verse afectada por las directivas de reintento implementadas por otros usuarios de los recursos y servicios. En el caso de las aplicaciones críticas para la empresa, es posible que quiera usar servicios Premium que no se compartan. Esto le brinda un mayor control sobre la carga y la consiguiente limitación de estos recursos y servicios, que puede ayudar a justificar el costo adicional.
Recursos relacionados
- Circuit Breaker pattern (Patrón Circuit Breaker)
- Patrón de transacción de compensación
- Patrones de idempotencia