Introduction : Le défi de la résilience dans l'informatique sans serveur

Les plateformes comme AWS Lambda, Azure Functions et Google Cloud Functions permettent aux équipes de se concentrer sur la logique d'affaires tout en ne payant que pour une utilisation réelle. Cependant, ce modèle introduit des défis de résilience uniques. Les fonctions sont apatrides, éphémères et communiquent souvent entre les services distribués. Une dépendance unique en cas de défaillance peut déclencher une cascade de temps ou de temps, de récupération et de coûts. Les démarrages froids, les étranglements et les défaillances transitoires du réseau sont fréquents.

Un disjoncteur agit comme une soupape de sécurité pour votre application. Il surveille les appels vers des services ou des ressources distants et empêche toute autre tentative lorsque les taux de défaillance dépassent un seuil. Cela protège le système d'être submergé, permet aux services défaillants de récupérer le temps et fournit un retour propre aux utilisateurs. Dans cet article, nous développons le contenu original pour vous donner un guide complet et réalisable pour la mise en œuvre des disjoncteurs dans les applications sans serveur, y compris des explications détaillées, des considérations spécifiques à la plateforme, des exemples de code et des meilleures pratiques.

Comprendre le motif du disjoncteur en profondeur

Le modèle de disjoncteur a été popularisé par Michael Nygard dans son livre Sortie-le! et plus tard officialisé dans les modèles de nuages-natifs. Il se comporte comme un disjoncteur électrique: lorsqu'un circuit détecte une faille (par exemple, un court), il ouvre et arrête le flux de courant. Dans le logiciel, les états de circuit sont:

  • Fermé: Demande de débit normal vers le service en aval. Le disjoncteur surveille les taux de défaillance (par exemple, erreurs HTTP 5xx, temps d'arrêt). Si les défaillances dépassent un seuil configuré dans une fenêtre de temps donnée (par exemple, 10 pannes en 30 secondes), le circuit se déplace vers Ouvrir.
  • Open: Les demandes sont immédiatement rejetées (ou une logique de repli est invoquée) sans appeler le service défaillant. Cela empêche les ressources gaspillées et donne au service en aval une fenêtre de récupération. Après une période de temps (p. ex. 30 secondes), le circuit passe à Half-Open.
  • Half-Ouvrir:[ Un nombre limité de demandes d'essai sont autorisées. Si elles réussissent (dans les critères de succès définis), le circuit se réinitialise à Fermé. Si les défaillances persistent, il retourne à Ouvrir et le délai d'attente est souvent réinitialisé ou incrémenté.

Sans cela, une brève panne pourrait provoquer une réessayer simultanée de tous les clients, créant ainsi un troupeau tonifiant qui étend la panne. Le modèle de disjoncteur fournit également des retours de rétroaction précoces aux clients, permettant une dégradation gracieuse – par exemple, le retour de données en cache ou un message d'erreur amical au lieu d'un temps mort.

Martin Fowler , un article séminal sur Circuit Breaker reste la référence fondamentale. Il explique comment le modèle s'intègre à d'autres modèles de résilience comme Retry et Bulkhead.

Paramètres clés pour l'accord

Chaque implémentation de disjoncteur expose les paramètres configurables qui doivent être ajustés à votre comportement d'applications:

  • Seuil d'échec:[ Nombre de défaillances consécutives (ou de débit sur une fenêtre) pour ouvrir le circuit.
  • Durée du temps:[ Combien de temps le circuit reste ouvert avant de passer à la demi-ouverture.
  • Compte d'essai à moitié ouvert: Nombre de demandes retenues pour fermer le circuit.
  • Classification des erreurs:[ Quelles réponses comptent comme des échecs? Seulement 5xx? Timeouts? 4xx? (En fait seulement des erreurs côté serveur).
  • Temps de récupération:[ Optionnellement incrémentiel (défaut exponentiel) pour éviter les basculements.

Les applications sans serveur ajoutent de la complexité : car les fonctions sont éphémères, vous ne pouvez pas compter sur l'état in-memory pour le circuit. Si une instance Lambda échoue, l'état du circuit peut être perdu. Ainsi, le stockage externe de l'état (DynamoDB, Redis, ou un service géré) est souvent nécessaire.

Mise en œuvre des disjoncteurs dans les environnements sans serveur

La mise en œuvre d'un disjoncteur dans une architecture sans serveur nécessite l'adaptation du modèle aux contraintes de la plateforme. Nous allons couvrir trois approches principales : utiliser des fonctionnalités d'API gérées, tirer parti de bibliothèques tierces dans votre code de fonction, et utiliser des services d'orchestration comme les fonctions AWS Step.

Approche 1: Glissement de niveau de passerelle et rupture de circuit de l'API

AWS API Gateway peut agir comme un disjoncteur rudimentaire en glissant des requêtes à une fonction Lambda backend. Lorsque la fonction renvoie trop d'erreurs 5xx ou dépasse les limites de concurrence, API Gateway peut être configuré pour renvoyer une réponse de repli (p. ex., un message statique d'un autorisant personnalisé ou une réponse d'intégration).

Exemple : Définir un plan d'utilisation de API Gateway avec une limite de débit et de débit qui reflètent la capacité de votre moteur. Lorsque la fonction Lambda est dépassée, API Gateway répond immédiatement avec , agissant comme un disjoncteur à sens unique.

Approche 2: Disjoncteurs en fonction avec bibliothèques

L'approche la plus flexible consiste à intégrer une bibliothèque de disjoncteurs dans vos fonctions Lambda. Comme les fonctions Lambda sont apatrides et à échelle horizontale, l'état du disjoncteur doit être stocké de manière externe afin que chaque invocation puisse vérifier l'état courant. Un modèle commun utilise Amazon DynamoDB (ou Redis avec ElastiCache) pour persister l'état du circuit à travers les invocations de fonctions.

Pour Node.js, la bibliothèque Opossum est un disjoncteur largement utilisé. Elle prend en charge les fonctions de repli, le délai d'exécution et le seuil de volume.

const CircuitBreaker = require('opossum');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

const circuitBreakerState = {
 state: 'CLOSED',
 failureCount: 0,
 lastFailureTime: null
};

// Persist state in DynamoDB after each transition
async function persistState(newState) {
 await dynamo.put({
 TableName: 'CircuitBreakerState',
 Item: { serviceId: 'payment-service', ...newState }
 }).promise();
}

async function loadState() {
 const data = await dynamo.get({
 TableName: 'CircuitBreakerState',
 Key: { serviceId: 'payment-service' }
 }).promise();
 return data.Item || circuitBreakerState;
}

// The actual downstream call
async function callPaymentService(payload) {
 const http = require('axios');
 const response = await http.post('https://payment.example.com/charge', payload);
 return response.data;
}

// Circuit breaker options
const options = {
 errorThresholdPercentage: 50,
 resetTimeout: 30000,
 volumeThreshold: 10
};

// Create breaker with external state integration (simplified)
const breaker = new CircuitBreaker(callPaymentService, options);

breaker.fallback(() => ({ error: 'Payment service unavailable, order processed in offline mode' }));

exports.handler = async (event) => {
 // Load state from DynamoDB and update breaker
 const savedState = await loadState();
 // Opossum doesn't natively restore state; you'd need to implement a wrapper.
 // For brevity, assume the breaker is fresh per function invocation but uses external checks.

 // In production, use a shared cache with TTL instead of per-invocation state load.
 return breaker.fire(event.body);
};

Cet exemple omet une intégration complète pour plus de clarté. Dans la pratique, vous devrez synchroniser l'état du disjoncteur sur de nombreuses invocations de fonctions simultanées en utilisant des écritures conditionnelles dans DynamoDB (verrouillage optimal) pour éviter les conditions de course.

Approche 3 : Fonctions de l'étape AWS – Disjoncteur de niveau d'orchestration

Pour les workflows multi-étapes (par exemple, la commande e-commerce), les fonctions AWS Step peuvent modéliser un disjoncteur comme une machine d'état. L'état peut vérifier un compteur ou un drapeau stocké dans une table DynamoDB. Si le nombre de défaillances dépasse un seuil, le workflow se redirige vers un chemin de repli (par exemple, e-mail un administrateur, file d'attente pour le traitement manuel).

Exemple : Une fonction Step qui appelle deux services en aval. Après une défaillance, elle incrémente un compteur DynamoDB. Avant chaque invocation ultérieure, la fonction Step lit le compteur. Si elle dépasse 5, le flux de travail prend immédiatement le chemin de recul. Ceci est effectivement un disjoncteur au niveau du flux de travail.

Défis et solutions spécifiques sans serveur

  • Cold starts:[ L'état du disjoncteur doit survivre au recyclage des instances de fonction. Utilisez l'état externe avec un court TTL pour fermer automatiquement le circuit après une période de non-activité.
  • Concurrence:[ De nombreuses instances de fonction peuvent vérifier et mettre à jour simultanément l'état. Utilisez un verrouillage optimiste (expressions de condition DynamoDB) ou des modèles d'accroissement/décrément atomiques.
  • Coût:[ Chaque vérification d'état ajoute des coûts de lecture/écriture. État de cache en mémoire avec une courte échéance (p. ex., 1 seconde) dans la même instance de fonction pour réduire la lecture de DynamoDB, mais accepter éventuellement la cohérence.
  • Grâcité de temps:[ Les fonctions de Lambda ont un temps d'invocation maximal (15 minutes). Les temps de disjoncteur doivent être beaucoup plus courts (secondes) pour éviter de maintenir la fonction ouverte.

Avantages de l'utilisation des disjoncteurs

Les avantages vont bien au-delà des bases. Explorons chaque avantage dans un contexte sans serveur:

Amélioration de la résilience – Prévenir les défaillances de l'effondrement

Si le service A appelle B et B appelle C, et C échoue, la défaillance se propage. Un disjoncteur sur B , appel à C fera que B ouvre son circuit après quelques pannes. Maintenant, les demandes de A à B sont immédiatement rejetées avec un recul, empêchant B d'épuiser sa limite de concurrence et de devenir un goulot d'étranglement.

Récupération plus rapide – auto-guérison sans intervention manuelle

Lorsqu'un circuit est ouvert, le service défaillant obtient une période de repos. Aucune requête n'est envoyée, ce qui lui permet de récupérer (par exemple redémarrer, effacer une fuite de mémoire, ou reconfigurer). L'état semi-ouvert sonde périodiquement le service. Une fois qu'il répond avec succès, le circuit se ferme automatiquement.

Expérience utilisateur améliorée – Dégradation gracieuse

Au lieu de montrer une page générique -Erreur de serveur ou un chargeur tournant, vous pouvez retourner des données statiques, une version simplifiée de la fonctionnalité, ou un message amical. Par exemple, un service de recommandation de produit peut utiliser un disjoncteur : lorsque ouvert, la page de produit affiche -Recommandations temporairement indisponibles - au lieu de échouer entièrement.

Économies – Éviter les invocations inutiles

Le prix sans serveur est basé sur les demandes et la durée. Lorsqu'un service en aval échoue, continuer à l'appeler gaspille de l'argent. Chaque invocation de votre fonction qui échoue immédiatement (ou qui entraîne un délai d'attente pour l'aval) coûte toujours.

Meilleures pratiques pour déployer des disjoncteurs

Mettre en place un disjoncteur n'est pas une activité unique. Utilisez ces pratiques pour maximiser l'efficacité dans un environnement sans serveur.

Définir les seuils d'échec et les délais appropriés

Par exemple, si votre service en aval vise un temps de mise à jour de 99,9 %, un seuil de 5 défaillances par minute peut être trop sensible (il pourrait s'ouvrir pendant des blips mineurs). Commencez par un seuil plus élevé (par exemple, un taux d'erreur de 20 % sur une fenêtre d'une minute) et ajustez-vous en utilisant des données de surveillance.

Mettre en oeuvre des mécanismes de repli

Chaque demande en circuit ouvert devrait avoir un retour en arrière.

  • Récupérer les données mises en cache (à partir d'ElastiCache, CloudFront ou d'une base de données).
  • Demander la demande de traitement ultérieur (p. ex., SQS DLQ).
  • Retourne une valeur par défaut ou une réponse statique.
  • Rediriger vers une version dégradée de la fonctionnalité (par exemple, désactiver la personnalisation).

Les chutes doivent être idémpotentes lorsque c'est possible, surtout pour les écrits.

États de l ' observatoire et du circuit de l ' enregistrement

Utilisez les mesures CloudWatch (par exemple, mesures personnalisées pour le compte ouvert des circuits, essais à demi ouverts, utilisation de la fonction de recul).Déterminez les alarmes : si un circuit reste ouvert pendant une période prolongée, avisez les opérations.Enregistrez également la raison de la défaillance – timeout, code d'erreur, etc. – pour aider au débogage.

Combiner avec d'autres modèles de résilience

  • Reessayer:Utilisez un motif de réessayer dans ledisjoncteur, mais avec un recul exponentiel et des jitters. Le disjoncteur lui-même ne devrait pas réessayer; au lieu de cela, l'appel du client est enveloppé avec une politique de réessayer (p. ex., les rétries intégrées de SDK.) AWS. Le disjoncteur s'ouvre après que tous les rétries ont échoué.
  • Bulkhead: Isolez les ressources par fonction ou service. Par exemple, réservez une limite de concordance pour les fonctions critiques par rapport aux fonctions non critiques. Lorsqu'un circuit s'ouvre, il réduit la charge sur le service défaillant, l'empêchant d'affecter d'autres parties du système.
  • Timeout: Toujours régler un timeout sur les appels en aval – plus court que la fenêtre de défaillance du disjoncteur. Cela empêche les longues demandes de suspension de fausser le nombre de défaillances.
  • Utilisez un processus de fond (p. ex., un événement programmé par CloudWatch) pour invoquer périodiquement un paramètre de contrôle de santé. Si le contrôle de santé échoue, ouvrez le circuit de façon préventive avant que les utilisateurs soient touchés.

Testez votre disjoncteur en panne

Utilisez des outils comme AWS Fault Injection Simulator (FIS) pour injecter des défaillances dans vos services en aval et observer le comportement du disjoncteur. Vérifiez que:

  • Le circuit s'ouvre dans les délais prévus.
  • Les replis s'exécutent correctement.
  • Le circuit se rétablit (à moitié ouvert puis fermé) après la résolution de la défaillance.
  • Aucun faux positif ne se produit sous des pics de charge normaux.

Tester dans un environnement de mise en scène qui reflète la production est essentiel. Documenter le comportement attendu et exécuter les forets régulièrement.

Utiliser un magasin d'État extérieur avec TTL

En sans serveur, vous ne pouvez pas compter sur la mémoire locale à travers les invocations. Utilisez DynamoDB, ElastiCache pour Redis, ou un service fait comme Eureka. Réglez un Time-to-Live (TTL) sur l'enregistrement d'état de sorte que si votre fonction est inactive pendant une longue période, le circuit se réinitialise automatiquement pour se fermer.

Conclusion

Comme sans serveur devient l'épine dorsale des applications modernes, les modèles de résilience comme le disjoncteur ne sont plus facultatifs – ils sont essentiels pour le contrôle des coûts, le temps de disponibilité et la satisfaction des utilisateurs. En comprenant la machine d'état, en la mettant en œuvre correctement dans les contraintes de votre plate-forme sans serveur (Pateway API, code de fonction, ou Step Functions), et en suivant les meilleures pratiques pour le suivi et les tests, vous pouvez construire des systèmes qui gracieusement dégradent sous défaillance et récupérer sans intervention manuelle.

Le motif de disjoncteur n'est qu'une pièce du puzzle de résilience. Combinez-le avec des réticules, des cloisons, des contrôles de santé et une observatoire complète pour créer des architectures sans serveur vraiment robustes.