design for failure (les traitements applicatifs doivent, dès leur conception, prévoir le cas où les composants qu’ils appellent pourraient tomber en erreur) a pris encore plus d’importance.
Et donc nous sommes passés de « prévenir toutes les défaillances » à « les défaillances font partie du jeu ».
Parmi toutes les solutions composant le design for failure, nous allons nous pencher sur le design pattern “Circuit Breaker” popularisé par Michael Nygard dans le livre “Release It!”.
Mais avant cela, faisons un tour rapide sur d’autres solutions permettant de gérer des problèmes de dépendances.
Le problème que nous voulons résoudre est la gestion des dépendances (externe ou interne) qui peuvent (et le seront tôt ou tard) défaillantes lors de l’exécution de notre application.
Par exemple lorsqu’une application appelle le service de paiement, que faire lorsque celui-ci n’est pas accessible ?
Comme pour toutes les difficultés, plusieurs solutions sont possibles. En voici quelques-unes.
Une solution simple est de se dire qu’il suffit de prendre son application telle quelle et de la mettre sur le cloud sans faire de modification (technique du lift and shift). Cela permet de tirer parti de tous les avantages de son fournisseur de cloud :
Malheureusement cela n’est pas suffisant, car même les fournisseurs de Cloud les plus connus peuvent avoir des problèmes et provoquer des indisponibilités comme Google en 2013.
De plus si le problème est applicatif (ne supporte pas la charge, crash, envoi de réponse non conforme…), la migration dans le cloud n’apporte aucune solution à notre problème.
Dernières précisions :
Solution plus évoluée que la précédente, mais qui peut demander beaucoup de modifications du code source de notre application (ajout de health check, passage en stateless pour vraiment en tirer profit…) : le répartiteur de charge.
C’est un composant réseau ou applicatif qui répartit les requêtes sur différentes instances de façon à équilibrer la charge.
Par exemple, AWS ELB d’Amazon et HAProxy.
Dans notre cas (gérer les dépendances et réseaux défaillants), le répartiteur de charge va :
Encore une fois, cela ne sera pas suffisant dans notre cas pour différentes raisons :
Autre solution possible, qui par contre va surement demander des modifications du code source de l’application : le design pattern timeout.
Il permet de ne pas attendre indéfiniment une réponse en positionnant un temps d’attente maximal.
Le problème de ce pattern est qu’il n’est “pas très intelligent”, car si le service appelé est hors service, le service appelant va quand même faire l’appel. Ce qui implique :
Dernier design pattern présenté, le Retry Pattern demandera des modifications de code.
Il consiste à envoyer à nouveau la requête qui a échoué. Et donc si le service appelé “tombe en marche”, cela sera transparent pour l’utilisateur au prix d'une latence significative.
Par contre, si le service appelé reste hors service, l’application risque une surcharge en multipliant les requêtes.
Ainsi, ce pattern n’est à utiliser que dans le cas particulier où nous savons, par expérience (mais dont la cause n’a pas encore été corrigée, car la solution est impossible/trop chère/inconnue), que l'interruption de service est temporaire et courte.
Mais le plus important est que les requêtes sur lesquelles on veut appliquer le pattern doivent être idempotentes, car sinon il y a un risque de corruption des données ou de comportement non prévu.
Par exemple, supposons que notre application permet d’accélérer ou de ralentir une voiture à l’aide de deux boutons.
Lors de l’appui sur le bouton d’accélération, nous envoyons l’ordre d’accélérer de 5km/h. Pour une raison quelconque, la requête n’arrive pas tout de suite et donc notre pattern envoie un deuxième ordre d’accélérer de 5km/h. Résultat au lieu d’accélérer de 5km/h nous accélérons de 10km/h
La bonne solution aurait été d’envoyer l’ordre idempotent : passe à 110km/h. Dans ce cas peu importe le nombre d’ordres reçus.
Si l’utilisation de requête idempotente n’est pas possible, il faudra utiliser un autre pattern nommé Exactly-once Delivery qui peut être compliqué à mettre en place.
Nous pourrions encore parler longtemps de pattern, mais il est temps de passer à l’essentiel de cet article : le design pattern “circuit breaker”.
Pour avoir une idée des autres patterns (Bulkhead, Event sourcing, Feature Flipping, Pets versus Cattle...), nous vous laissons lire notre livre blanc “Cloud Ready Apps”.
Nous venons de voir quelques solutions pour résoudre la gestion des dépendances (externe ou interne) qui peuvent (et le seront tôt ou tard) défaillantes lors de l’exécution de notre application.
Lors de la prochaine partie, nous discuterons du pattern circuit breaker.