Comment éviter les «tempêtes de nouvelles tentatives» dans les services distribués?

10

Une «tempête de nouvelles tentatives» est provoquée lorsque les clients sont configurés pour réessayer un nombre défini de fois avant d'abandonner, une stratégie de nouvelle tentative est nécessaire car des pertes de paquets se produiront en fonctionnement normal d'un service.

Prenez cet exemple:

Exemple d'architecture

Si, par exemple, les services dans leur ensemble étaient mis à l'échelle pour prendre en charge 80000 demandes par seconde et s'exécutaient à environ 80% de la capacité, un pic de trafic qui aurait fait en sorte que le service recevrait 101000 demandes par seconde entraînerait l'échec de 1000 de ces demandes.

Lorsque les politiques de nouvelle tentative démarrent, vous vous retrouvez avec plus de 1 000 demandes supplémentaires, selon l'endroit où l'échec a été détecté, ce qui pousserait le service dans son ensemble jusqu'à 102 000 demandes par seconde - à partir de là, votre service entre dans une spirale de mort doublant le nombre des demandes ayant échoué chaque seconde.

Autre que la surproduction massive de services au-delà du pic de transaction prévu, qui serait inefficace. Quelles stratégies pouvez-vous utiliser pour éviter les "tempêtes de relance"?

Richard Slater
la source
Si 100kQPS représente 80% de la capacité, alors 101kQPS ne devrait pas entraîner de pannes de 1k, il devrait en résulter zéro pannes - n'est-ce pas le point de surprovision?
Adrian
@Adrian, vous avez raison, c'était un exemple artificiel pour expliquer le point - j'essayais d'être assez réducteur pour clarifier mon point sans être trop abstrait. J'ai corrigé «mis à l'échelle pour prendre en charge 100 000» à «mis à l'échelle pour prendre en charge 80 000».
Richard Slater

Réponses:

7

Cela dépend de ce que vous essayez d'éviter.

Si vous essayez d'éviter toute interruption de service de quelque chose qui est un service véritablement critique (je pense en termes de "personnes mourront si mon appel API n'est pas correctement servi"), vous devez simplement budgétiser les énormes inefficacités qui proviennent largement de la mise à disposition de ressources dédiées. Et oui, ils doivent être dédiés, rien de tout cela ne permettant des pics de trafic, plusieurs pics de services entraîneraient donc une panne.

Dans le scénario beaucoup plus probable où votre service serait en panne, il serait gênant de pouvoir résoudre le problème à la fois côté client et côté serveur. Bien qu'il soit intéressant de noter qu'il est logiquement impossible de résoudre réellement le problème de trop de trafic car sans traiter le trafic (qui consomme des ressources), vous ne pouvez pas savoir s'il s'agit d'une nouvelle tentative, s'il s'agit d'une nouvelle tentative pour une demande qui a réussi mais qui n'a pas été traitée correctement. par le client, s'il s'agit d'un DDOS, etc. Mais vous pouvez atténuer l'impact.

Dans le code client, écrivez une logique de nouvelle tentative sensible qui a une limite supérieure et un mécanisme d'échec gracieux. De cette façon, vous ne collez pas vos utilisateurs dans une boucle infinie de demandes qui échouent et vous leur donnez simplement une erreur leur disant d'essayer tout ce qu'ils viennent de faire en peu de temps.

Pour votre infrastructure côté serveur, la solution la plus simple consiste à limiter. Limites strictes sur les demandes, surtout si vous pouvez essayer de les répartir logiquement en fonction de votre cas d'utilisation spécifique (par exemple. Si vous avez un service centralisé, prenez des décisions difficiles, voulez-vous commencer à bloquer les demandes géographiquement distantes qui pourraient entraîner le blocage des threads côté serveur? Ou voulez-vous répartir uniformément votre panne inévitable mais mineure? etc.) Cela revient essentiellement au fait que le retour intentionnel d'un 503 à partir d'une passerelle est beaucoup moins cher que de laisser la demande passer et d'envoyer un 504 en tous cas. Forcez les clients à se comporter en fonction de ce que vous pouvez actuellement fournir et fournissez les bonnes réponses afin que les clients puissent réagir de manière appropriée.

hvindin
la source
5

Une façon de prévenir ces tempêtes de nouvelles tentatives consiste à utiliser des mécanismes d'interruption.

Dans la section Implémentation de la suppression lors d'une nouvelle tentative du guide Google App Engine Designing for Scale :

Votre code peut réessayer en cas d'échec, qu'il s'agisse d'appeler un service tel que Cloud Datastore ou un service externe à l'aide de URL Fetch ou de l'API Socket. Dans ces cas, vous devez toujours mettre en œuvre une politique de suppression exponentielle aléatoire afin d'éviter le problème du troupeau tonitruant . Vous devez également limiter le nombre total de nouvelles tentatives et gérer les échecs une fois que la limite de nouvelles tentatives est atteinte.

La plupart des API GAE ont déjà de tels mécanismes / politiques de désactivation activés par défaut.

Dan Cornilescu
la source
Merci, l'implémentation de mécanismes de backoff est un excellent conseil, je préfère généralement le backoff exponentiel configurable en utilisant le bloc d'application de gestion des défauts transitoires . Cependant, grâce à plus de 5 ans d'expérience opérationnelle dans l'exploitation d'applications à grande échelle dans Azure, même avec des interruptions exponentielles en place, des «tempêtes de nouvelles tentatives» se produisent encore assez souvent - je n'ai jamais été en mesure de trouver une stratégie viable pour les éviter.
Richard Slater