De Linux-opdrachtregel en Bash-scripting beheersen · Les

Idempotente scripts en retrylogica met back-off

Ontwerp bewerkingen die veilig opnieuw kunnen worden uitgevoerd en voeg exponentiële back-off toe voor onbetrouwbare externe aanroepen.

Les 4 van 413 stappen

Idempotente scripts en retrylogica met back-off is een gratis De Linux-opdrachtregel en Bash-scripting beheersen-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject De Linux-opdrachtregel en Bash-scripting beheersen. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus De Linux-opdrachtregel en Bash-scripting beheersen bevat in totaal 4 lessen.

Wat idempotentie is en waarom die belangrijk is

Idempotentie betekent dat meerdere uitvoeringen van dezelfde bewerking hetzelfde resultaat opleveren als één uitvoering. In Bash-scripting is dit cruciaal, omdat scripts crashen, netwerken uitvallen en mensen per ongeluk dingen opnieuw uitvoeren.

  • Een niet-idempotent script dat twee keer een gebruiker maakt, kan mislukken of dubbele gegevens maken.
  • Een idempotent script controleert eerst: bestaat dit al?
  • Idempotente scripts zijn veilig te gebruiken in cron-taken, CI-pijplijnen en herhalingslussen.

De gouden regel: controleer voordat je handelt. Elke destructieve of creërende bewerking moet worden beveiligd met een voorafgaande voorwaardelijke test.

Het maken van bestanden en mappen beveiligen

Het meest voorkomende idempotentiepatroon is controleren of een bron al bestaat voordat je deze maakt. Bash biedt hiervoor beknopte eenregelige opdrachten.

  • [ -d dir ] — waar als de map bestaat
  • [ -f file ] — waar als het gewone bestand bestaat
  • mkdir -p — maakt de map alleen als deze ontbreekt (ingebouwde idempotentie)

Geef waar mogelijk de voorkeur aan ingebouwde vlaggen zoals -p en --no-clobber boven handmatige controles — ze zijn atomair en veilig tegen racecondities.

#!/usr/bin/env bash
set -euo pipefail

CONFIG_DIR="$HOME/.myapp"
CONFIG_FILE="$CONFIG_DIR/config.ini"

# Idempotent: mkdir -p never fails if dir already exists
mkdir -p "$CONFIG_DIR"

# Idempotent: only write config if it doesn't exist yet
if [ ! -f "$CONFIG_FILE" ]; then
  echo '[defaults]' > "$CONFIG_FILE"
  echo 'timeout=30' >> "$CONFIG_FILE"
  echo "Created $CONFIG_FILE"
else
  echo "Config already exists, skipping."
fi

Idempotent gebruikers- en groepsbeheer

Systeembeheertaken zoals het toevoegen van gebruikers of groepen moeten idempotent zijn — als je het script opnieuw uitvoert op dezelfde machine, mag het geen fout opleveren of dubbele items maken.

  • id username geeft 0 terug als de gebruiker bestaat
  • getent group groupname controleert of een groep bestaat
  • Verpak elke bewerking in een voorwaarde, zodat het script veilig opnieuw kan worden uitgevoerd

Dit patroon vormt de basis van configuratiebeheertools zoals Ansible — elke taak is een beveiligde, idempotente bewerking.

#!/usr/bin/env bash
set -euo pipefail

APP_USER="apprunner"
APP_GROUP="appgroup"

# Idempotent group creation
if ! getent group "$APP_GROUP" &>/dev/null; then
  groupadd "$APP_GROUP"
  echo "Group '$APP_GROUP' created."
else
  echo "Group '$APP_GROUP' already exists."
fi

# Idempotent user creation
if ! id "$APP_USER" &>/dev/null; then
  useradd -m -g "$APP_GROUP" -s /bin/bash "$APP_USER"
  echo "User '$APP_USER' created."
else
  echo "User '$APP_USER' already exists."
fi

Slotbestanden gebruiken om gelijktijdige uitvoeringen te voorkomen

Zelfs een idempotent script kan problemen veroorzaken als twee instanties tegelijk worden uitgevoerd. Een slotbestand zorgt ervoor dat er maar één instantie tegelijk wordt uitgevoerd.

  • Maak bij het opstarten een slotbestand en verwijder het bij het afsluiten.
  • Gebruik een trap om het slot op te ruimen, zelfs als het script wordt onderbroken.
  • mkdir op één pad is atomair op de meeste Linux-bestandssystemen — veiliger voor vergrendeling dan touch.

Zonder slot kunnen een trage cron-taak en een handmatige heruitvoering elkaar in de weg zitten en gedeelde toestand beschadigen.

#!/usr/bin/env bash
set -euo pipefail

LOCKFILE="/tmp/myapp_deploy.lock"

# Atomic lock acquisition using mkdir
if ! mkdir "$LOCKFILE" 2>/dev/null; then
  echo "ERROR: Another instance is running (lock: $LOCKFILE). Exiting." >&2
  exit 1
fi

# Guarantee lock removal on any exit
trap 'rmdir "$LOCKFILE"; echo "Lock released."' EXIT

echo "Lock acquired. Running deployment..."
sleep 2   # simulate work
echo "Deployment complete."

Voltooide stappen bijhouden met een toestandbestand

Voor scripts met meerdere stappen (migraties, installaties, inrichting) kun je met een toestandbestand bijhouden welke stappen al zijn voltooid. Elke stap controleert het toestandbestand voordat deze wordt uitgevoerd en schrijft ernaar wanneer de stap klaar is.

  • Goedkoop en draagbaar — er is geen database nodig.
  • Een mislukt script kan hervatten vanaf het punt waar het was gestopt.
  • Sla de toestand op een voorspelbare locatie op, zoals /var/lib/myapp/ of ~/.myapp/state/.

Dit patroon wordt gebruikt door belangrijke hulpmiddelen zoals apt, cloud-init en frameworks voor databasemigraties.

#!/usr/bin/env bash
set -euo pipefail

STATE_DIR="/tmp/myapp_state"
mkdir -p "$STATE_DIR"

run_step() {
  local step_name="$1"
  local step_cmd="$2"
  local marker="$STATE_DIR/${step_name}.done"

  if [ -f "$marker" ]; then
    echo "[SKIP] $step_name already completed."
    return 0
  fi

  echo "[RUN]  $step_name ..."
  eval "$step_cmd"
  touch "$marker"
  echo "[DONE] $step_name"
}

run_step "install_deps"   "echo 'Installing dependencies...'"
run_step "configure_db"   "echo 'Configuring database...'"
run_step "start_service"  "echo 'Starting service...'"

Inleiding tot herhalingslogica

Externe aanroepen — HTTP-verzoeken, DNS-opzoekingen en aanroepen van cloud-API's — zijn van nature onbetrouwbaar. Eén fout mag niet je volledige script afbreken. Herhalingslogica probeert een mislukte opdracht automatisch opnieuw.

  • Naïeve herhaling: loop N keer totdat de opdracht slaagt.
  • Stel altijd een maximumaantal pogingen in om oneindige lussen te voorkomen.
  • Registreer elke poging zodat fouten kunnen worden vastgesteld.

De eenvoudigste herhalingsfunctie verpakt elke opdracht en probeert deze tot een vast aantal keer opnieuw, met een constante vertraging. Dit is goed genoeg voor veel gebruikssituaties, maar heeft onder belasting een belangrijk probleem — dat hierna wordt behandeld.

#!/usr/bin/env bash
set -euo pipefail

# Simple fixed-delay retry (3 attempts, 2s apart)
retry() {
  local max_attempts=3
  local delay=2
  local attempt=1

  until "$@"; do
    if (( attempt >= max_attempts )); then
      echo "ERROR: Command failed after $max_attempts attempts: $*" >&2
      return 1
    fi
    echo "Attempt $attempt failed. Retrying in ${delay}s..." >&2
    sleep "$delay"
    (( attempt++ ))
  done
}

# Example: retry a curl call
retry curl --silent --fail --max-time 5 https://httpbin.org/get -o /dev/null
echo "Request succeeded."

Het probleem van de stormloop

Wanneer veel clients na een fout tegelijk opnieuw proberen, veroorzaken ze een stormloop — allemaal met hetzelfde vaste interval, waardoor ze de server exact op hetzelfde moment overbelasten en herstel onmogelijk maken.

  • 100 scripts proberen elke 5 seconden opnieuw → elke 5 seconden 100 gelijktijdige verzoeken.
  • De server heeft het al moeilijk; de gesynchroniseerde belasting maakt het erger.
  • De oplossing is exponentiële wachttijdverlenging: verdubbel de wachttijd na elke fout.
  • Voeg jitter (willekeurige variatie) toe om herhalingen bij verschillende clients uit elkaar te trekken.

Exponentiële wachttijdverlenging met jitter is de industriestandaard die wordt gebruikt door AWS-SDK's, Google Cloud-clients en alle belangrijke gedistribueerde systemen.

Exponentiële backoff implementeren

Exponentiële backoff verhoogt de wachttijd na elke fout exponentieel: 1 s, 2 s, 4 s, 8 s, 16 s... Zo krijgt het externe systeem tijd om te herstellen en wordt de totale belasting verminderd.

De formule: delay = base * (2 ^ attempt)

  • Stel een limiet in (maximale wachttijd), zodat wachttijden niet onbeperkt blijven groeien.
  • Parameters om af te stemmen: base_delay, max_delay, max_attempts.

Deze functie is herbruikbaar: geef er als argumenten elke gewenste opdracht aan door.

#!/usr/bin/env bash
set -euo pipefail

retry_with_backoff() {
  local max_attempts="${RETRY_MAX_ATTEMPTS:-5}"
  local base_delay="${RETRY_BASE_DELAY:-1}"
  local max_delay="${RETRY_MAX_DELAY:-30}"
  local attempt=0
  local delay="$base_delay"

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: '$*' failed after $max_attempts attempts." >&2
      return 1
    fi
    echo "Attempt $attempt failed. Backing off for ${delay}s..." >&2
    sleep "$delay"
    # Double the delay, but cap it
    delay=$(( delay * 2 ))
    (( delay > max_delay )) && delay=$max_delay
  done

  echo "Command succeeded on attempt $(( attempt + 1 ))."
}

retry_with_backoff echo "Simulated success"

Jitter toevoegen aan backoff

Jitter voegt willekeurigheid toe aan de backoff-wachttijd. Zelfs met exponentiële backoff proberen alle clients het nog steeds gelijktijdig opnieuw als ze op hetzelfde moment starten. Jitter doorbreekt deze synchronisatie.

Twee veelgebruikte jitterstrategieën:

  • Volledige jitter: sleep random(0, cap) — maximale spreiding en de laagste piekbelasting.
  • Gelijke jitter: sleep cap/2 + random(0, cap/2) — garandeert een minimale wachttijd en voorkomt dat er onmiddellijk opnieuw te veel verzoeken worden verstuurd.

Gebruik in Bash $RANDOM (0–32767) om willekeurige getallen te genereren. Schaal deze met modulo-rekenkunde naar het bereik van uw wachttijd.

#!/usr/bin/env bash
set -euo pipefail

retry_with_jitter() {
  local max_attempts="${1:-5}"; shift
  local base_delay=1
  local max_delay=32
  local attempt=0
  local cap=$base_delay

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: Giving up after $max_attempts attempts." >&2
      return 1
    fi

    # Full jitter: random value in [0, cap]
    local jitter=$(( RANDOM % (cap + 1) ))
    echo "Attempt $attempt failed. Sleeping ${jitter}s (cap=${cap}s)..." >&2
    sleep "$jitter"

    # Grow cap exponentially, bounded by max_delay
    cap=$(( cap * 2 ))
    (( cap > max_delay )) && cap=$max_delay
  done
}

retry_with_jitter 4 curl --silent --fail --max-time 3 https://httpbin.org/get -o /dev/null
echo "Done."

Idempotentie en opnieuw proberen combineren in een echte workflow

In productiescripts werken idempotentie en logica voor opnieuw proberen samen. Een typische implementatieworkflow kan het volgende doen:

  1. Een vergrendeling verkrijgen (gelijktijdige uitvoeringen voorkomen)
  2. Statusbestanden controleren (voltooide stappen overslaan)
  3. Opnieuw proberen met backoff gebruiken voor externe aanroepen (download, API, DNS)
  4. Stappen pas als voltooid markeren nadat succes is bevestigd
  5. De vergrendeling vrijgeven via trap

Door deze combinatie kunt u scripts op elk moment veilig opnieuw uitvoeren — na een crash, een time-out of een handmatige afbreking — zonder het systeem in een defecte toestand achter te laten.

#!/usr/bin/env bash
set -euo pipefail

STATE_DIR="/tmp/deploy_state" && mkdir -p "$STATE_DIR"
LOCK="/tmp/deploy.lock"

mkdir "$LOCK" 2>/dev/null || { echo "Already running."; exit 1; }
trap 'rmdir "$LOCK"' EXIT

step_done() { [ -f "$STATE_DIR/$1.done" ]; }
mark_done() { touch "$STATE_DIR/$1.done"; }

retry_backoff() {
  local attempt=0 delay=1
  until "${@:2}"; do
    (( ++attempt >= $1 )) && { echo "Failed after $1 attempts."; return 1; }
    echo "Retry $attempt in ${delay}s..."; sleep $delay; delay=$(( delay * 2 ))
  done
}

if ! step_done "download_artifact"; then
  retry_backoff 4 curl -fsSL https://httpbin.org/get -o /tmp/artifact.json
  mark_done "download_artifact"
  echo "[DONE] download_artifact"
else
  echo "[SKIP] download_artifact"
fi

echo "Deployment finished successfully."

Niet-herhaalbare fouten afhandelen

Niet alle fouten moeten opnieuw worden geprobeerd. Opnieuw proberen bij een 404 Not Found of een 403 Forbidden is verspilling — zonder menselijke tussenkomst zullen deze aanvragen nooit slagen. Uw logica voor opnieuw proberen moet onderscheid maken tussen:

  • Tijdelijke fouten — netwerk-time-out, 503 Service Unavailable, DNS-fout → opnieuw proberen
  • Permanente fouten — 401 Unauthorized, 404 Not Found, ongeldige invoer → onmiddellijk mislukken

Controleer met curl de HTTP-statuscode en sla opnieuw proberen over voor 4xx-antwoorden. Gebruik --write-out '%{http_code}' om de status afzonderlijk van de hoofdtekst vast te leggen.

#!/usr/bin/env bash
set -euo pipefail

fetch_with_retry() {
  local url="$1"
  local max_attempts=4
  local delay=1
  local attempt=0
  local http_code

  until http_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url"); do
    : # curl itself failed (network error)
    (( ++attempt >= max_attempts )) && { echo "Network error, giving up."; return 1; }
    sleep $delay; delay=$(( delay * 2 ))
  done

  # Permanent client errors — do not retry
  if [[ "$http_code" =~ ^4 ]]; then
    echo "ERROR: HTTP $http_code for $url — not retrying." >&2
    return 1
  fi

  # Transient server errors — retry
  if [[ "$http_code" =~ ^5 ]]; then
    (( ++attempt >= max_attempts )) && { echo "Server error, giving up."; return 1; }
    echo "HTTP $http_code — backing off ${delay}s..."
    sleep $delay; delay=$(( delay * 2 ))
    fetch_with_retry "$url"
    return
  fi

  echo "HTTP $http_code — success."
}

fetch_with_retry "https://httpbin.org/status/200"

Kennistoets: een backoffstrategie kiezen

Een implementatiescript downloadt een releaseartefact uit een S3-bucket. Tijdens een recent incident mislukten 80 pijplijninstanties gelijktijdig door een korte S3-storing. Toen S3 na 10 seconden herstelde, probeerden alle 80 instanties het op precies hetzelfde moment opnieuw. Dit veroorzaakte een nieuwe overbelasting en verlengde de storing met 3 minuten.

Welke strategie voor opnieuw proberen zou deze kettingreactie van een stormloop in toekomstige incidenten het best voorkomen?

Samenvatting: idempotentie en opnieuw proberen met backoff

In deze les hebt u geleerd hoe u Bash-scripts ontwerpt die veilig opnieuw kunnen worden uitgevoerd en bestand zijn tegen tijdelijke fouten.

Idempotentiepatronen:

  • Beveilig elke bewerking met een controle op het bestaan ([ -f ], [ -d ], id, getent).
  • Gebruik mkdir -p en andere ingebouwde idempotente opties waar die beschikbaar zijn.
  • Gebruik vergrendelingsbestanden (atomair mkdir) om gelijktijdige uitvoeringen te voorkomen.
  • Gebruik statusbestanden (markeringsbestanden per stap) om na een fout verder te kunnen gaan.

Patronen voor opnieuw proberen met backoff:

  • Stel altijd een maximumaantal pogingen in — probeer nooit voor altijd opnieuw.
  • Gebruik exponentiële backoff: verdubbel de wachttijd na elke fout.
  • Voeg jitter (willekeurigheid) toe om synchronisatie van een stormloop te voorkomen.
  • Maak onderscheid tussen tijdelijke fouten (opnieuw proberen) en permanente fouten (direct mislukken).

Door deze patronen te combineren ontstaan scripts die geschikt zijn voor productie: veilig, inzichtelijk en zelfherstellend.

Gratis beginnen

Leer Bash met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
22
Lessen
88

Veelgestelde vragen

Is de les “Idempotente scripts en retrylogica met back-off” gratis?

Ja — de volledige tekst van “Idempotente scripts en retrylogica met back-off” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus De Linux-opdrachtregel en Bash-scripting beheersen wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus De Linux-opdrachtregel en Bash-scripting beheersen bevat in totaal 4 lessen.

Wat leer ik in “Idempotente scripts en retrylogica met back-off”?

Ontwerp bewerkingen die veilig opnieuw kunnen worden uitgevoerd en voeg exponentiële back-off toe voor onbetrouwbare externe aanroepen. Je oefent met De Linux-opdrachtregel en Bash-scripting beheersen door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met De Linux-opdrachtregel en Bash-scripting beheersen te beginnen?

Ervaring vooraf is niet nodig. De Linux-opdrachtregel en Bash-scripting beheersen op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.

Hoe lang duurt de les “Idempotente scripts en retrylogica met back-off”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over De Linux-opdrachtregel en Bash-scripting beheersen?

Ja. Elke les over De Linux-opdrachtregel en Bash-scripting beheersen bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Strikte modus met set -euo pipefail
  2. Trap-handlers voor opruimen en signalen
  3. Veilige tijdelijke bestanden en vergrendelingsmappen
  4. Idempotente scripts en retrylogica met back-off
← Terug naar De Linux-opdrachtregel en Bash-scripting beheersen