Patrones de diseño para aplicaciones de alta disponibilidad en Azure - Resilient Applications (Parte I: Retry Policy)

En un sistema distribuido, los fallos sucederán. El hardware puede fallar. La red puede tener fallas ocasionales. Rara vez, un servicio o región entera puede experimentar una interrupción, incluso en interrupciones programadas.
La creación de una aplicación confiable en la nube es diferente de la construcción de una aplicación confiable en una configuración empresarial. Mientras que históricamente puede haber comprado hardware de gama alta para hacer escalamiento vertical (scale up), en un entorno de nube debe escalar de manera horizontal (scale out) en lugar de escalar verticalmente (scale up). Los costos de los entornos en la nube se mantienen bajos a través del uso de hardware de productos básicos. En lugar de centrarse en prevenir los fallos y optimizar el "tiempo medio entre fallos", en este nuevo entorno el enfoque cambia a "tiempo medio para restaurar". El objetivo es minimizar el efecto de un fallo.


¿Que es resiliencia?

Resiliencia es la capacidad de un sistema para recuperarse de fallas y continuar funcionando. No se trata de evitar fallos, sino de responder a los fallos de una manera que evita el tiempo de inactividad o la pérdida de datos. El objetivo de la resiliencia es devolver la aplicación a un estado completamente funcional después de un fallo.

Dos aspectos importantes de la resiliencia son la alta disponibilidad y la recuperación de desastres.

  • Alta disponibilidad: (HA, por sus siglas en inglés) es la capacidad de la aplicación para continuar funcionando en un estado saludable, sin tiempo de inactividad significativo. Por "estado saludable", queremos decir que la aplicación es sensible, y los usuarios pueden conectarse a la aplicación e interactuar con ella.
  • La recuperación de desastres: (DR, por sus siglas en inglés) es la habilidad para recuperarse de incidentes raros pero importantes: fallos no transitorios, a gran escala, tales como interrupción del servicio que afecta a toda una región. La recuperación de desastres incluye copia de seguridad y archivado de datos, y puede incluir intervención manual, como restaurar una base de datos desde una copia de seguridad.

Un termino común en aplicaciones resilientes es continuidad de negocio - business continuity (BC), lo cual permite que un negocio ejecute funciones esenciales de negocio durante una contingencia o situaciones muy adversas como desastres naturales, tiempos de caída de servicio, etc.

BC cubre todo el funcionamiento de la empresa, incluidas las instalaciones físicas, las personas, las comunicaciones, el transporte y TI. Nosotros nos enfocaremos a aplicaciones en la nube.

Fuente: https://docs.microsoft.com/en-us/azure/architecture/resiliency/


Patrones de diseño para aplicaciones resilentes

En esta serie de articulos veremos algunos patrones de diseño que podemos implementar en nuestros componentes de aplicaciones en la nube. Cabe mencionar que estos patrones de diseño no es para implementarse en toda la aplicación, en vez de eso, se debe pensar en qué componentes de la aplicación podrían tener algún tipo de fallo, por ejemplo: el componente de acceso a datos a SQL, Web API de terceros, streaming, etc.

Esto son algunos patrones de diseño populares que deberíamos tener en nuestro repertorio de conocimiento:

  1. Retry Pattern
  2. Throttling Pattern
  3. Circuit-Breaker Pattern

Nota: para mas información acerca de patrones de diseño para aplicaciones resilentes, puede consultar la documentación oficial de Microsoft: https://docs.microsoft.com/en-us/azure/architecture/

Retry Pattern

El patrón de diseño de "Reintento" habilita a que una aplicación pueda manejar fallos transitorios cuando nuestra aplicación intenta conectarse a un servicio o un recurso de red, re-intentando de forma transparente una operación fallida. Este patrón puede mejorar la estabilidad de la aplicación de una manera considerable.

Ahora, cuando se implementa un patrón de reintento (Retry Pattern), se debe implementar algo llamado: política de reintento (Retry Policy). Una la política de reintento define dos cosas importantes:

  1. Número máximo de intentos
  2. Estrategia de retroceso.

image

Número máximo de intentos

La política de re-intento es muy importante ya que si no hacemos esto, podemos caer en un bucle interminable de repeticiones. Por lo tanto, es mandatorio definir cuántas veces desea volver a intentarlo antes de renunciar y decir: “¿sabe qué? Esto no va a funcionar, vamos devolver un error. Algo más grave esta pasando con el sistema y la política de reintento ya no va a funcionar”.

Estrategia de retroceso

La estrategia de retroceso es acerca de cuánto tiempo se espera entre cada reintento. Puede ser que apenas espere una cantidad fija del tiempo entre cada reintento, puede ser entre uno o dos segundos, o el tiempo que se defina por el negocio. Sin embargo, una mejor idea, una mejor estrategia, es tener una creciente estrategia de retroceso, cada vez más tiempo entre cada reintento.


Código de ejemplo de una política de reintento en C#

Este ejemplo en C # ilustra una implementación del patrón de reintento. El método OperationWithBackOffRetryAsync, que se muestra a continuación, invoca un servicio externo de forma asíncrona a través del método TransientOperationAsync. Los detalles del método TransientOperationAsync serán específicos para el servicio.

Ejemplo:

private int retryCount = 3;

private int baseDelay = 5;

private int delayFactor = 0; public async Task OperationWithBackOffRetryAsync() {

TimeSpan delay = TimeSpan.FromSeconds(baseDelay); int currentRetry = 0; for (;;) { try { // Call external service. await TransientOperationAsync(); // Return or break. break; } catch (Exception ex) { Trace.TraceError("Operation Exception");

// back-off strategy

// increase delay time to meet growing back-off strategy currentRetry++;

delayFactor++;

baseDelay = baseDelay * delayFactor;

delay = TimeSpan.FromSeconds(baseDelay); // Check if the exception thrown was a transient exception // based on the logic in the error detection strategy. // Determine whether to retry the operation, as well as how // long to wait, based on the retry strategy. if (currentRetry > this.retryCount || !IsTransient(ex)) { // If this isn't a transient error or we shouldn't retry, // rethrow the exception. throw; } } // Wait to retry the operation. // Consider calculating an exponential delay here and // using a strategy best suited for the operation and fault. await Task.Delay(delay); } } // Async method that wraps a call to a remote service (details not shown). private async Task TransientOperationAsync() { ... }

La sentencia que invoca este método está contenida en un bloque try / catch envuelto en un ciclo “for”. El ciclo “for” sale si la llamada al método TransientOperationAsync tiene éxito sin lanzar una excepción. Si el método TransientOperationAsync falla, el bloque catch examina el motivo de la anomalía. Si se cree que es un error transitorio, el código espera un breve retraso antes de volver a intentar la operación, incluyendo en nuestra lógica la estrategia de retroceso.
El ciclo “for” también rastrea el número de veces que se ha intentado la operación, y si el código falla tres veces se supone que la excepción es más duradera. Si la excepción no es transitoria o es duradera, el controlador de captura genera una excepción. Esta excepción sale del ciclo “for” y debe ser detectada por el código que invoca el método OperationWithBasicRetryAsync.
El método IsTransient, que se muestra a continuación, comprueba un conjunto específico de excepciones que son relevantes para el entorno en el que se ejecuta el código. La definición de una excepción transitoria variará según los recursos a los que se accede y el entorno en el que se ejecuta la operación.


private bool IsTransient(Exception ex) { // Determine if the exception is transient. // In some cases this is as simple as checking the exception type, in other // cases it might be necessary to inspect other properties of the exception. if (ex is OperationTransientException) return true; var webException = ex as WebException; if (webException != null) { // If the web exception contains one of the following status values // it might be transient. return new[] {WebExceptionStatus.ConnectionClosed, WebExceptionStatus.Timeout, WebExceptionStatus.RequestCanceled }. Contains(webException.Status); } // Additional exception checking logic goes here. return false; }

Fuente: https://docs.microsoft.com/en-us/azure/architecture/patterns/retry

¿Cuando utilizar este patrón?

Utilizar este patrón cuando una aplicación podría experimentar fallas transitorias ya que interactúa con un servicio remoto o accede a un recurso remoto. Se espera que estas fallas sean de corta duración, y repetir una petición que previamente falló podría tener éxito en un intento subsiguiente.

Cuando se implementa un patrón de reintento es que la velocidad no es un problema, no es importante.

Dependiendo de la estrategia de back-off. Es posible que se tenga una respuesta más lenta de lo normal, pero el éxito es lo que obtendrá con un patrón de reintento, por lo que aunque tome un poco más de tiempo en última instancia se tendrá éxito en lo que está tratando de ejecutar.

Este patrón puede no ser útil cuando:

  1. Cuando es probable que una falla sea duradera, porque esto puede afectar la capacidad de respuesta de una aplicación. La aplicación puede estar perdiendo tiempo y recursos tratando de repetir una solicitud que es probable que se produzca un error.
  2. Para el manejo de fallas que no se deben a fallas transitorias, como excepciones internas causadas por errores en la lógica de negocio.
  3. Como alternativa a abordar problemas de escalabilidad en un sistema. Si una aplicación experimenta fallas frecuentes por bloqueos de disponibilidad, a menudo es una señal de que el servicio o recurso que se accede debe ampliarse.

Siguientes pasos:

  • Patrones de diseño para aplicaciones de alta disponibilidad Azure - Resilient Applications (Parte II: Throttling Pattern)
  • Patrones de diseño para aplicaciones de alta disponibilidad Azure - Resilient Applications (Parte III: Circuit-Breaker Pattern)

Comments

Popular posts from this blog

Configurar y desplegar una Web API en Azure App Service Environment

Despliegue de contenedores Docker a Azure Web Apps

Conectar .NET Web API con Azure API Management

Ejecutar pruebas de volumen con Visual Studio y Team Service