Read in English · Leer en español · Auf Deutsch lesen
Section 9.2 sur 9.8 · Intermédiaire · 27 min de lecture
Équilibrage de charge, absence d’état et sessions
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Bienvenue dans la section neuf point deux. La scalabilité horizontale vous donne de nombreuses instances. Cette leçon porte sur ce qui se trouve devant elles, et sur la manière de répartir le travail pour que les utilisateurs ne remarquent jamais les instances qui vont et viennent.
Imaginez un supermarché où un membre du personnel oriente les clients vers les caisses. Envoyer les gens vers chaque caisse à tour de rôle est équitable mais naïf. Un client avec un chariot rempli pour une famille de six personnes bloque une caisse pendant que la suivante est inoccupée. Envoyer chaque client vers la caisse où la file est la plus courte fonctionne bien mieux. La personne qui oriente doit aussi savoir quelles caisses sont ouvertes, et cesser d’envoyer des clients vers une caisse sur le point de fermer, le temps que sa file se termine. Et tout fonctionne au mieux si n’importe quelle caisse peut servir n’importe quel client. C’est le travail d’un équilibreur de charge.
Un équilibreur de charge décide quelle instance reçoit chaque connexion ou requête. Il vérifie aussi quelles instances sont en bonne santé, donne aux clients une adresse stable unique, et assure souvent la terminaison TLS. Un équilibreur de couche 4 ne voit que les adresses IP et les ports. Il choisit un backend une fois par connexion et la transmet sans regarder à l’intérieur. Il est donc extrêmement rapide, et il fonctionne avec n’importe quel protocole. Un proxy de couche 7 comprend HTTP. Il lit chaque requête : il peut donc router, réessayer, limiter le débit et journaliser chacune d’elles. Ici, il route selon le chemin : les requêtes de commande vers le pool des commandes, les requêtes de carte vers le pool de la carte, et les recherches vers le pool de recherche. Cette différence prend de nombreuses équipes au dépourvu. gRPC envoie de nombreuses requêtes sur une seule connexion de longue durée. Un équilibreur de couche 4 choisit une fois par connexion : les nouvelles instances peuvent donc ne rien recevoir. gRPC a besoin d’un proxy de couche 7, ou d’un équilibrage dans le client.
Quelle instance doit recevoir la requête suivante ? Prenons deux instances, A et B, qui traitent chacune une requête à la fois. Huit requêtes arrivent à dix millisecondes d’intervalle : d’abord un rapport qui prend cent millisecondes, puis sept lectures de la carte qui en prennent dix. Le round robin envoie les requêtes à tour de rôle, quel que soit leur coût. Trois lectures de la carte atterrissent donc sur l’instance A, bloquées derrière le rapport. Elles prennent quatre-vingt-dix, quatre-vingts et soixante-dix millisecondes. Les lectures de la carte prennent en moyenne environ quarante et une millisecondes, et la pire quatre-vingt-dix. Pendant ce temps, l’instance B est inactive la moitié du temps. « Moins de connexions » envoie chaque requête à l’instance qui en a le moins en cours. Le rapport va à A, et comme B est toujours libérée à temps, chaque lecture de la carte va à B. Désormais, chaque lecture de la carte prend dix millisecondes, et la moyenne des huit requêtes passe d’environ quarante-neuf à environ vingt et une. Le round robin compte les requêtes. « Moins de connexions » compte le travail en cours : les nouvelles requêtes se détournent donc automatiquement d’une instance occupée ou lente.
Pourquoi ne pas toujours choisir l’instance la moins chargée du pool ? Parce que, dans un grand système, des dizaines d’équilibreurs ne voient chacun que leurs propres requêtes, ou un relevé de charge vieux de quelques secondes. S’ils choisissent tous l’instance qui semblait la moins chargée, ils choisissent tous la même. Elle est submergée, et le troupeau passe à la suivante. Michael Mitzenmacher a analysé une alternative d’une simplicité frappante : choisir deux instances au hasard, et envoyer la requête à la moins chargée. Le hasard empêche l’effet de troupeau, et la comparaison évite les pires instances. C’est désormais une option standard dans des proxys comme Envoy, et un bon choix par défaut partout où il y a de nombreux équilibreurs.
Un équilibreur doit savoir quelles instances peuvent recevoir du trafic. Une sonde de vivacité demande si le processus est bloqué sans possibilité de rétablissement. Une sonde de disponibilité demande s’il doit recevoir du trafic maintenant. L’erreur classique consiste à appeler la base de données depuis les sondes de vivacité. Une brève panne de la base de données fait échouer toutes les sondes, toutes les instances redémarrent à la fois, et la tempête de redémarrages prolonge la panne. Gardez la sonde de vivacité locale et minimale.
Les instances quittent le pool à chaque déploiement et à chaque retrait d’instances. Le drainage des connexions en fait un non-événement. D’abord, l’instance reçoit un signal d’arrêt. Sa sonde de disponibilité échoue : l’équilibreur de charge cesse donc de lui envoyer de nouvelles requêtes. Les requêtes déjà en cours se terminent normalement. Les connexions keep-alive inactives sont fermées, pour que les clients se reconnectent ailleurs. Puis le processus s’arrête proprement, avant la fin de son délai de grâce.
Les applications plus anciennes conservaient la session de chaque utilisateur dans la mémoire d’un seul serveur ; les équilibreurs proposaient donc des sessions persistantes. Mais la charge devient inégale, et quand cette instance tombe en panne, les paniers disparaissent et les gens sont déconnectés. Le remède consiste à sortir l’état de session de l’instance. Un jeton signé de courte durée peut transporter l’identité de l’utilisateur avec chaque requête. Un stockage de sessions partagé gère un état plus volumineux et la déconnexion immédiate, au prix d’un appel réseau par requête. Et l’état qui doit survivre, comme un panier que l’utilisateur s’attend à retrouver le lendemain, a sa place dans la base de données. N’importe quelle instance peut alors servir n’importe quelle requête.
La section complète traite aussi du hachage et de l’équilibrage de charge global avec le DNS et l’anycast. Ensuite, dans la section neuf point trois, nous verrons ce qui se passe quand le travail arrive plus vite qu’il ne peut être fait. À très vite.
Ce qu’il faut savoir
- Un équilibreur de charge répartit les connexions ou les requêtes sur les instances en bonne santé, masque les changements d’instances derrière une adresse unique et assure souvent la terminaison TLS ; il peut se trouver en périphérie ou à l’intérieur des clients et des sidecars.
- Les équilibreurs de couche 4 transmettent les connexions selon l’adresse et le port, rapidement et sans rien savoir du protocole ; les équilibreurs de couche 7 lisent chaque requête HTTP et peuvent router, réessayer et limiter le débit, ce qu’exigent les protocoles à équilibrer par requête comme gRPC sur HTTP/2.
- Le round robin ignore le coût des requêtes ; « moins de connexions » et la puissance de deux choix aléatoires (Mitzenmacher) s’adaptent à la charge, et deux choix aléatoires évitent l’effet de troupeau que provoquent de nombreux équilibreurs lorsqu’ils se ruent tous sur l’instance la moins chargée.
- Un échec de la sonde de vivacité redémarre une instance, et un échec de la sonde de disponibilité la retire seulement de la rotation ; laissez les dépendances partagées hors des sondes de vivacité pour qu’un incident passager de la base de données ne puisse pas redémarrer tout le parc.
- Le drainage des connexions arrête les nouvelles requêtes vers une instance qui s’en va et laisse le travail en cours se terminer, si bien que les déploiements et les retraits d’instances ne provoquent aucune erreur.
- Les sessions persistantes provoquent une charge inégale et des sessions perdues en cas de défaillance ; déplacez l’état de session vers des jetons signés, un stockage partagé ou la base de données pour que n’importe quelle instance puisse servir n’importe quelle requête.
- L’équilibrage de charge global oriente les utilisateurs vers les régions par des réponses DNS, limitées par la mise en cache et les TTL, ou par anycast, qui annonce une même adresse depuis de nombreux sites et laisse le routage choisir le plus proche.
Ce que fait un équilibreur de charge
Un équilibreur de charge se place entre les clients et un pool d’instances interchangeables et décide, pour chaque connexion ou requête, quelle instance la reçoit. La répartition n’est qu’une partie du travail. Un équilibreur de charge vérifie aussi quelles instances sont en bonne santé et cesse d’envoyer du trafic à celles qui ne le sont pas ; il donne aux clients une adresse stable unique pendant que les instances vont et viennent au gré des déploiements et de l’autoscaling ; il assure souvent la terminaison TLS, de sorte que les certificats sont gérés en un seul endroit ; et en couche 7, il peut router, réessayer, limiter le débit et enregistrer chaque requête. C’est donc le composant qui rend la scalabilité horizontale invisible pour les clients.
L’équilibrage de charge intervient à plusieurs niveaux. Un équilibreur de charge cloud managé ou une paire de proxys comme HAProxy, NGINX ou Envoy se trouve généralement en périphérie. Entre les services, l’équilibrage se fait de plus en plus côté client : une bibliothèque cliente gRPC ou un proxy sidecar de service mesh (section 6.3) détient la liste des instances en bonne santé fournie par la découverte de services et en choisit une lui-même, ce qui supprime un saut réseau et un composant central. Quelle que soit la forme, les mêmes décisions reviennent : ce que l’équilibreur peut voir, quel algorithme il utilise, comment il décide qu’une instance est en bonne santé, et comment les instances partent sans perdre de travail.
Équilibrage de charge de couche 4 et de couche 7
Les numéros de couche viennent du modèle réseau OSI. Un équilibreur de charge de couche 4 travaille au niveau de la couche transport : il voit les adresses IP et les ports TCP ou UDP, choisit un backend à l’ouverture d’une connexion, puis transmet les paquets de cette connexion sans regarder à l’intérieur. Comme il en fait très peu, il est extrêmement rapide et fonctionne avec n’importe quel protocole, y compris les bases de données, les courtiers de messages et le trafic chiffré qu’il ne peut pas lire. Maglev de Google et les équilibreurs de charge réseau des principaux clouds sont des conceptions de couche 4 qui traitent des millions de paquets par seconde.
Un équilibreur de charge de couche 7 est un proxy inverse qui comprend le protocole applicatif, presque toujours HTTP. Il termine la connexion du client (et généralement TLS), lit chaque requête, et ouvre ou réutilise ses propres connexions vers les backends. Cela lui permet de router selon le chemin, l’hôte, un en-tête ou un cookie (/orders/* vers le pool de commandes, /menus/* vers le pool de la carte), d’équilibrer chaque requête plutôt que chaque connexion, de réessayer une requête échouée sur une autre instance, d’appliquer des limites de débit, d’ajouter des en-têtes de traçage et de journaliser chaque requête avec son statut et sa latence. Le prix à payer est davantage de traitement par requête, un composant qui doit détenir les clés TLS, et un endroit de plus où les délais d’expiration et les nouvelles tentatives doivent être configurés de manière cohérente avec le reste du système (section 9.4).
Une différence prend de nombreuses équipes au dépourvu. HTTP/2 et gRPC multiplexent de nombreuses requêtes sur une seule connexion de longue durée. Un équilibreur de couche 4 choisit un backend une fois par connexion, si bien qu’un client qui ouvre une seule connexion envoie toutes ses requêtes à une seule instance, quel que soit le nombre d’instances : les nouvelles instances ajoutées par l’autoscaling ne reçoivent rien tant que les clients ne se reconnectent pas. Équilibrer le trafic gRPC exige donc un proxy de couche 7 ou un équilibrage côté client par requête.
Une organisation typique à deux niveaux
Les algorithmes d’équilibrage de charge
L’algorithme décide quelle instance en bonne santé reçoit la connexion ou la requête suivante. Les plus simples ignorent à quel point les instances sont occupées ; les meilleurs utilisent une mesure de la charge ; ceux fondés sur le hachage envoient délibérément les requêtes liées au même endroit. Le tableau résume les choix courants.
Algorithmes d’équilibrage de charge courants
| Algorithme | Comment il choisit | Points forts | Faiblesses |
|---|---|---|---|
| Round robin (à tour de rôle) | Chaque instance à tour de rôle | Simple, prévisible, aucun état sur la charge | Aveugle au coût des requêtes et aux instances lentes |
| Round robin pondéré | À tour de rôle, proportionnellement à des poids configurés | Mélange des tailles d’instances ; augmente progressivement la part des nouvelles versions (canaris) | Les poids sont des estimations statiques ; reste aveugle à la charge réelle |
| Aléatoire | Uniformément au hasard | Ne nécessite aucun état partagé : de nombreux équilibreurs peuvent donc l’utiliser indépendamment | Inégal par hasard, surtout avec peu de requêtes |
| Moins de connexions (least connections, ou moins de requêtes en attente) | L’instance qui a le moins de requêtes en cours | S’adapte aux coûts de requête inégaux et aux instances lentes | Exige des comptes exacts ; chacun des nombreux équilibreurs ne voit que les siens ; les nouvelles instances peuvent être submergées |
| Pondéré par la latence (par exemple EWMA du temps de réponse) | L’instance qui a la meilleure latence récente, souvent combinée à la charge | S’écarte rapidement des instances dégradées | Peut osciller ; une instance qui échoue vite paraît attrayante si les erreurs ne sont pas pénalisées |
| Hachage (IP source, en-tête, hachage cohérent) | Une même clé correspond toujours à la même instance | Affinité pour les caches et les sessions ; le hachage cohérent déplace peu de clés quand le pool change | Les clés chaudes surchargent une instance ; distribution inégale des clés |
| Puissance de deux choix aléatoires (power of two choices) | Choisir deux instances au hasard ; envoyer à la moins chargée | Presque aussi bon que « moins de connexions », sans effet de troupeau, avec peu de coordination | Légèrement moins bon qu’un choix global parfait fondé sur une information parfaite |
Exemple commenté
Exemple commenté : pourquoi « moins de connexions » bat le round robin quand les coûts des requêtes diffèrent
Deux instances, A et B, traitent chacune une requête à la fois (les instances réelles ont davantage de threads, mais l’effet est le même à plus grande échelle). Huit requêtes arrivent à 10 ms d’intervalle. La première est un rapport de ventes de restaurant qui coûte 100 ms ; les sept autres sont des lectures de la carte qui coûtent 10 ms chacune.
| Round robin : répartition | A reçoit les requêtes 1, 3, 5, 7 ; B reçoit 2, 4, 6, 8, quel que soit leur coût |
|---|---|
| Round robin sur A | R1 s’exécute de 0 à 100 ms. R3 (arrivée à 20) attend et se termine à 110 : 90 ms. R5 (40) se termine à 120 : 80 ms. R7 (60) se termine à 130 : 70 ms |
| Round robin sur B | R2, R4, R6 et R8 se terminent chacune 10 ms après leur arrivée ; B est inactive la moitié du temps |
| Résultat du round robin | Lectures de la carte : 90, 80, 70, 10, 10, 10, 10 ms, moyenne 290 ÷ 7 ≈ 41 ms, pire cas 90 ms. Les huit requêtes : moyenne (100 + 290) ÷ 8 ≈ 49 ms |
| « Moins de connexions » : répartition | R1 va à A (les deux sont inactives). À 10 ms, A a 1 requête en cours et B en a 0 : R2 va donc à B ; B est toujours de nouveau libre avant l’arrivée suivante, donc R3 à R8 vont toutes à B |
| Résultat de « moins de connexions » | Chaque lecture de la carte prend 10 ms ; le rapport prend 100 ms. Moyenne (100 + 70) ÷ 8 ≈ 21 ms, pire lecture de la carte 10 ms |
| Pourquoi | Le round robin compte les requêtes, pas le travail. « Moins de connexions » compte le travail en cours, qui, d’après la loi de Little (en cours = débit × temps), augmente sur l’instance la plus lente ou la plus occupée, si bien que les nouvelles requêtes s’en détournent automatiquement |
Le même effet apparaît chaque fois que les coûts des requêtes varient fortement (recherches contre consultations, exports contre lectures) ou qu’une instance est plus lente que ses pairs à cause d’un voisin bruyant, d’une pause du ramasse-miettes ou d’un cache froid.
« Moins de connexions » exige un compte qui ait du sens. Avec HTTP/2 ou gRPC, de nombreuses requêtes partagent une même connexion : les équilibreurs comptent donc plutôt les requêtes en attente ; avec des connexions WebSocket de longue durée, le compte mesure des sessions, pas la charge.
Une instance qui vient de démarrer a zéro connexion et peut être submergée avant que ses caches ne soient chauds ; de nombreux équilibreurs proposent une période de démarrage progressif (slow start) qui augmente sa part en une minute environ.
La puissance de deux choix aléatoires
Si « moins de connexions » fonctionne si bien, pourquoi ne pas toujours choisir l’instance la moins chargée globalement ? Parce que, dans un grand système, il n’existe aucune vue unique et actuelle de la charge. Des dizaines d’équilibreurs de charge ou des milliers d’équilibreurs côté client ne voient chacun que leurs propres requêtes, ou un relevé de charge vieux de quelques secondes. S’ils choisissent tous l’instance qui semblait la moins chargée, ils choisissent tous la même, qui est aussitôt submergée, puis ils passent tous à la suivante : un troupeau qui poursuit la charge à travers le pool.
La thèse de doctorat de Michael Mitzenmacher (1996), publiée sous le titre The Power of Two Choices in Randomized Load Balancing (IEEE Transactions on Parallel and Distributed Systems, 2001) et s’appuyant sur des travaux antérieurs d’Azar, Broder, Karlin et Upfal, a analysé une alternative d’une simplicité frappante : choisir deux instances au hasard et envoyer la requête à la moins chargée des deux. Dans le modèle classique des balles et des urnes, placer n requêtes au hasard sur n serveurs donne un serveur le plus chargé avec environ log n ÷ log log n requêtes ; avec deux choix aléatoires, ce nombre tombe à environ log log n ÷ log 2, une amélioration exponentielle, tandis qu’un troisième choix n’apporte que peu de chose de plus. Le hasard empêche l’effet de troupeau, et la comparaison évite les pires instances.
La puissance de deux choix aléatoires est désormais l’option par défaut ou une option standard dans des proxys très répandus : l’équilibreur least-request d’Envoy compare par défaut deux hôtes choisis au hasard, et NGINX propose random two least_conn. C’est un bon choix par défaut pour l’équilibrage côté client dans tout système comportant de nombreux équilibreurs.
Hachage et affinité
Parfois, le but est d’envoyer des requêtes liées à la même instance : à un nœud de cache qui détient déjà la carte d’un restaurant, ou à l’instance qui détient une session WebSocket. Hacher une clé (identifiant du restaurant, identifiant de l’utilisateur, IP source) vers une instance permet d’y parvenir. Un simple hash mod N réaffecte la plupart des clés chaque fois que N change, ce qui vide tous les caches au pire moment, pendant un ajout d’instances. Le hachage cohérent (présenté à la section 7.4) ne déplace qu’environ 1 ÷ N des clés quand une instance arrive ou part. Sa faiblesse, ce sont les clés chaudes : le restaurant le plus populaire à midi tombe sur une seule instance. Des variantes comme le hachage cohérent à charge bornée plafonnent la part de chaque instance et déversent l’excédent sur l’instance suivante de l’anneau.
Contrôles de santé : vivacité et disponibilité
Un équilibreur de charge doit savoir quelles instances peuvent recevoir du trafic. Un contrôle de santé actif sonde chaque instance périodiquement, par exemple GET /healthz toutes les cinq secondes, la marque comme hors service après quelques échecs consécutifs et de nouveau en service après quelques succès. Les contrôles passifs, souvent appelés détection des valeurs aberrantes (outlier detection), observent le trafic réel et éjectent une instance qui renvoie, par exemple, cinq erreurs serveur consécutives. Les deux se complètent : les contrôles actifs trouvent les instances qui ne reçoivent pas de trafic, et les contrôles passifs réagissent aux défaillances qu’une sonde triviale ne met pas à l’épreuve.
Kubernetes a rendu explicite une distinction importante. Une sonde de vivacité (liveness probe) demande si le processus est bloqué sans possibilité de rétablissement (interblocage, mémoire épuisée dans une boucle) ; si elle échoue, l’instance est redémarrée. Une sonde de disponibilité (readiness probe) demande si l’instance doit recevoir du trafic maintenant ; si elle échoue, l’instance est retirée de l’équilibreur de charge mais continue de tourner, par exemple pendant qu’elle chauffe ses caches au démarrage, ou pendant qu’elle déleste de la charge. Une troisième, la sonde de démarrage (startup probe), met les deux autres en attente pendant qu’une application lente démarre.
L’erreur classique consiste à vérifier des dépendances partagées dans ces sondes. Si la sonde de vivacité de chaque instance appelle la base de données, une brève panne de la base fait échouer toutes les sondes, l’orchestrateur redémarre toutes les instances à la fois, et la tempête de redémarrages (caches froids, milliers de nouvelles connexions) prolonge la panne bien après le retour de la base de données. Des contrôles de disponibilité qui incluent une dépendance partagée peuvent de même marquer tout le parc comme non disponible, transformant des erreurs partielles en panne totale. Gardez la sonde de vivacité locale et minimale ; laissez la sonde de disponibilité refléter la capacité propre de l’instance à servir ; et gérez les défaillances des dépendances avec les patrons de la section 9.4. Les équilibreurs de charge se prémunissent contre le cas extrême en échouant en mode ouvert : un Application Load Balancer d’AWS route vers toutes les cibles si toutes échouent à leurs contrôles de santé, et Envoy ignore l’état de santé quand la part d’hôtes en bonne santé tombe sous un seuil de panique (50 % par défaut), au motif qu’envoyer du trafic à des instances douteuses vaut mieux que de ne l’envoyer nulle part.
Drainage des connexions et arrêt en douceur
Les instances quittent le pool en permanence : chaque déploiement les remplace, l’autoscaling les retire, et la plateforme les déplace lors des maintenances. Si une instance est simplement tuée, chaque requête qu’elle traitait échoue, et les clients voient des erreurs pendant ce qui devrait être une mise en production de routine. Le drainage des connexions (appelé deregistration delay chez AWS) retire l’instance de la rotation de l’équilibreur pour qu’elle ne reçoive plus de nouvelles requêtes, attend que les requêtes déjà en cours se terminent, et ne la laisse s’arrêter qu’ensuite.
L’instance doit coopérer. À la réception de son signal d’arrêt, elle doit faire échouer sa sonde de disponibilité, continuer à servir les requêtes en cours, dire aux clients sur des connexions keep-alive d’aller ailleurs (un en-tête Connection: close en HTTP/1.1, une trame GOAWAY en HTTP/2), terminer ou rendre le travail d’arrière-plan, et se terminer avant la fin du délai de grâce. Dans Kubernetes, le retrait d’un pod de l’équilibreur de charge et l’envoi de SIGTERM se produisent en parallèle : une requête peut donc encore arriver juste après le signal ; un court délai pré-arrêt (pre-stop) de quelques secondes couvre cette situation de concurrence. Le délai de grâce par défaut est de 30 secondes, après quoi le processus est tué. Les requêtes de longue durée (gros envois de fichiers, réponses en streaming, WebSockets) exigent soit un délai de grâce plus long, soit un protocole qui permet aux clients de se reconnecter ailleurs sans perdre de travail.
L’arrêt en douceur en cinq étapes
Les sessions persistantes et leurs problèmes
Les applications web plus anciennes conservaient la session de chaque utilisateur (qui est connecté, ce que contient le panier) dans la mémoire du serveur qui l’avait créée. Derrière un équilibreur de charge, cela ne fonctionne que si toutes les requêtes d’un utilisateur reviennent vers ce serveur ; les équilibreurs proposent donc des sessions persistantes (sticky sessions, ou affinité de session), généralement en posant un cookie qui désigne l’instance. La persistance est un moyen rapide de placer une application avec état derrière un équilibreur, et elle a un usage légitime comme indication de performance, par exemple pour garder un utilisateur sur une instance dont le cache local contient déjà ses données.
Comme garantie de bon fonctionnement, en revanche, elle pose des problèmes. La charge devient inégale, car quelques gros utilisateurs ou un grand bureau derrière une seule adresse IP restent attachés à une instance quelle que soit sa charge. Quand une instance tombe en panne, ses utilisateurs perdent leur session : les paniers disparaissent et les gens sont déconnectés. Les retraits d’instances et chaque déploiement ont le même effet, sauf si les sessions sont drainées, ce qui peut prendre autant de temps que la plus longue session. Les nouvelles instances ajoutées par l’autoscaling ne reçoivent que les nouveaux utilisateurs : elles soulagent donc lentement les instances surchargées. Et le système n’est plus sans état au sens de la section 9.1, si bien que chacun de ces problèmes réapparaît lors d’un basculement entre zones ou entre régions.
Externaliser l’état de session
Le remède consiste à sortir l’état de session de l’instance, pour que n’importe quelle instance puisse servir n’importe quelle requête. Il existe trois options principales, souvent combinées. Un jeton côté client transporte l’état avec chaque requête : un cookie signé (et si nécessaire chiffré) ou un jeton comme un JWT contenant l’identité de l’utilisateur et quelques données. Les serveurs n’ont besoin d’aucun stockage, mais les jetons doivent rester petits, ne peuvent pas facilement être révoqués avant leur expiration et doivent être protégés contre la falsification et le vol (section 8.3). Un stockage de sessions partagé, typiquement Redis ou Memcached, conserve les sessions dans un stockage externe rapide, indexé par un identifiant de session placé dans le cookie ; il gère un état plus volumineux et la déconnexion immédiate, au prix d’un appel réseau par requête et d’une dépendance qui doit elle-même être hautement disponible. Une base de données convient à l’état qui doit survivre à tout, comme un panier que l’utilisateur s’attend à retrouver le lendemain, qui relève en réalité des données métier plutôt que des données de session.
Un découpage pratique pour la plateforme de livraison de repas : l’identité dans un jeton signé de courte durée, le panier dans la base de données du service de commandes (les utilisateurs passent du téléphone à l’ordinateur portable), et l’état transitoire de l’interface dans le client. Aucune instance ne conserve quoi que ce soit dont une requête ultérieure a besoin : l’équilibreur de charge peut donc utiliser un équilibrage « moins de requêtes » sans affinité, et les instances peuvent être ajoutées, retirées et remplacées à volonté.
Équilibrage de charge global : DNS et anycast
Dès qu’un système tourne dans plusieurs régions, quelque chose doit envoyer chaque utilisateur vers une région adaptée : la région en bonne santé la plus proche, ou celle qui détient ses données. L’équilibrage de charge global se fait généralement de l’une de deux façons, souvent combinées.
L’équilibrage de charge global des serveurs fondé sur le DNS utilise un serveur de noms faisant autorité qui répond au même nom par des adresses différentes selon l’origine de la requête, la santé de chaque région et des poids configurés : un utilisateur à Lisbonne reçoit l’adresse de la région UE, un utilisateur dans l’Ohio celle de la région US. Il ne nécessite aucun réseau particulier, mais il est grossier et lent à réagir. Les réponses sont mises en cache par les résolveurs au moins pendant la durée de vie de l’enregistrement (souvent 30 à 300 secondes), et parfois plus longtemps : après la défaillance d’une région, certains utilisateurs continuent donc d’y arriver pendant plusieurs minutes. Le serveur de noms voit l’emplacement du résolveur plutôt que celui de l’utilisateur, même si l’extension EDNS Client Subnet (RFC 7871) permet aux résolveurs de transmettre une partie de l’adresse du client.
Avec l’anycast, la même adresse IP est annoncée depuis de nombreux sites par le protocole de routage d’internet (BGP), et les paquets de chaque utilisateur sont livrés au site le plus proche au sens du routage. Le basculement consiste à retirer l’annonce d’un site défaillant, ce qui prend effet rapidement sans attendre les caches DNS. L’anycast est utilisé depuis longtemps pour les serveurs racines du DNS et les services de résolution ; les CDN et les équilibreurs de charge globaux des grands fournisseurs de cloud l’utilisent pour amener les utilisateurs vers un site périphérique proche, où des proxys de couche 7 terminent la connexion et transmettent les requêtes, via le réseau du fournisseur, à une région en bonne santé. La conception multirégion, le placement des données et le basculement sont le sujet de la section 10.6.
Équilibrage de charge global par DNS et par anycast
Vérifiez votre compréhension
Pourquoi un serveur gRPC nouvellement ajouté derrière un équilibreur de charge de couche 4 pourrait-il ne recevoir aucun trafic ?
L’équilibrage de couche 4 choisit un backend par connexion, et les clients gRPC gardent une seule connexion HTTP/2 de longue durée : les clients existants ne lui envoient donc aucune requête tant qu’ils ne se reconnectent pas.
Quelle est la différence d’effet entre l’échec d’une sonde de vivacité et l’échec d’une sonde de disponibilité ?
L’échec de la sonde de vivacité amène l’orchestrateur à redémarrer l’instance ; l’échec de la sonde de disponibilité la retire seulement de l’équilibreur de charge jusqu’à ce qu’elle la réussisse de nouveau.
Pourquoi de nombreux équilibreurs de charge indépendants qui choisissent l’instance la moins chargée obtiennent-ils de mauvais résultats ?
Ils agissent sur la même vue périmée et choisissent tous la même instance, qu’ils surchargent tour à tour ; choisir la meilleure de deux instances tirées au hasard évite cet effet de troupeau.
Citez deux problèmes causés par la conservation des sessions dans la mémoire des instances avec des sessions persistantes.
Les utilisateurs perdent leur session quand l’instance tombe en panne ou est remplacée, et la charge devient inégale parce que les gros utilisateurs restent attachés à une seule instance.
Sources et lectures complémentaires
Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.
Demander : « Nous faisons tourner huit instances derrière un équilibreur de charge avec des sessions persistantes, et nous perdons les paniers des utilisateurs à chaque déploiement. Comment passer à des instances sans état en toute sécurité ? »