Gestion des échecs en Bash : Implémenter un Exponential Backoff pour les réessais

  • 30 juin 2024
  • 4 min. à lire

En programmation, les échecs temporaires lors des connexions réseau ou des accès à des ressources distantes sont inévitables. Plutôt que de laisser ces interruptions compromettre vos scripts, pourquoi ne pas adopter une stratégie éprouvée pour les gérer efficacement ? Découvrez comment implémenter l'exponential backoff, un design pattern puissant qui rendra vos scripts plus fiables et résilients.

En travaillant sur des scripts Bash (entre autre), on se heurte souvent à des échecs temporaires, surtout lors des connexions réseau ou des accès à des ressources distantes comme avec SSH, cURL, ou encore avec des APIs. Généralement, on tente d’introduire des logiques de retry basé sur des logs d’erreurs, mais cela devient rapidement une prise de tête.

Alors, comment gérer ces échecs sans complexifier inutilement notre script ? Et surtout, comment mettre en place une logique de réessai intelligente et efficace ?

La solution réside dans une stratégie élégante et éprouvée : l’exponential backoff. Il est couramment utilisé dans des protocoles de communication tels que TCP/IP pour la gestion des collisions, ainsi que dans les API et services web pour la gestion des erreurs.

Qu’est-ce que l’Exponential Backoff ?

L’exponential backoff (ou en Français et à la hache : repli exponentiel) est une technique qui consiste à augmenter le délai entre chaque tentative de réessai de manière exponentielle. Cette approche permet de réduire la charge sur le système et d’augmenter les chances de succès en laissant plus de temps pour que les conditions transitoires se résolvent.

  • Réduction de la charge sur le système : En espaçant les tentatives de plus en plus, on évite de surcharger le système ou le réseau qui peut déjà être en difficulté.
  • Augmentation des chances de succès : Si le temps d’attente augmente, on laisse alors plus de temps pour que les conditions transitoires (comme des problèmes de réseau) se résolvent d’elles-mêmes.
  • Réduction des collisions : Elle permet de réduire les risques de collisions répétées dans les systèmes où plusieurs clients tentent d’accéder à la même ressource.

Implémentation en Bash

Pour illustrer cette technique, partons d’un exemple où nous vérifions la connexion SSH à un serveur distant.

Script initial et contexte

Imaginons que vous avez un script Bash qui doit vérifier la connexion SSH à un serveur distant. Actuellement, notre script essaie de se connecter une seule fois et échoue immédiatement si la connexion ne peut pas être établie.

Voici à quoi il ressemble :

__check_ssh_connexion() {
    ssh_status=$(ssh -o BatchMode=yes -o ConnectTimeout=5 "${SSH_USER}@${HOST}" echo ok 2>&1)
    if [ "$ssh_status" != "ok" ]; then
        echo "La connexion SSH à $SSH_USER@$HOST a échoué. Veuillez vérifier la configuration SSH."
        exit 1
    fi
    echo "Connexion SSH au serveur distant réussie"
}

# Vérification de la connexion SSH avec le serveur
__check_ssh_connexion

Ce script utilise la commande ssh pour tenter une connexion à un serveur spécifié par SSH_USER et HOST. Si la connexion échoue, le script affiche un message d’erreur et termine avec un code de sortie 1. Si la connexion réussit, il affiche un message de succès.

Cependant, ce script ne gère pas les échecs temporaires de manière optimale. Par exemple, si le réseau est momentanément indisponible, le script échouera immédiatement sans réessayer. C’est ici que la logique d’exponential backoff entre en jeu, permettant de réessayer plusieurs fois avec des délais croissants entre chaque tentative.

Ajout de la logique d’Exponential Backoff

Pour éviter de réécrire cette logique dans chaque fonction, nous créons une fonction générique __exponential_backoff qui prendra en paramètre une autre fonction à exécuter avec cette logique de repli exponentiel.

__exponential_backoff() {
    local max_retries=5
    local retry_count=0
    local wait_time=1
    local func=$1

    while [ $retry_count -lt $max_retries ]; do
        $func
        local status=$?
        if [ $status -eq 0 ]; then
            return 0
        else
            echo "La tentative $((retry_count+1))/$max_retries a échoué pour $func. Réessai dans $wait_time secondes..."
            sleep $wait_time
            wait_time=$((wait_time * 2))
            retry_count=$((retry_count + 1))
        fi
    done

    echo "La fonction $func a échoué après $max_retries tentatives."
    exit 1
}

Modification de la fonction initiale pour qu’elle renvoit un code de sortie approprié

Une fois notre fonction __exponential_backoff en place, nous devons apporter encore quelques ajustement pour que la fonction __check_ssh_connexion renvoit 0 en cas de succès et 1 (ou un autre code non nul) en cas d’échec.

Dans notre exemple, cela donne :

__check_ssh_connexion() {
    ssh_status=$(ssh -o BatchMode=yes -o ConnectTimeout=5 "${SSH_USER}@${HOST}" echo ok 2>&1)
    if [ "$ssh_status" != "ok" ]; then
        echo "La connexion SSH à $SSH_USER@$HOST a échoué. Veuillez vérifier la configuration SSH."
        return 1
    fi
    echo "Connexion SSH au serveur distant réussie"
    return 0
}

Implémentation finale de l’exponential backoff

Et pour terminer, nous modifions notre appel de vérification de connexion SSH pour utiliser cette nouvelle fonction :

# Vérification de la connexion SSH avec le serveur
__exponential_backoff __check_ssh_connexion

Grâce à cette implémentation, notre script tentera de se reconnecter plusieurs fois en cas d’échec, avec un délai croissant entre chaque tentative. Cela augmente considérablement les chances de succès en laissant le temps aux conditions transitoires de se résoudre.

Avantages

  1. Exponential Backoff : Le temps d’attente double après chaque tentative échouée, ce qui est souvent plus efficace pour les systèmes qui peuvent se rétablir après une courte période.
  2. Contrôle des tentatives : Le nombre maximum de tentatives (max_retries) est configurable.
  3. Clarté des messages: Messages clairs pour chaque tentative et en cas d’échec final.

Inconvénients

  1. Limité aux fonctions : Ce script est conçu pour être utilisé avec des fonctions Bash.

Pour finir ..

Simple et efficace, l’implémentation de l’exponential backoff permet de gérer efficacement les échecs temporaires et d’améliorer la fiablité et la résilience de vos scripts.

Par ailleurs, cette technique est un design pattern logiciel éprouvé que vous pouvez utiliser dans d’autres contextes similaire.

Alternative avec le retry linéaire

Étienne BERSAC propose une alternative plus simple en bash, avec seulement 11 lignes pour gérer les échecs temporaires grâce à son approche de retry linéaire. Cette approche offre l’avantage d’être utilisable directement avec des commandes passées en arguments.

Voici le script proposé (Code source d’origine) :

#!/bin/bash -eux

for s in {0..10} ; do
if "$@" ; then
exit 0
else
sleep "$s"
fi
done

exec "$@"

Avantages

  1. Simplicité : Le script est plus simple et plus concis, ce qui le rend facile à comprendre et à maintenir.
  2. Flexibilité : Ce script peut être utilisé directement avec des commandes passées en arguments, sans nécessiter de modifications majeures.
  3. Gradual Backoff : Le temps d’attente augmente linéairement, ce qui peut être suffisant pour de nombreux cas d’usage.

Inconvénients

  1. Moins de contrôle : Le nombre de tentatives et l’intervalle de temps sont moins configurables. Bien que la plage 0..10 puisse être ajustée.

Bonus : code complet

__exponential_backoff() {
    local max_retries=5
    local retry_count=0
    local wait_time=1
    local func=$1

    while [ $retry_count -lt $max_retries ]; do
        $func
        local status=$?
        if [ $status -eq 0 ]; then
            return 0
        else
            echo "La tentative $((retry_count+1))/$max_retries a échoué pour $func. Réessai dans $wait_time secondes..."
            sleep $wait_time
            wait_time=$((wait_time * 2))
            retry_count=$((retry_count + 1))
        fi
    done

    echo "La fonction $func a échoué après $max_retries tentatives."
    exit 1
}

# Fonction métier
__check_ssh_connexion() {
    ssh_status=$(ssh -o BatchMode=yes -o ConnectTimeout=5 "${SSH_USER}@${HOST}" echo ok 2>&1)
    if [ "$ssh_status" != "ok" ]; then
        echo "La connexion SSH à $SSH_USER@$HOST a échoué. Veuillez vérifier la configuration SSH."
        return 1
    fi
    echo "Connexion SSH au serveur distant réussie"
    return 0
}

# Vérification de la connexion SSH avec le serveur avec l'Exponential Backoff
__exponential_backoff __check_ssh_connexion
Nicolas Verlhiac

Nicolas Verlhiac

Full stack software expert | E-commerce & CRM

Nous sommes spécialisés dans la création de solutions technologiques innovantes qui aident les entreprises à rester compétitives et à prospérer.

tracking-thumb