Compartir a través de


Antipatrón de carga masiva de reintentos

Cuando un servicio no está disponible o ocupado, hacer que los clientes vuelvan a intentar sus conexiones con demasiada frecuencia puede hacer que el servicio tenga dificultades para recuperarse y puede empeorar el problema. Tampoco tiene sentido reintentar para siempre, ya que las solicitudes normalmente solo son válidas durante un período de tiempo definido.

Descripción del problema

En la nube, a veces los servicios experimentan problemas y dejan de estar disponibles para los clientes, o tienen que regular o limitar la velocidad de los clientes. Aunque es un procedimiento recomendado para que los clientes vuelvan a intentar las conexiones con errores a los servicios, es importante que no vuelvan a intentarlo con demasiada frecuencia o durante demasiado tiempo. Es poco probable que los reintentos efectuados en un breve período de tiempo se realicen correctamente, ya que es posible que los servicios no se hayan recuperado. Además, los servicios se pueden poner bajo aún más estrés cuando se realizan muchos intentos de conexión mientras intentan recuperarse, y los intentos repetidos de conexión pueden incluso sobrecargar el servicio y empeorar el problema subyacente.

En el ejemplo siguiente se muestra un escenario en el que un cliente se conecta a una API basada en servidor. Si la solicitud no se realiza correctamente, el cliente vuelve a intentarlo inmediatamente y mantiene el reintento para siempre. A menudo, este tipo de comportamiento es más sutil que en este ejemplo, pero se aplica el mismo principio.

public async Task<string> GetDataFromServer()
{
    while(true)
    {
        var result = await httpClient.GetAsync(string.Format("http://{0}:8080/api/...", hostName));
        if (result.IsSuccessStatusCode) break;
    }

    // ... Process result.
}

Cómo corregir el problema

Las aplicaciones cliente deben seguir algunas mejores prácticas para evitar provocar una tormenta de reintentos.

  • Limite el número de reintentos y no vuelva a intentarlo durante un largo período de tiempo. Aunque podría parecer fácil escribir un bucle while(true), seguramente no desea seguir reintentándolo durante un largo período de tiempo, ya que la situación que condujo al inicio de la solicitud ha cambiado probablemente. En la mayoría de las aplicaciones, el reintento durante unos segundos o minutos es suficiente.
  • Hacer una pausa entre los reintentos. Es poco probable que reintentar inmediatamente tenga éxito si un servicio no está disponible. Aumente gradualmente la cantidad de tiempo que espera entre los intentos, por ejemplo, mediante una estrategia de retroceso exponencial.
  • Maneje los errores con elegancia. Si el servicio no responde, considere si tiene sentido anular el intento y devolver un error al usuario o al autor de la llamada del componente. Tenga en cuenta estos escenarios de error al diseñar la aplicación.
  • Considere la posibilidad de usar el patrón de disyuntor, que está diseñado específicamente para ayudar a evitar las cargas masivas de reintentos.
  • Si el servidor proporciona un encabezado de respuesta retry-after, asegúrese de que no intenta reintentar hasta que haya transcurrido el período de tiempo especificado.
  • Use los SDK oficiales al comunicarse con los servicios de Azure. Estos SDK suelen tener directivas de reintento integradas y protecciones que impiden provocar o contribuir a las cargas masivas de reintentos. Si se comunica con un servicio que no tiene un SDK o cuyo SDK no maneja correctamente la lógica de reintento, considere usar una biblioteca como Polly (para .NET) o retry (para JavaScript) para gestionar correctamente su lógica de reintento y evitar tener que escribir el código usted mismo.
  • Si se ejecuta en un entorno que lo admite, use una malla de servicio (u otra capa de abstracción) para enviar llamadas salientes. Normalmente, estas herramientas, como Dapr, admiten directivas de reintento y siguen automáticamente los procedimientos recomendados, como el respaldo después de varios intentos. Este enfoque significa que no es imprescindible que escriba código de reintento.
  • Considere la posibilidad de procesar por lotes las solicitudes y usar la agrupación de solicitudes cuando esté disponible. Muchos SDK controlan el procesamiento por lotes de solicitudes y la agrupación de conexiones en su nombre, lo que reducirá el número total de intentos de conexión salientes que realiza la aplicación, aunque debe tener cuidado de no volver a intentar estas conexiones con demasiada frecuencia.

Los servicios también deben protegerse contra las cargas masivas de reintentos.

  • Agregue una capa de puerta de enlace para que pueda apagar las conexiones durante un incidente. Este es un ejemplo del patrón Bulkhead . Azure proporciona muchos servicios de puerta de enlace diferentes para diferentes tipos de soluciones, como Front Door, Application Gatewayy API Management.
  • Limite las solicitudes en la puerta de enlace; esto garantizará que no se acepten tantas solicitudes que los componentes de back-end no puedan seguir funcionando.
  • Si tiene un límite, devuelva un encabezado retry-after para ayudar a los clientes a comprender cuándo volver a intentar las conexiones.

Consideraciones

  • Los clientes deben tener en cuenta el tipo de error devuelto. Algunos tipos de error no indican un error del servicio, pero en su lugar indican que el cliente envió una solicitud no válida. Por ejemplo, si una aplicación cliente recibe una respuesta de error de 400 Bad Request, probablemente intentar la misma solicitud no ayudará, ya que el servidor le indica que su solicitud no es válida.
  • Los clientes deben tener en cuenta el tiempo adecuado para reanudar las conexiones. El período de tiempo durante el cual debe reintentar dependerá de sus requisitos empresariales y de si puede propagar razonablemente un error a un usuario o a la persona que llama. En la mayoría de las aplicaciones, el reintento durante unos segundos o minutos es suficiente.

Cómo detectar el problema

Desde la perspectiva de un cliente, los síntomas de este problema podrían incluir tiempos de procesamiento o respuesta muy largos, junto con la telemetría que indica intentos repetidos de reintentar la conexión.

Desde la perspectiva de un servicio, los síntomas de este problema podrían incluir un gran número de solicitudes de un cliente en un breve período de tiempo, o un gran número de solicitudes de un solo cliente mientras se recuperan de interrupciones. Los síntomas también pueden incluir dificultades al recuperar el servicio o errores en cascada continuos del servicio justo después de que se haya reparado un error.

Diagnóstico de ejemplo

En las secciones siguientes se muestra un enfoque para detectar una posible carga masiva de reintentos, tanto en el cliente como en el servicio.

Identificación de la telemetría de cliente

Azure Application Insights registra la telemetría de las aplicaciones y hace que los datos estén disponibles para la consulta y visualización. Las conexiones salientes se rastrean como dependencias, y la información sobre ellas puede ser accedida y graficada para identificar cuándo un cliente realiza un gran número de solicitudes salientes al mismo servicio.

El gráfico siguiente se tomó de la pestaña Métricas del portal de Application Insights y muestra la métrica de fallos de dependencia dividida por nombre de dependencia remota. Esto ilustra un escenario en el que había un gran número (más de 21 000) de intentos fallidos de conexión a una dependencia en un breve período de tiempo.

Captura de pantalla de Application Insights en la que se muestran 21 000 errores de dependencia con una única dependencia en un lapso de 30 minutos

Identificación de la telemetría del servidor

Las aplicaciones de servidor pueden detectar un gran número de conexiones desde un solo cliente. En el ejemplo siguiente, Azure Front Door actúa como puerta de enlace para una aplicación y se ha configurado para registrar todas las solicitudes a un área de trabajo de Log Analytics.

La siguiente consulta de Kusto se puede ejecutar en Log Analytics. Identificará las direcciones IP de cliente que han enviado un gran número de solicitudes a la aplicación en el último día.

AzureDiagnostics
| where ResourceType == "FRONTDOORS" and Category == "FrontdoorAccessLog"
| where TimeGenerated > ago(1d)
| summarize count() by bin(TimeGenerated, 1h), clientIp_s
| order by count_ desc

La ejecución de esta consulta durante una tormenta de reintentos muestra un gran número de intentos de conexión desde una sola dirección IP.

Captura de pantalla de Log Analytics en la que se muestran 81 608 conexiones entrantes a Front Door desde una única dirección IP dentro de un período de una hora