
Robustesse logicielle et paradigmes de timeout/retry avec backoff
- 2 septembre 2024
- 23 mins de lecture
- Qualité logicielle
Table des matières
Introduction
Les programmes informatiques doivent souvent accéder à des ressources externes, comme des périphériques E/S (Input/Output) (interfaces réseau, disques de stockage) gérés par le système d’exploitation, et des services distants sur Internet.
Bien que ces ressources permettent d’interagir avec le monde extérieur, elles sont souvent peu fiables en raison de problèmes tels que la congestion réseau, les pannes de service, les redémarrages de périphériques, ainsi que toute faute de ressource.
Note
Puisque les ressources peu fiables sont inévitables, les programmes doivent être conçus pour gérer cette instabilité. Plus précisément, ils doivent traiter les défis suivants :
- L’accès à la ressource peut prendre trop de temps ou ne jamais se terminer.
- L’accès à la ressource peut renvoyer une erreur.
- Une ressource peut être surchargée lorsqu’elle est sollicitée par de nombreux programmes simultanément, entraînant des erreurs ou une absence de réponse.
Pour rendre les programmes plus robustes face à ces problèmes, les stratégies suivantes sont couramment utilisées :
- Timeouts : définir un timeout pour l’accès à la ressource afin d’éviter d’attendre indéfiniment. Si le timeout est écoulé, on considère qu’une erreur s’est produite.
Illustration du timeout - Politiques de retry : si un accès à la ressource échoue, réessayer selon une politique prédéfinie qui indique quand et comment retenter.
- Backoff et jitter : pour éviter de surcharger les ressources ou le CPU pendant les retries, utiliser une stratégie de backoff qui introduit un délai avant chaque tentative. Ajouter du jitter (variation aléatoire) au backoff permet d’éviter que plusieurs programmes ne surchargent la ressource en reprenant en même temps.
Illustration du retry avec backoff
Le tableau ci-dessous résume les problèmes courants de fiabilité des ressources et les stratégies associées.
| Problème de fiabilité de la ressource | Mesure de robustesse du programme | Description |
|---|---|---|
| L’accès ne se termine jamais | Timeout d’accès | Définir un timeout égal au délai attendu pour obtenir un résultat. Si aucun délai n’est spécifié, le timeout est infini. |
| Erreur d’accès | Politique de retry | Réessayer l’accès jusqu’à réussite pour les programmes autonomes, ou arrêter après un nombre défini de tentatives pour les programmes interactifs. Réessayer sélectivement en fonction du type d’erreur. |
| Surcharge de la ressource | Retry avec backoff | Réessayer l’accès après un délai (backoff) pour éviter de surcharger la ressource, si la politique de ressource le permet. |
| Surcharge due aux accès concurrents | Jitter dans le backoff | Ajouter une perturbation aléatoire au backoff pour éviter des retries simultanés. Cela aide à prévenir l’aggravation des erreurs d’accès causée par la surcharge des ressources. |
Bulletin d'information
Abonnez-vous à notre bulletin d'information et restez informé(e).
Comment appliquer les mesures de robustesse ?
Les mesures de robustesse sont configurées à l’aide de paramètres spécifiques.
Chaque ressource accédée par un programme dispose de sa propre mesure de robustesse, ce qui signifie que différentes ressources sollicitées par le même programme peuvent avoir des configurations différentes. De même, on peut appliquer des réglages différents à plusieurs accès d’une même ressource.
La mesure de robustesse globale pour un programme accédant à une ressource suit le flux de travail ci-dessous :
D’abord, la ressource est sollicitée avec un timeout, qui peut être fini ou infini.
Si l’accès réussit, le programme continue comme prévu.
Si l’accès expire ou génère une erreur, la politique de retry est vérifiée.
Si aucun retry n’est autorisé, le programme considère l’accès comme un échec et gère l’erreur (par exemple en l’affichant à l’utilisateur, en interrompant le traitement ou en la journalisant et en l’ignorant).
Si un retry est autorisé, le temps de backoff est calculé, et le programme attend ce délai avant de retenter l’accès à la ressource.
Stratégies d’accès fiable aux ressources
Utilisation du timeout
Pour empêcher un programme d’attendre indéfiniment une ressource qui risque de ne pas répondre, on définit un délai maximal d’attente, appelé timeout. Si la ressource ne répond pas pendant la période du timeout, elle est traitée comme une erreur.
Il est recommandé d’utiliser systématiquement un timeout lors de l’accès à des ressources susceptibles de répondre lentement.
Comment choisir la valeur du timeout ?
Quand un programme tente d’accéder à une ressource, il doit attendre la réponse avant de poursuivre les tâches qui en dépendent.
Pendant cette attente, le programme est essentiellement « en pause » pour cette fonction. Mais si le timeout est trop court, le programme peut abandonner trop vite et provoquer des échecs inutiles.
Ainsi, en définissant un timeout, il faut trouver le bon équilibre entre deux éléments : le temps de réponse habituel de la ressource et la réactivité attendue du programme.
Ces deux facteurs vont souvent dans des directions opposées. Un timeout plus long réduit le risque de fausses alertes, mais ralentit la réaction du programme lorsque quelque chose tourne mal. Inversement, un timeout plus court rend le programme plus réactif, mais peut provoquer des abandons prématurés.
Si vous utilisez des bibliothèques tierces pour accéder aux ressources, surveillez leurs paramètres de timeout par défaut. Certaines bibliothèques peuvent définir des timeouts très longs ou même infinis par défaut.
Comme le rappelle judicieusement un article du blog Bearer, “Libraries can’t be trusted”.
C’est pourquoi il est important de fixer le timeout en fonction d’une analyse précise du comportement réel de votre système.
Pour les connexions et les requêtes de service, un article du blog d’ingénierie Zalando du 26 juillet 2023 propose des recommandations solides sur le réglage des timeouts.
You can setup a connection timeout which is some multiple of your expected RTT.
Connection timeout = RTT * 3is commonly used as a conservative approach, but you can adjust it based on your specific needs.
In general, the connection timeout for a microservice should be set low enough so that it can quickly detect an unreachable service, but high enough to allow the service to start up or recover from a short-lived problem
Ils recommandent de fixer le timeout de connexion en fonction du round-trip time (RTT) et de le maintenir aussi court que possible. Vous pouvez déterminer le RTT avec l’utilitaire réseau ping.
Set request timeout based on collected metrics and SLA.
Ils suggèrent de définir le timeout de requête en fonction de l’accord de niveau de service (SLA) du programme, du SLA de la ressource, et des mesures de latence collectées.
Dans le contexte des services, Kevin Ahlquist explique comment ils ont déterminé les timeouts chez Bluestem dans un article du 21 juillet 2016.
1. Load test to find the service’s behavior under maximum steady-state load (i.e. how much load can this service sustain indefinitely before it tips?).
2. Gather empirical data on throughput, latency, and error rate from those load tests.
3. Set baseline timeouts (and alerts) based on this data.
4. Refine timeouts based on continued data-gathering to bring error rates to acceptable levels.
La leçon principale est de fixer les timeouts à partir de données d’essais solides. Pour plus de détails sur la manière dont ils ont utilisé ces données pour déterminer les timeouts, consultez l’article complet.
Doit-on réessayer ?
Quand un accès à une ressource génère une erreur — qu’elle provienne d’un timeout, d’une indisponibilité ou d’un problème interne — une façon de traiter le cas est de réessayer. Mais avant de retenter, il est important de décider si le retry vaut le coût. Cette décision est guidée par une politique de retry.
Si la politique prévoit un retry, une période de backoff est calculée et utilisée. La valeur du backoff est déterminée selon les stratégies de backoff et les stratégies de jitter.
Deux éléments clés contrôlent la gestion des retries : les politiques de retry et le délai de backoff.
Politiques de retry
Les politiques de retry décident s’il faut retenter un accès après une erreur. En termes simples, elles définissent jusqu’à quel point les tentatives peuvent se poursuivre. Une politique de retry précise combien de fois (max-retries) ou pendant combien de temps (max-delay) un accès à une ressource doit être relancé.
Une politique de retry peut définir différents max-retries ou max-delay selon le type d’erreur observée. Par exemple, la politique peut autoriser 10 retries pour un timeout, mais aucun retry pour d’autres erreurs.
max-retriesest une valeur entière telle que :0signifie aucun retry.- Une valeur négative signifie des retries infinis.
- Une valeur positive représente le nombre de tentatives avant d’abandonner.
max-delayest un nombre réel configuré de manière similaire àmax-retries.
Comment décider des valeurs max-retries/max-delay ?
Avant de choisir des retries, prenez en compte les éléments suivants :
- Les erreurs doivent être transitoires : ne réessayez pas si l’erreur est permanente, car le retry ne la résoudra pas.
- Les accès doivent être idempotents : le retry ne doit pas poser de problème si la même opération est répétée. La ressource doit se comporter comme si les tentatives échouées n’avaient jamais eu lieu.
- Tenez compte des accès sensibles au temps : il est inutile de réessayer si le résultat sera périmé au moment où il arrive. Par exemple, si des données en temps réel sont nécessaires sous 2 secondes, retenter au-delà de ce délai n’a pas de sens.
Cela est particulièrement vrai pour les programmes interactifs où une réponse rapide est essentielle à une bonne expérience utilisateur, comme le définit l’accord de niveau de service (SLA).
Pour les accès sensibles au temps, vous pouvez fixer le seuil de retry avecmax-delay, qui représente le temps total écoulé depuis la première tentative.
Suivez ces directives générales pour fixer le seuil de retry :
- Si l’erreur est permanente ou si l’accès à la ressource n’est pas idempotent, ne réessayez pas.
- Si le programme peut attendre indéfiniment (comme certains microservices), réessayez infiniment.
- Sinon, fixez
max-retries/max-delayà une valeur positive basée sur le SLA (utile pour les programmes plus interactifs).
Note
Il existe des politiques de retry plus avancées, telles que :
- Les retries adaptatifs (a.k.a. retry token bucket), où les succès augmentent les chances de retry et les échecs les diminuent.
- Circuit breaker, où les retries ne sont autorisés que si le taux d’échec reste en dessous d’un seuil.
L’article de blog de 2022 de Marc Brooker évalue diverses politiques de retry.
Choisir la bonne politique est crucial, notamment pour l’accès aux services.
That doesn’t mean that backing off between retries is a bad idea. It’s a good idea, but only helps for short term overload (spikes, etc). It does not reduce the total work in the system, or total amplification factor of retries.
Stratégies de backoff
Les stratégies de backoff sont des algorithmes utilisés pour calculer le temps d’attente avant de retenter une opération échouée. Explorons quelques stratégies de backoff courantes.
Tout d’abord, voici quelques termes clés à connaître :
- Valeur de base de backoff : c’est le temps d’attente minimal possible. C’est le point de départ du calcul du backoff. Sa valeur est non négative.
- Valeur maximale de backoff : c’est la limite supérieure du temps de backoff. Le backoff ne peut pas dépasser cette valeur.
- Nombre de retries : c’est le nombre de retries consécutifs effectués jusqu’à présent. Il commence à 0 et augmente à chaque retry, puis revient à 0 après un accès réussi à la ressource.
- Temps de backoff : c’est le temps de backoff après retries. Il est calculé selon la stratégie utilisée.
On note aussi le minimum de deux nombres et par .
Note
Dans les stratégies de backoff, les valeurs de , et sont les entrées utilisées par les algorithmes. Le résultat de ces algorithmes est , qui est le temps de backoff calculé.
Essentiellement, est la durée d’attente avant de retenter après retries, telle que déterminée par la stratégie choisie.
Maintenant, explorons les différentes stratégies de backoff.
Backoff exponentiel
Le backoff exponentiel est l’une des stratégies les plus courantes dans les systèmes distribués. Ici, le temps de backoff augmente de manière exponentielle à chaque retry.
Étant donné un multiplicateur (où ) :
Soyez prudent avec le backoff exponentiel, car il peut rapidement ralentir votre programme en raison de l’augmentation rapide des délais d’attente.
La valeur la plus courante pour est 2.
Note rapide : certaines implémentations simplifient cela en prenant toujours le multiplicateur égal à la base, ce qui donne la formule : .
Backoff linéaire ou par intervalle
Avec le backoff linéaire, le temps d’attente augmente d’un intervalle fixe après chaque retry.
Étant donné un intervalle (où ) :
Note rapide : certaines implémentations fixent toujours l’intervalle sur la valeur de base, ce qui donne la formule : .
Backoff de Fibonacci
Cette stratégie utilise la suite de Fibonacci pour calculer le temps de backoff, ajoutant davantage de variabilité qu’un backoff linéaire.
Étant donné le nombre de Fibonacci tel que est le premier nombre de Fibonacci :
Backoff polynomial
Le backoff polynomial utilise une fonction polynomiale pour calculer le temps de backoff, ce qui offre une augmentation plus personnalisable des délais d’attente.
Étant donné exposants polynomiaux (où chaque ) :
Backoff aléatoire
Le backoff aléatoire ajoute un élément d’imprévisibilité en choisissant une valeur aléatoire entre et pour chaque retry.
Backoff constant
Ici, le temps de backoff reste constant, toujours égal à la valeur de backoff initiale.
Note rapide : le backoff constant peut être implémenté avec un backoff exponentiel où , un backoff linéaire où , ou en fixant dans toute autre stratégie.
Note rapide : certaines implémentations incluent une stratégie Sans Backoff, qui est simplement un cas particulier de backoff constant où .
Utilisation du jitter
Quand plusieurs programmes tentent d’accéder à la même ressource en même temps, cela peut provoquer des erreurs dues aux accès concurrents.
Si ces programmes utilisent le même timeout et les mêmes politiques de retry, ils risquent de retenter au même moment, ce qui peut générer de nouvelles erreurs d’accès.
C’est particulièrement fréquent avec des serveurs sollicités par de nombreux clients identiques, comme des interfaces web.
Quand beaucoup de clients envoient des requêtes en même temps, le serveur peut se trouver surchargé et renvoyer des erreurs. Comme ces clients suivent la même stratégie de backoff, ils retenteront également en même temps, ce qui aggrave le problème.
Pour l’éviter, une solution courante consiste à ajouter une composante aléatoire au temps de backoff — appelé jitter.
Le jitter introduit des variations aléatoires sur le temps de backoff , calculé par les stratégies de backoff, et/ou sur le nombre de retries .
Plusieurs algorithmes permettent d’ajouter du jitter au backoff, produisant un .
Ci-dessous se trouvent des algorithmes de jitter couramment utilisés. Chacun prend le backoff calculé par les stratégies de backoff et retourne un . Ils peuvent aussi modifier la valeur de .
Les # retries attempted (x) marqués en rouge sont les points où le jitter a eu un impact, c’est-à-dire où de l’aléatoire a été ajouté au calcul final du backoff.
Full Jitter
Cet algorithme choisit aléatoirement soit le temps de backoff calculé, soit aucun backoff du tout. Le est calculé comme suit :
Où est une fonction qui retourne aléatoirement l’un de ses arguments.
Equal Jitter
Cet algorithme applique un jitter complet à la moitié du backoff calculé :
Jitter décorrelé
Cet algorithme réinitialise le nombre de retries (utilisé comme paramètre dans les stratégies de backoff) avec une probabilité de .
L’idée est de faire « oublier » les retries précédents à l’algorithme de backoff, puis d’utiliser le nouveau temps de backoff calculé.
Pas de jitter
Aucun jitter n’est appliqué. Le est simplement le backoff calculé :
Mise à l’échelle du backoff avec jitter
Pour mettre à l’échelle le backoff avec jitter par un facteur, soit un nombre réel strictement positif. Le backoff final devient :
Ce facteur est utile pour augmenter ou diminuer les valeurs de backoff calculées sans modifier les autres paramètres du calcul.
Implémentation
Les stratégies robustes d’accès aux ressources sont généralement implémentées sous forme de bibliothèques qui enveloppent les accès aux ressources pour gérer les timeouts et les retries. Dans cette section, nous verrons comment concevoir de telles bibliothèques.
Conception principale
Au cœur de la conception, chaque accès à une ressource doit être implémenté dans une fonction. Le programme utilise ensuite la bibliothèque de robustesse pour appeler cette fonction.
La bibliothèque fournit une fonction de haut niveau, que l’on appellera access_wrapper, qui entoure l’accès à la ressource.
La fonction access_wrapper prend la fonction d’accès à la ressource comme paramètre, appelée ici resource_access.
Vous pouvez retrouver un exemple d’implémentation dans la section Exemple d’implémentation.
Cœur de la robustesse
La bibliothèque doit également permettre de configurer les mécanismes de timeout et de retry.
Timeout
Le timeout doit être configurable, où :
- une valeur négative indique un timeout infini.
- une valeur non négative fixe la durée réelle du timeout.
Retry
Le mécanisme de retry implique plusieurs configurations clés :
- Configuration de la politique de retry : définit les politiques pour décider quand réessayer.
- Configuration de la stratégie de backoff : spécifie la stratégie de backoff à appliquer.
- Configuration de la stratégie de jitter : définit la stratégie de jitter pour ajouter de l’aléatoire au backoff.
- Facteur d’échelle de backoff : détermine le facteur d’échelle pour ajuster le backoff.
Configuration des politiques de retry
Chaque politique de retry inclut des paramètres de configuration pour initialiser son état. Les variables d’état importantes peuvent inclure max-retry, max-delay, le nombre actuel de retries et l’horodatage de la première tentative.
La politique définit une fonction retry_allowed pour décider s’il faut retenter. Cette fonction renvoie un booléen et met à jour l’état de la politique de retry en fonction des paramètres comme l’état courant et l’erreur observée.
Elle définit aussi une fonction new_access, qui réinitialise le compteur de retries et l’horodatage de la première tentative.
Différentes politiques de retry devraient être configurables pour des types d’erreurs spécifiques.
Configuration de la stratégie de backoff
Chaque stratégie de backoff dispose de paramètres de configuration pour calculer le backoff. Ceux-ci incluent généralement le backoff de base, le backoff maximal, et des paramètres spécifiques à la stratégie comme le multiplicateur pour le backoff exponentiel.
Une fonction calculate_backoff calcule la valeur brute du backoff en fonction du nombre de retries et des paramètres de configuration.
La bibliothèque devrait permettre de choisir des stratégies de backoff différentes selon les types d’erreurs, afin de ne pas appliquer la même stratégie à toutes les erreurs.
Configuration de la stratégie de jitter
Chaque stratégie de jitter comprend une fonction apply_jitter qui ajoute du jitter au backoff brut.
Cette fonction prend en entrée une référence au nombre de retries (qu’elle peut modifier) et la fonction calculate_backoff. Elle renvoie le jittered_backoff.
La bibliothèque devrait permettre de choisir des stratégies de jitter différentes pour des types d’erreurs différents.
Mise à l’échelle du backoff
Le jittered_backoff est mis à l’échelle en le multipliant par un facteur configuré positif. Une fonction scale_backoff effectue cette mise à l’échelle, en prenant le jittered_backoff et le facteur d’échelle, puis en renvoyant le final_backoff mis à l’échelle.
La bibliothèque doit permettre de choisir des facteurs d’échelle différents pour des types d’erreurs différents.
Fonctionnalités additionnelles
Parfois, le programme doit être informé des retries et effectuer des actions spécifiques lors des erreurs d’accès ou des succès.
Pour cela, la bibliothèque doit permettre à la fonction access_wrapper d’accepter deux paramètres supplémentaires : des callbacks déclenchés en cas de succès ou d’erreur :
on_success: ce callback est appelé lorsque l’accès à la ressource réussit. Il ne prend aucun paramètre et ne renvoie rien.on_error: ce callback est appelé lorsque l’accès à la ressource échoue. Il prend en paramètre l’erreur observée et éventuellement le nombre de retries, et il ne renvoie rien.
La bibliothèque Python Backoff-Utils implémente ces fonctionnalités.
Exemple d’implémentation
Voici un exemple d’implémentation d’un timeout et d’un retry avec backoff dans une bibliothèque d’exemple appelée robustlib.
Cette bibliothèque utilise un backoff exponentiel avec full jitter. La politique de retry est configurée pour ne réessayer que lors d’une ValueError ou d’une TimeoutError Python.
1import time
2import random
3
4# Bibliothèque pour l’accès robuste aux ressources
5
6class RetryPolicy:
7 def __init__(self, max_retry, max_delay):
8 self.max_retry = max_retry
9 self.max_delay = max_delay
10 self.current_retries = 0
11 self.first_try_timestamp = time.time()
12
13 def retry_allowed(self, error):
14 """Détermine si un retry est autorisé en fonction de l’état actuel et de l’erreur."""
15 if not isinstance(error, (ValueError, TimeoutError)):
16 # Réessayer seulement pour ValueError
17 return False
18
19 if self.current_retries >= self.max_retry:
20 # Ne pas réessayer si le nombre maximum de retries est atteint
21 return False
22
23 if time.time() - self.first_try_timestamp > self.max_delay:
24 # Ne pas réessayer si le délai maximum est dépassé
25 return False
26
27 return True
28
29 def new_access(self):
30 """Réinitialise l’état des retries pour une nouvelle tentative d’accès à la ressource."""
31 self.current_retries = 0
32 self.first_try_timestamp = time.time()
33
34 @property
35 def retry_count(self):
36 return self.current_retries
37
38 @retry_count.setter
39 def retry_count(self, new_value):
40 assert new_value >= 0, "Invalid new_value"
41 self.current_retries = new_value
42
43class BackoffStrategy:
44 def __init__(self, *, base_backoff, max_backoff, multiplier=2):
45 self.base_backoff = base_backoff
46 self.max_backoff = max_backoff
47 self.multiplier = multiplier
48
49 def calculate_backoff(self, retry_count):
50 """Calcule le délai de backoff en fonction du nombre de retries."""
51 backoff = min(
52 self.base_backoff * (self.multiplier ** retry_count),
53 self.max_backoff
54 )
55 return backoff
56
57class JitterStrategy:
58 def apply_jitter(self, calculate_backoff_func, retry_count_ref):
59 """Applique un full jitter au backoff calculé par la stratégie."""
60 base_backoff = calculate_backoff_func(retry_count_ref.retry_count)
61 jittered_backoff = random.uniform(0, base_backoff)
62 return jittered_backoff
63
64class AccessWrapper:
65 def __init__(self, *, retry_policy, backoff_strategy, jitter_strategy, scaling_factor=1.0, timeout=-1,
66 on_success=None, on_error=None):
67 self.timeout = timeout
68 self.retry_policy = retry_policy
69 self.backoff_strategy = backoff_strategy
70 self.jitter_strategy = jitter_strategy
71 self.scaling_factor = scaling_factor
72 self.on_success = on_success
73 self.on_error = on_error
74
75 def wrap(self, resource_access):
76 """Enveloppe la fonction d’accès à la ressource avec les stratégies de robustesse."""
77 def wrapper(*args, **kwargs):
78 retry_policy_state = self.retry_policy
79 retry_policy_state.new_access()
80
81 while True:
82 try:
83 # Tenter l’accès à la ressource
84 result = resource_access(self.timeout, *args, **kwargs)
85
86 # Appeler le callback de succès si fourni
87 if self.on_success:
88 self.on_success()
89
90 return result
91 except Exception as error:
92 # Appeler le callback d’erreur si fourni
93 if self.on_error:
94 self.on_error(error, retry_policy_state.retry_count)
95
96 # Vérifier si un retry est autorisé
97 if not retry_policy_state.retry_allowed(error):
98 raise
99
100 # Calculer le backoff avec jitter et mise à l’échelle
101 calculate_backoff = self.backoff_strategy.calculate_backoff
102 jittered_backoff = self.jitter_strategy.apply_jitter(calculate_backoff, retry_policy_state)
103 final_backoff = jittered_backoff * self.scaling_factor
104
105 # Attendre avant de retenter
106 time.sleep(final_backoff)
107
108 # Incrémenter le compteur de retries
109 retry_policy_state.retry_count += 1
110
111 return wrapper
Voici un exemple d’utilisation de la bibliothèque robustlib :
1import random
2from datetime import datetime
3import robustlib
4
5# Configurer le wrapper d’accès
6retry_policy = robustlib.RetryPolicy(max_retry=5, max_delay=60)
7backoff_strategy = robustlib.BackoffStrategy(base_backoff=1, max_backoff=32, multiplier=2)
8jitter_strategy = robustlib.JitterStrategy()
9
10access_wrapper = robustlib.AccessWrapper(
11 retry_policy=retry_policy,
12 backoff_strategy=backoff_strategy,
13 jitter_strategy=jitter_strategy,
14 scaling_factor=1.0,
15 on_success=lambda: print(f"[{datetime.now().time()}] on_success called"),
16 on_error=lambda e, c: print(f"[{datetime.now().time()}] on_error: attempt {c} failed with: {e}")
17)
18
19# Définir une fonction d’accès à la ressource
20def example_resource_access(timeout):
21 # Simuler un accès à la ressource qui peut expirer
22 if random.random() < 0.3:
23 raise TimeoutError("Simulated timeout error")
24
25 # Simuler un accès à la ressource qui peut échouer
26 if random.random() < 0.3:
27 raise ValueError("Simulated resource access error")
28
29 return "Resource accessed successfully"
30
31# Envelopper la fonction d’accès à la ressource
32wrapped_resource_access = access_wrapper.wrap(example_resource_access)
33
34# Tenter d’accéder à la ressource
35try:
36 result = wrapped_resource_access()
37 print(result)
38except Exception as e:
39 print(f"Failed to access resource: {e}")
Sample output:
[05:34:16.749457] on_error: attempt 0 failed with: Simulated timeout error
[05:34:16.895572] on_error: attempt 1 failed with: Simulated timeout error
[05:34:17.063954] on_error: attempt 2 failed with: Simulated resource access error
[05:34:21.044946] on_error: attempt 3 failed with: Simulated timeout error
[05:34:27.702886] on_error: attempt 4 failed with: Simulated resource access error
[05:34:41.832590] on_success called
Resource accessed successfully
Bulletin d'information
Abonnez-vous à notre bulletin d'information et restez informé(e).
Conclusion
A good approach to retries combines backoff, jitter, and a good retry policy.
Dans ce guide, nous avons exploré les stratégies pour améliorer la robustesse des programmes lors de l’accès aux ressources, en insistant sur l’importance de combiner timeout, backoff, jitter et politiques de retry.
Ces éléments, correctement configurés, offrent une approche flexible et adaptable pour gérer l’accès aux ressources, en particulier dans les systèmes distribués.
Autres applications
Ces stratégies ne se limitent pas à l’accès aux services — elles sont aussi essentielles dans des domaines comme les protocoles réseau, l’accès aux bases de données, les verrous de fichiers et les interactions matérielles. Voici quelques exemples :
- Protocoles réseau : le backoff exponentiel avec full jitter aide à prévenir la congestion réseau lors des retries.
- Accès aux bases de données : une politique de retry fixe (retries non infinis) avec un backoff croissant peut réduire l’impact sur les verrous et l’état des transactions.
- Verrous de fichiers : des retries immédiats avec un backoff minimal sont utiles quand on s’attend à ce que les verrous soient libérés rapidement.
En plus des stratégies de retry courantes disponibles dans des bibliothèques comme Python’s Backoff-Utils, Rust’s Backoff et Rust’s Tokio-Retry, il est également possible de créer des stratégies personnalisées adaptées à des besoins spécifiques.
Une bibliothèque flexible doit offrir une interface permettant aux développeurs d’implémenter facilement ces stratégies personnalisées.
Lectures complémentaires
En comprenant et en appliquant correctement ces stratégies, vous pouvez renforcer significativement la résilience et la fiabilité de vos systèmes dans de nombreuses applications.


