Read in English · Leer en español · Auf Deutsch lesen
Section 6.1 sur 6.8 · Intermédiaire · 24 min de lecture
Pourquoi distribuer, et les illusions de l’informatique distribuée
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Répartir un système sur de nombreuses machines apporte de vrais avantages, et des problèmes qui ne peuvent pas se produire à l’intérieur d’un seul programme. Dans cette leçon, vous verrez quand cela en vaut la peine, et comment en évaluer le prix avant de payer.
Imaginez un petit restaurant où un seul cuisinier fait tout dans une seule cuisine. C’est simple. Si le cuisinier est malade, le restaurant est fermé, mais rien ne se perd entre plusieurs personnes. Imaginez maintenant une chaîne, avec une boulangerie centrale, une cuisine à sauces et de nombreuses succursales, toutes reliées par des camionnettes de livraison. Elle peut servir bien plus de clients, et une cuisine peut fermer sans entraîner la fermeture des autres. Mais les camionnettes restent coincées dans les embouteillages et des commandes se perdent. Quand une livraison n’arrive pas, personne ne sait si la boulangerie n’a jamais reçu la commande ou si la camionnette est tombée en panne. Les logiciels distribués fonctionnent comme la chaîne.
Un système distribué est un ensemble de programmes, sur des machines différentes, qui coopèrent en échangeant des messages sur un réseau pour fournir ce que les utilisateurs perçoivent comme un seul service. Une application de livraison de repas en est un. L’idée clé de cette section est que la distribution est une décision qui a un prix, et non un signe de maturité.
Il existe une poignée de bonnes raisons de découper un système. La première est la montée en charge : le flux de positions des livreurs reçoit des milliers de mises à jour par seconde, les écrans d’inscription des restaurants quelques-unes par heure. La deuxième est l’isolation des pannes. Une fuite de mémoire dans les recommandations ne peut pas faire tomber l’étape de paiement, car elles s’exécutent sur des machines différentes. Viennent ensuite les mises en production indépendantes et l’autonomie des équipes. Ces raisons organisationnelles sont souvent les plus fortes. D’autres sont la géographie et la résidence des données, des besoins technologiques particuliers et l’isolation de sécurité, par exemple garder les données de carte dans un petit service renforcé. Mais chacune a, à l’intérieur d’un monolithe, un substitut partiel moins coûteux, comme davantage de copies derrière un équilibreur de charge, ou des caches. Examinez d’abord le substitut.
Le problème qui définit le domaine est la défaillance partielle : certaines parties fonctionnent tandis que d’autres sont en défaillance, lentes ou injoignables. Ici, le service des commandes demande au service de paiement d’autoriser vingt-quatre livres cinquante, et attend. Si aucune réponse n’arrive avant le délai d’expiration, peut-être la requête s’est-elle perdue, et aucun débit n’a eu lieu. Peut-être le service de paiement a-t-il planté ou s’est-il bloqué, et personne ne sait si la carte a été débitée. Ou peut-être la carte a-t-elle été débitée, et seule la réponse s’est perdue. Pour l’appelant, les trois cas sont identiques. Toute opération susceptible d’être retentée doit donc être idempotente : l’effectuer deux fois a le même effet que l’effectuer une fois. En général, chaque requête porte une clé unique que le récepteur mémorise.
Des ingénieurs de Sun Microsystems ont recensé huit fausses hypothèses que font les nouveaux venus. Les deux premières sont que le réseau est fiable et que la latence est nulle. Fixez donc un délai d’expiration pour chaque appel distant, et préférez quelques appels à gros grain à de nombreux petits appels. La bande passante n’est pas infinie, et le transport n’est pas gratuit. N’envoyez que les champs dont les consommateurs ont besoin. Le réseau n’est pas sécurisé, même à l’intérieur de votre centre de données. Authentifiez chaque appel entre services. La topologie change : utilisez donc la découverte de services, pas des adresses codées en dur. Et il y a plus d’un administrateur : un partenaire peut modifier une règle et casser vos appels. Enfin, le réseau n’est pas homogène : utilisez donc des formats standard et versionnés. Chacune de ces illusions provoque encore de vraies pannes.
Chaque raison de distribuer se paie dans la même monnaie. D’abord, la latence. Un appel à l’intérieur d’un processus prend quelques nanosecondes. Un aller-retour entre machines d’un même centre de données prend de quelques centaines de microsecondes à une milliseconde ou plus. Ensuite, l’observabilité. L’histoire d’une requête est éparpillée dans de nombreux journaux : il vous faut donc des identifiants de corrélation et un traçage distribué. Troisièmement, la cohérence. Avec des bases de données séparées, il n’y a pas de transaction partagée : les données vont donc diverger un temps et doivent converger. Quatrièmement, et c’est le coût le plus sous-estimé, la charge d’exploitation. Chaque service a besoin d’un pipeline, d’une supervision et de quelqu’un d’astreinte. Ces coûts augmentent avec le nombre d’unités déployables.
Vous pouvez évaluer une partie de ce prix sur le papier. Ici, passer une commande traverse une chaîne synchrone de cinq services. Chacun attend le suivant, et chacun, pris isolément, est disponible quatre-vingt-dix-neuf virgule neuf pour cent du temps. Mais les disponibilités se multiplient. Ensemble, ils ne donnent qu’environ quatre-vingt-dix-neuf virgule cinq pour cent : environ trois virgule six heures d’indisponibilité par mois. Et les latences s’additionnent. Avec vingt millisecondes de travail dans chaque service, plus environ deux par saut, la médiane est d’environ cent dix millisecondes. Cette dépendance au fait que l’autre partie soit disponible au même moment s’appelle le couplage temporel. Comptez donc les sauts synchrones, pas les services. Appelez en parallèle les services indépendants, mettez en cache les données stables, et laissez les parties qui n’ont pas besoin de répondre tout de suite travailler à partir d’une file.
La section complète contient les tableaux détaillés et une liste de contrôle à utiliser avant de distribuer. Ensuite, dans la section six point deux, nous nous intéressons à l’architecture à base de services et à l’architecture orientée services classique. À très vite.
Ce qu’il faut savoir
- Un système distribué est un ensemble de programmes, sur des machines différentes, qui coopèrent en échangeant des messages sur un réseau pour fournir ce que les utilisateurs perçoivent comme un seul service.
- Les raisons légitimes de distribuer sont la montée en charge, l’isolation des pannes, la déployabilité indépendante, l’autonomie des équipes, la géographie et la résidence des données, des besoins technologiques différents et l’isolation de sécurité ; chacune a, à l’intérieur d’un monolithe, un substitut partiel moins coûteux qu’il faut envisager en premier.
- La défaillance partielle est le problème qui définit le domaine : après un délai d’expiration, l’appelant ne peut pas savoir si la requête a été perdue, si l’appelé est tombé en panne ou si seule la réponse a été perdue ; les opérations retentées doivent donc être idempotentes.
- Les huit illusions de l’informatique distribuée (Deutsch et ses collègues de Sun, plus la huitième de Gosling) sont de fausses hypothèses sur la fiabilité, la latence, la bande passante, la sécurité, la topologie, l’administration, le coût du transport et l’homogénéité.
- La distribution coûte en latence, en observabilité, en cohérence et en effort d’exploitation, et ces coûts augmentent à peu près avec le nombre d’unités déployables.
- Dans une chaîne synchrone, les disponibilités se multiplient et les latences s’additionnent : cinq services à 99,9 % donnent environ 99,5 %, soit à peu près 3,6 heures d’indisponibilité par mois.
- Les appels synchrones lient les services entre eux à l’exécution (couplage temporel) : le nombre de sauts synchrones dans les parcours clés compte donc davantage que le nombre de services.
Ce qui rend un système distribué
Un système distribué est un ensemble de programmes, s’exécutant sur des ordinateurs différents, qui coopèrent en échangeant des messages sur un réseau pour fournir ce que les utilisateurs perçoivent comme un seul service. Votre application de livraison de repas en est un : le téléphone, les services de commandes et de paiement de l’entreprise, la tablette du restaurant, le téléphone du livreur et un processeur de paiement par carte jouent tous un rôle dans une même commande. Le chapitre 5 a décrit des styles déployés comme une seule unité (les monolithes) ; ce chapitre porte sur les styles dont les parties sont déployées séparément et communiquent à travers le réseau.
Leslie Lamport, qui a plus que quiconque contribué à donner au domaine des fondements mathématiques, en a résumé la difficulté essentielle dans un e-mail interne de mai 1987 : un système distribué est un système dans lequel la défaillance d’un ordinateur dont vous ignoriez jusqu’à l’existence
peut rendre votre propre ordinateur inutilisable. La boutade fonctionne parce qu’elle est vraie. Dès qu’un système dépend de machines qu’il ne contrôle pas et ne voit pas, toute une catégorie de pannes apparaît, qui ne peuvent tout simplement pas se produire à l’intérieur d’un seul processus.
C’est pourquoi la distribution est une décision qui a un prix, et non un signe de maturité. La suite de cette section expose les raisons qui justifient ce prix, les fausses hypothèses qui conduisent à le sous-estimer, et une manière d’en calculer une partie avant de payer.
Pourquoi distribuer
Il existe une poignée de raisons légitimes de répartir un système entre plusieurs processus et machines. Chacune correspond à un attribut de qualité du chapitre 2 qu’une seule unité de déploiement peine à fournir au-delà d’une certaine taille. Chacune a aussi, à l’intérieur d’un monolithe, un substitut partiel moins coûteux, et un bon architecte examine d’abord ce substitut.
Les raisons de distribuer, et ce qu’un monolithe bien construit sait déjà faire
| Raison | Ce qu’offre la distribution | Ce qu’un déploiement unique sait déjà faire |
|---|---|---|
| Montée en charge | Des parties soumises à des charges très différentes peuvent être mises à l’échelle séparément : le flux de positions des livreurs reçoit des milliers de mises à jour par seconde, les écrans d’inscription des restaurants quelques-unes par heure. | Exécuter de nombreuses répliques identiques derrière un équilibreur de charge, ajouter des répliques en lecture et des caches. Cela suffit pour la plupart des systèmes, et coûte bien moins cher. |
| Isolation des pannes | Une fuite de mémoire dans les recommandations ne peut pas faire tomber l’étape de paiement, car elles s’exécutent dans des processus différents, sur des machines différentes. | Des pools de threads séparés et des cloisonnements (bulkheads) à l’intérieur d’un processus donnent une isolation partielle ; un plantage fait quand même tout tomber. |
| Déployabilité indépendante | Une équipe peut mettre sa partie en production vingt fois par jour sans coordonner de mise en production avec qui que ce soit. | Un monolithe modulaire doté d’une bonne automatisation peut aussi être mis en production plusieurs fois par jour, mais chaque mise en production livre tout. |
| Autonomie des équipes | Les équipes sont responsables d’un service de bout en bout, choisissent leur rythme et en assurent l’astreinte ; la loi de Conway joue en votre faveur au lieu de jouer contre vous. | La responsabilité par module fonctionne bien jusqu’à quelques équipes ; au-delà, un build et une mise en production partagés deviennent une file d’attente. |
| Géographie et résidence des données | Exécuter des parties près des utilisateurs pour réduire la latence, ou conserver les données à caractère personnel dans une seule juridiction parce qu’une loi ou un contrat l’exige. | Déployer une copie complète par région (une cellule). C’est en soi une forme de distribution, mais une forme simple. |
| Besoins technologiques différents | Un modèle de recommandation a besoin de GPU, la recherche d’un moteur de recherche, une intégration avec un système hérité d’un environnement d’exécution particulier. | Appeler le composant spécialisé et garder tout le reste ensemble. |
| Isolation de sécurité | Confier les données de carte à un petit service distinct, renforcé et audité séparément, réduit le périmètre d’une évaluation PCI DSS. | Pas vraiment : tout ce qui se trouve dans le processus peut atteindre n’importe quoi d’autre en mémoire. |
Deux observations découlent de ce tableau. Premièrement, les raisons les plus fortes sont souvent organisationnelles : la déployabilité indépendante et l’autonomie des équipes comptent surtout quand de nombreuses équipes travaillent sur un même produit, ce qui explique pourquoi la loi de Conway (chapitre 3) revient sans cesse dans ce chapitre. Deuxièmement, la distribution est rarement du tout ou rien. Beaucoup de systèmes réussis sont un monolithe modulaire accompagné de deux ou trois services séparés, chacun détaché pour une raison précise et nommée.
La défaillance partielle : le problème qui définit le domaine
À l’intérieur d’un processus, un appel de fonction soit retourne, soit le processus entier meurt avec lui. À travers un réseau, il existe une troisième issue, et elle change tout : la défaillance partielle, dans laquelle certaines parties du système fonctionnent tandis que d’autres sont en défaillance, lentes ou injoignables. Pire, l’appelant ne peut souvent pas savoir lesquelles.
Prenons le service des commandes qui demande au service de paiement d’autoriser £24,50. Si aucune réponse n’arrive avant le délai d’expiration, le service des commandes ne sait pas si la requête s’est perdue en chemin, si le service de paiement a planté ou s’est bloqué avant ou après avoir débité la carte, ou si la carte a été débitée et que seule la réponse s’est perdue. Du côté de l’appelant, les trois situations sont identiques.
Trois défaillances qui se ressemblent
Les conséquences pratiques façonnent toute conception distribuée. Une nouvelle tentative pourrait débiter le client deux fois : les opérations susceptibles d’être retentées doivent donc pouvoir être répétées sans risque. Cette propriété est l’idempotence, et on l’obtient généralement en donnant à chaque requête une clé unique que le récepteur mémorise. Une dépendance lente est souvent pire qu’une dépendance morte, car les appelants continuent d’attendre et immobilisent des threads et des connexions ; quand un service ralentit, ses appelants épuisent leurs ressources et ralentissent à leur tour : c’est une défaillance en cascade. Et comme un réseau peut se scinder de sorte que deux groupes de machines ne puissent plus se joindre tout en continuant de fonctionner, des données conservées aux deux endroits peuvent diverger : c’est le sujet de la section sur CAP, plus loin dans ce chapitre.
Il n’existe pas non plus d’horloge globale. L’horloge de chaque machine dérive et est corrigée séparément : les horodatages de deux services ne peuvent donc pas être comparés en toute confiance, et « qu’est-ce qui s’est passé en premier ? » peut être une question difficile. L’article de Lamport de 1978 sur le temps et les horloges dans les systèmes distribués a montré comment ordonner des événements sans s’appuyer sur des horloges physiques ; pour les architectes, la leçon est simplement de ne pas fonder la justesse sur la comparaison d’heures murales entre machines.
Les huit illusions de l’informatique distribuée
Les illusions de l’informatique distribuée sont huit hypothèses que font les personnes qui découvrent les systèmes distribués ; elles sont toutes fausses et finissent toutes par causer des problèmes. La liste vient de Sun Microsystems : L Peter Deutsch en a établi sept en 1994, en s’appuyant sur quatre que des collègues, dont Bill Joy, avaient déjà relevées, et James Gosling, le créateur de Java, a ajouté la huitième vers 1997. Trois décennies plus tard, chacune décrit encore, cette semaine, une vraie panne quelque part.
Les huit illusions, avec une conséquence et une réponse de conception pour chacune
| Illusion | Ce qui tourne mal quand on y croit | Que faire à la place |
|---|---|---|
| 1. Le réseau est fiable | Un appel de paiement reste bloqué ; le thread appelant attend indéfiniment ; sous charge, tous les threads sont vite en attente et le service des commandes cesse de répondre à qui que ce soit. | Fixer un délai d’expiration pour chaque appel distant, ne retenter que les opérations idempotentes, placer des files là où le travail peut attendre (patrons de résilience). |
| 2. La latence est nulle | Une page de panier qui faisait 40 appels internes au processus fait désormais 40 appels réseau à 2 ms chacun ; 80 ms surgissent de nulle part, et bien plus en queue de distribution. | Concevoir des interfaces à gros grain, regrouper les requêtes, appeler en parallèle les services indépendants, mettre en cache les données stables. |
| 3. La bande passante est infinie | Chaque événement MenuUpdated transporte toutes les photos des plats ; une soirée chargée sature la liaison et retarde les mises à jour de position qui la partagent. | Envoyer des identifiants et les champs dont les consommateurs ont besoin, paginer, compresser, garder les données volumineuses à côté du code qui les traite. |
| 4. Le réseau est sécurisé | Les services à l’intérieur du centre de données acceptent n’importe quelle requête sans authentification ; un seul conteneur compromis peut appeler l’API de remboursement. | Chiffrer et authentifier le trafic entre services (par exemple avec TLS mutuel) et autoriser chaque appel : zero trust. |
| 5. La topologie ne change pas | Une adresse IP codée en dur dans la configuration cesse de fonctionner quand le conteneur est replanifié sur une autre machine. | Utiliser la découverte de services et des noms DNS, et s’attendre à ce que des instances apparaissent et disparaissent à chaque minute. |
| 6. Il y a un seul administrateur | Un fournisseur de cloud, l’équipe pare-feu d’un partenaire ou un autre service de l’entreprise modifie une règle, renouvelle un certificat ou abaisse une limite de débit, et vos appels commencent à échouer. | Savoir qui contrôle chaque saut, superviser les dépendances qui ne vous appartiennent pas, convenir de contrats et de préavis de changement. |
| 7. Le coût du transport est nul | La sérialisation consomme du CPU, et les données qui traversent des zones de disponibilité, des régions ou l’internet public sont facturées au gigaoctet ; la facture réseau augmente plus vite que le trafic. | Mesurer le coût des flux de données, garder le trafic bavard en local, choisir des formats compacts là où le volume est élevé. |
| 8. Le réseau est homogène | Un service suppose que chaque client parle la même version de protocole, utilise le même encodage de caractères et la même horloge ; un réseau mobile, l’ancien système d’un partenaire ou un proxy contredit cette hypothèse. | Utiliser des formats et des protocoles standard, explicitement versionnés, et tester l’interopérabilité avec les vrais clients. |
Ce que coûte la distribution
Chaque raison de distribuer se paie dans la même monnaie. La latence est le premier coût. Un appel de fonction à l’intérieur d’un processus prend quelques nanosecondes ; un aller-retour réseau entre machines du même centre de données prend généralement de quelques centaines de microsecondes à une milliseconde ou plus, avant toute sérialisation ou tout traitement. Entre continents, la physique fixe un plancher : la lumière dans une fibre optique voyage à environ 200 000 km par seconde, si bien qu’un aller-retour Londres–New York (environ 5600 km dans chaque sens) ne peut pas prendre moins d’environ 56 ms. L’arithmétique de la latence de queue du chapitre 2 s’applique à chaque saut.
L’observabilité est le deuxième. Dans un monolithe, une trace de pile raconte toute l’histoire d’une requête en échec. Dans un système distribué, l’histoire est éparpillée dans les journaux de nombreux services sur de nombreuses machines. L’observabilité, c’est-à-dire la capacité à comprendre ce que fait le système à partir de ce qu’il produit, doit être prévue dès la conception : identifiants de corrélation transmis à chaque appel, journaux centralisés, métriques par service et traçage distribué (chapitre 9).
La cohérence est le troisième. Une seule transaction de base de données peut mettre à jour une commande et son paiement de façon atomique. Quand la commande et le paiement vivent dans des services différents avec des bases de données différentes, il n’y a pas de transaction partagée ; le système doit accepter une période pendant laquelle les deux divergent et s’assurer qu’ils convergent. Les sections suivantes présentent les outils : les modèles de cohérence, ainsi que les sagas et le patron outbox.
La charge d’exploitation est le quatrième, et celui que l’on sous-estime le plus souvent. Chaque service a besoin d’un pipeline de build, d’un déploiement, d’une configuration, de secrets, d’une supervision, d’alertes, d’une planification de la capacité, de correctifs de sécurité et de quelqu’un d’astreinte. Les tests changent aussi : une modification n’est sûre que si chaque consommateur de son interface fonctionne encore, ce qui demande des tests de contrat et des changements rétrocompatibles. Ces coûts sont à peu près proportionnels au nombre d’unités déployables, c’est pourquoi le nombre de services compte.
Faire le calcul : les chaînes synchrones
Le moment le moins coûteux pour découvrir qu’une conception distribuée est trop fragile ou trop lente, c’est sur le papier. L’outil est la règle des éléments en série du chapitre 2 : si une requête a besoin que chaque composant d’une chaîne fonctionne, la disponibilité de la chaîne est le produit des disponibilités des composants, et sa latence est à peu près la somme de leurs latences.
Une chaîne synchrone de cinq services
Exemple commenté
Exemple commenté : le budget de disponibilité et de latence d’une chaîne d’appels
Une plateforme de livraison de repas traite Commander au moyen d’une chaîne synchrone : l’API gateway appelle le service des commandes, qui appelle le service de la carte, qui appelle la tarification, qui appelle le paiement. Chaque service, pris isolément, atteint une disponibilité de 99,9 % (mesurée dans le temps, sur un mois de 30 jours soit 43 200 minutes), répond avec une médiane de 20 ms pour son propre travail et a un p99 de 250 ms. Chaque saut réseau ajoute environ 2 ms. Supposons, avec générosité, que les défaillances sont indépendantes et que toute défaillance fait échouer la requête entière.
| Un service | Disponibilité 0,999 = 99,9 % ; indisponibilité autorisée 0,001 × 43 200 = 43,2 minutes par mois. |
|---|---|
| Deux en série | 0,999 × 0,999 = 0,998 (99,8 %) ; environ 86 minutes par mois. |
| Cinq en série | 0,9995 ≈ 0,99501 (99,50 %) ; indisponible environ 0,005 × 43 200 ≈ 216 minutes, soit 3,6 heures, par mois. Cinq services respectables chacun pris isolément ont produit un système à deux neuf et demi. |
| Dix en série | 0,99910 ≈ 0,99004 (99,0 %) ; environ 430 minutes, plus de 7 heures, par mois. |
| Latence médiane, cinq en série | Les latences s’additionnent : environ 5 × (20 ms + 2 ms) ≈ 110 ms, contre 20 ms pour le même travail effectué dans un seul processus. (À strictement parler, les médianes ne s’additionnent pas exactement ; la somme est une bonne première estimation.) |
| Latence de queue, cinq en série | La probabilité qu’une requête rencontre au moins un saut dans son 1 % le plus lent est de 1 − 0,995 ≈ 4,9 % : environ 1 requête sur 20 est donc ralentie par au moins un saut à 250 ms. Avec dix sauts, elle est de 1 − 0,9910 ≈ 9,6 %. |
| Ce qu’il faut pour garder 99,9 % de bout en bout | Chacun des cinq services aurait besoin d’environ 99,98 % (0,99985 ≈ 0,9990) : environ 8,6 minutes d’indisponibilité par mois chacun, ce qui, en pratique, suppose une reprise automatisée. |
| Réponses de conception | Appeler la carte et la tarification en parallèle (la latence devient celle du plus lent des deux, et non la somme) ; mettre en cache les données de la carte dans le service des commandes ; rendre la confirmation du paiement asynchrone, pour que la commande soit acceptée puis payée peu après ; supprimer les sauts qui ne font que transmettre des requêtes. |
Le chapitre 2 a présenté la règle selon laquelle les disponibilités se multiplient en série ; c’est avec la distribution que cette règle commence à mordre, car chaque appel réseau ajoute un composant à la série.
L’hypothèse d’indépendance flatte le résultat. Les services qui partagent une base de données, un cluster, un système de configuration ou un train de mises en production tombent en panne ensemble, et un appelé lent peut épuiser les threads de l’appelant et propager la panne vers le haut.
Le remède consiste rarement à rendre chaque service héroïquement fiable. Il consiste à raccourcir les chaînes synchrones et à laisser les parties qui n’ont pas besoin de répondre immédiatement travailler à partir d’une file.
Le quantum d’architecture, revisité
Le chapitre 5 a présenté le quantum d’architecture : une partie déployable indépendamment, à forte cohésion fonctionnelle, données comprises, à laquelle s’applique un même ensemble de caractéristiques d’architecture (Styles architecturaux, patrons et tactiques). Dans Software Architecture: The Hard Parts (2021), Ford, Richards, Sadalage et Dehghani affinent l’idée avec deux types de couplage. Le couplage statique est ce qui doit être présent pour qu’une partie puisse être déployée et exécutée : son code, ses bibliothèques, sa base de données et son infrastructure. Le couplage dynamique est la manière dont les parties dépendent les unes des autres pendant l’exécution : si elles s’appellent de façon synchrone ou asynchrone, et comment elles se coordonnent et restent cohérentes.
L’exemple commenté montre pourquoi l’aspect dynamique compte. Pendant toute la durée d’un appel synchrone, l’appelant ne peut pas terminer son travail si l’appelé n’est pas disponible et rapide ; les deux sont liés sur le plan opérationnel, et leurs disponibilités et latences se combinent comme calculé ci-dessus. Cinq services déployés séparément dans une chaîne synchrone constituent cinq unités de déploiement, mais pour la disponibilité de Commander, ils se comportent comme une seule unité fragile. Cette dépendance au fait que l’autre partie soit disponible au même moment est le couplage temporel. La messagerie asynchrone en supprime une grande partie : si le service des commandes dépose un message dans une file durable, le service de paiement peut être indisponible pendant une minute puis rattraper son retard, et le client obtient quand même un numéro de commande. Le prix à payer est que le client n’obtient pas immédiatement de réponse définitive, ce qui est autant une décision produit qu’une décision technique.
C’est la leçon la plus profonde de la section. Tracer les frontières des services, c’est la moitié de la conception ; décider quelles interactions doivent être synchrones, et garder cet ensemble réduit, c’est l’autre moitié.
Liste de contrôle : avant de distribuer
Utilisez ces questions avant de répartir un système, ou une partie de système, à travers le réseau.
- Nommer le facteurQuelle raison du tableau ci-dessus s’applique, à quelle partie, avec quelles preuves ? « Les microservices, c’est moderne » n’est pas un facteur.
- Essayer d’abord la solution la moins coûteuseDes répliques, du cache, une réplique en lecture, un processus d’arrière-plan ou une frontière de module plus nette à l’intérieur du monolithe pourraient-ils répondre au besoin ?
- Dessiner le graphe d’appelsCompter les sauts synchrones dans chaque parcours utilisateur clé et calculer le budget de disponibilité et de latence, comme dans l’exemple commenté.
- Décider à qui appartiennent quelles donnéesChaque donnée a besoin d’un seul service propriétaire ; tout ce qui doit changer de façon atomique avec elle appartient probablement au même service.
- Concevoir pour la défaillance partielleDélais d’expiration, nouvelles tentatives avec idempotence, solutions de repli et ce que voit l’utilisateur quand une dépendance est indisponible, décidés avant la première panne.
- Vérifier les prérequisLe provisionnement et le déploiement automatisés, la supervision, les journaux centralisés et le traçage distribué doivent exister, ou être planifiés et financés.
- Aligner les équipes sur les frontièresChaque service doit avoir une seule équipe propriétaire, capable de le modifier, de le déployer et de l’assurer sans rien demander aux autres.
Vérifiez votre compréhension
Un service appelle un service de stock sans délai d’expiration. Le service de stock devient très lent au lieu de tomber en panne. Pourquoi cela peut-il être pire que s’il avait planté ?
Un service planté échoue vite et ses appelants peuvent réagir ; un service lent fait attendre ses appelants, si bien que leurs threads et leurs connexions se remplissent jusqu’à ce qu’eux aussi cessent de répondre, ce qui propage la panne sous forme de défaillance en cascade.
Trois services ont chacun une disponibilité de 99,95 % et sont appelés en série à chaque requête. Quelle est la disponibilité approximative du chemin de requête ?
0,99953 ≈ 0,9985, soit environ 99,85 %, en supposant des défaillances indépendantes.
Pourquoi l’illusion « le coût du transport est nul » compte-t-elle même quand la latence et la bande passante sont satisfaisantes ?
Déplacer des données coûte du CPU pour la sérialisation et de l’argent pour le trafic entre zones, régions ou fournisseurs : une conception bavarde ou volumineuse peut donc coûter cher même quand elle est assez rapide.
Pourquoi cinq services déployés séparément peuvent-ils quand même se comporter comme une seule unité du point de vue de la disponibilité ?
Si une requête les appelle de façon synchrone en chaîne, les cinq doivent être disponibles au même moment ; leurs disponibilités se combinent, ils sont donc couplés temporellement malgré des déploiements séparés.
Sources et lectures complémentaires
- Leslie Lamport: the 1987 email defining a distributed system (lamport.org)
- The Eight Fallacies of Distributed Computing (James Gosling's page)
- Software Engineering Radio episode 470: L Peter Deutsch on the fallacies of distributed computing (2021)
- Martin Fowler: Microservices and the First Law of Distributed Objects
- Google SRE book: Addressing Cascading Failures
Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.
Demander : « Notre étape de paiement appelle six services l’un après l’autre. Comment déterminer si c’est trop fragile, et que devons-nous changer en premier ? »