¿Cómo evitar las "tormentas de reintento" en los servicios distribuidos?

10

Se produce una "tormenta de reintento" cuando los clientes están configurados para reintentar un número determinado de veces antes de darse por vencido, es necesaria una política de reintento debido a que se producirá la pérdida de paquetes en el funcionamiento normal de un servicio.

Toma este ejemplo:

Arquitectura de muestra

Si, por ejemplo, los servicios en su conjunto se ampliaron para admitir 80,000 solicitudes por segundo y se ejecutan a aproximadamente el 80% de la capacidad, un aumento en el tráfico que hizo que el servicio recibiera 101,000 solicitudes por segundo provocaría que 1,000 de esas solicitudes fallaran.

Cuando entran en vigencia las políticas de reintento, terminas con más de 1,000 solicitudes adicionales, dependiendo de dónde se detectó la falla, lo que empujaría el servicio en su conjunto a 102,000 solicitudes por segundo; a partir de ahí, tu servicio entra en una espiral de muerte duplicando el número de solicitudes fallidas cada segundo.

Aparte del sobreaprovisionamiento masivo de servicios más allá de la transacción pico proyectada, lo que sería ineficiente. ¿Qué estrategias puede emplear para evitar "volver a intentar las tormentas"?

Richard Slater
fuente
Si 100kQPS es el 80% de la capacidad, entonces 101kQPS no debería resultar en fallas de 1k, debería resultar en fallas cero, ¿no es ese el punto del sobreaprovisionamiento?
Adrian
@Adrian tiene razón, fue un ejemplo artificial para explicar el punto: estaba tratando de ser lo suficientemente reductor como para aclarar mi punto sin ser demasiado abstracto. He corregido el "escalado para admitir 100,000" a "escalado para admitir 80,000".
Richard Slater

Respuestas:

7

Depende de lo que intentes evitar.

Si está tratando de evitar cualquier interrupción del servicio de algo que es un servicio realmente crítico (estoy pensando en términos de "las personas morirán si mi llamada API no se atiende de manera adecuada"), solo necesita presupuestar para las enormes ineficiencias que provienen de un suministro excesivo de recursos dedicados. Y sí, tienen que ser dedicados, nada de esto permite el aumento de picos de tráfico, por lo que el aumento de múltiples servicios provocaría una interrupción.

En el escenario mucho más probable de que su servicio no funcione, sería inconveniente que pueda abordar el problema tanto desde el lado del cliente como del servidor. Aunque vale la pena señalar que es lógicamente imposible resolver el problema de demasiado tráfico porque sin procesar el tráfico (que consume recursos) no puede saber si es un reintento, si es un reintento para una solicitud que fue exitosa pero se manejó incorrectamente por el cliente, si es un DDOS, etc. Pero puede mitigar el impacto.

En el código del cliente, escriba una lógica de reintento razonable que tenga un límite superior y un mecanismo para fallar con gracia. De esa manera, no se queda con sus usuarios en un ciclo infinito de solicitudes fallidas y simplemente les da un error diciéndoles que intenten lo que sea que hayan hecho en poco tiempo.

Para la infraestructura del lado del servidor , la solución más simple es acelerar. Límites estrictos a las solicitudes, especialmente si puede intentar distribuirlas lógicamente en función de su caso de uso específico (es decir, si tiene un servicio centralizado que toma algunas decisiones difíciles, ¿desea comenzar a bloquear las solicitudes geográficamente distantes que podrían dar lugar a hilos colgados? ¿lado del servidor? ¿O desea distribuir su interrupción inevitable pero menor de manera uniforme? etc) Básicamente se reduce al hecho de que devolver un 503 intencionalmente desde una puerta de enlace es muchísimo más barato que dejar pasar la solicitud y enviar un 504 de todas formas. Básicamente obligar a los clientes a comportarse en función de lo que puede proporcionar actualmente y proporcionar las respuestas correctas para que los clientes puedan reaccionar adecuadamente.

hvindin
fuente
5

Una forma de prevenir estas tormentas de reintento es mediante el uso de mecanismos de retroceso.

Desde la sección Implementar backoff on retry de Google App Engine Designing for Scale :

Su código puede volver a intentar en caso de falla, ya sea llamando a un servicio como Cloud Datastore o un servicio externo usando URL Fetch o Socket API. En estos casos, siempre debe implementar una política de retroceso exponencial aleatorio para evitar el problema del rebaño atronador . También debe limitar el número total de reintentos y manejar las fallas después de alcanzar el límite máximo de reintentos.

La mayoría de las API de GAE ya tienen dichos mecanismos / políticas de retroceso habilitados de forma predeterminada.

Dan Cornilescu
fuente
Gracias, la implementación de mecanismos de retroceso es un gran consejo, generalmente busco un retroceso exponencial configurable usando el Bloque de aplicación de manejo de fallas transitorias . Sin embargo, a través de más de 5 años de experiencia operativa operando aplicaciones de hiper-escala en Azure, incluso con retrasos exponenciales en su lugar, las "tormentas de reintento" todavía ocurren con bastante frecuencia; nunca he podido encontrar una estrategia viable para evitarlas.
Richard Slater