Linux-kommandolinjen og Bash-scripting på ekspertniveau · Lektion

Idempotente scripts og retry-logik med backoff

Design handlinger, der er sikre at køre igen, og tilføj eksponentiel backoff til ustabile eksterne kald.

Lektion 4 af 413 trin

Idempotente scripts og retry-logik med backoff er en gratis Linux-kommandolinjen og Bash-scripting på ekspertniveau-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Linux-kommandolinjen og Bash-scripting på ekspertniveau, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Linux-kommandolinjen og Bash-scripting på ekspertniveau-kurset indeholder 4 lektioner i alt.

Hvad er idempotens, og hvorfor er det vigtigt

Idempotens betyder, at den samme operation kan køres flere gange og give det samme resultat som ved én kørsel. I Bash-scripting er dette afgørende, fordi scripts går ned, netværksforbindelser afbrydes, og mennesker ved et uheld kører ting igen.

  • Et ikke-idempotent script, der opretter en bruger to gange, kan fejle eller oprette dublerede data.
  • Et idempotent script tjekker først: findes dette allerede?
  • Idempotente scripts er sikre at bruge i cron-job, CI-pipelines og gentagelsesløkker.

Den vigtigste regel er: tjek, før du handler. Enhver destruktiv eller oprettende operation bør beskyttes af et forudgående betingelsestjek.

Beskyttelse af oprettelse af filer og mapper

Det mest almindelige idempotensmønster er at tjekke, om en ressource allerede findes, før den oprettes. Bash giver korte kommandoer på én linje til dette.

  • [ -d dir ] — sand, hvis mappen findes
  • [ -f file ] — sand, hvis den almindelige fil findes
  • mkdir -p — opretter kun mappen, hvis den ikke findes (indbygget idempotens)

Foretræk indbyggede flag som -p og --no-clobber frem for manuelle tjek, når de er tilgængelige — de er atomiske og sikre mod kapløbsforhold.

#!/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 håndtering af brugere og grupper

Systemadministrationsopgaver som tilføjelse af brugere eller grupper skal være idempotente — hvis scriptet køres igen på den samme maskine, bør det hverken give en fejl eller oprette dubletter.

  • id username returnerer 0, hvis brugeren findes
  • getent group groupname tjekker, om en gruppe findes
  • Omslut hver operation med en beskyttelse, så scriptet er sikkert at køre igen

Dette mønster er grundlaget for konfigurationsstyringsværktøjer som Ansible — hver opgave er en beskyttet, idempotent operation.

#!/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

Brug af låsefiler til at forhindre samtidige kørsler

Selv et idempotent script kan give problemer, hvis to instanser kører samtidigt. En låsefil sikrer, at kun én instans kører ad gangen.

  • Opret en låsefil ved opstart, og fjern den ved afslutning.
  • Brug en trap til at rydde låsen op, selv hvis scriptet afbrydes.
  • mkdir på en enkelt sti er atomisk på de fleste Linux-filsystemer — det er sikrere end touch til låsning.

Uden en lås kan et langsomt cron-job og en manuel genkørsel kollidere og beskadige den delte tilstand.

#!/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."

Sporing af gennemførte trin med en tilstandsfil

For scripts med flere trin (migreringer, installationer, klargøring) kan du spore, hvilke trin der allerede er gennemført, ved hjælp af en tilstandsfil. Hvert trin tjekker tilstandsfilen, før det udføres, og skriver til den, når det er færdigt.

  • Billigt og portabelt — ingen database er nødvendig.
  • Gør det muligt for et fejlet script at fortsætte fra det sted, hvor det stoppede.
  • Gem tilstanden på en forudsigelig placering som /var/lib/myapp/ eller ~/.myapp/state/.

Dette mønster bruges af større værktøjer som apt, cloud-init og rammeværk til databasemigrering.

#!/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...'"

Introduktion til gentagelseslogik

Eksterne kald — HTTP-anmodninger, DNS-opslag og kald til cloud-API'er — er grundlæggende upålidelige. En enkelt fejl bør ikke afbryde hele dit script. Gentagelseslogik forsøger automatisk en fejlet kommando igen.

  • Naiv gentagelse: gentag løkken N gange, indtil kommandoen lykkes.
  • Sæt altid et maksimalt antal gentagelser for at undgå uendelige løkker.
  • Log hvert forsøg, så fejl kan diagnosticeres.

Den enkleste gentagelsesfunktion omslutter en vilkårlig kommando og prøver den igen op til et fast antal gange med en konstant forsinkelse. Det er tilstrækkeligt til mange anvendelser, men har en kritisk svaghed under belastning — som gennemgås i næste afsnit.

#!/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."

Problemet med den trampende flok

Når mange klienter gentager deres forsøg samtidigt efter en fejl, skaber de en trampende flok — alle prøver igen med præcis samme faste interval, overbelaster serveren på nøjagtig samme tidspunkt og gør genoprettelse umulig.

  • 100 scripts prøver igen hvert 5. sekund → 100 samtidige anmodninger hvert 5. sekund.
  • Serveren har allerede problemer; den synkroniserede belastning gør det værre.
  • Løsningen er eksponentiel backoff: fordobl ventetiden efter hver fejl.
  • Tilføj jitter (tilfældig variation) for at desynkronisere gentagelser på tværs af klienter.

Eksponentiel backoff med jitter er branchestandarden, som bruges af AWS SDK'er, Google Cloud-klienter og alle større distribuerede systemer.

Implementering af eksponentiel backoff

Eksponentiel backoff øger ventetiden eksponentielt efter hver fejl: 1s, 2s, 4s, 8s, 16s ... Det giver fjernsystemet tid til at komme sig, samtidig med at den samlede belastning reduceres.

Formlen: delay = base * (2 ^ attempt)

  • Sæt en grænse (maksimal forsinkelse), så ventetiderne ikke vokser uden begrænsning.
  • Parametre, der kan justeres: base_delay, max_delay, max_attempts.

Denne funktion kan genbruges — giv den en vilkårlig kommando som argument.

#!/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"

Tilføjelse af jitter til backoff

Jitter tilføjer tilfældighed til backoff-forsinkelsen. Selv med eksponentiel backoff vil alle klienter stadig forsøge igen synkront, hvis de starter på samme tid. Jitter bryder denne synkronisering.

To almindelige jitter-strategier:

  • Fuld jitter: sleep random(0, cap) — størst spredning og lavest spidsbelastning.
  • Ensartet jitter: sleep cap/2 + random(0, cap/2) — garanterer en minimumsventetid og forhindrer øjeblikkelig overbelastning.

I Bash kan du bruge $RANDOM (0–32767) til at generere tilfældige tal. Tilpas dem til dit forsinkelsesinterval ved hjælp af moduloaritmetik.

#!/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."

Kombination af idempotens og gentagelse i et realistisk arbejdsforløb

I produktionsscripts arbejder idempotens og gentagelseslogik sammen. Et typisk udrulningsforløb kan være:

  1. Hent en lås (forhindrer samtidige kørsler)
  2. Kontrollér tilstandsfiler (spring gennemførte trin over)
  3. Brug gentagelse med backoff til eksterne kald (download, API, DNS)
  4. Markér først trin som gennemførte efter bekræftet succes
  5. Frigiv låsen via trap

Denne kombination gør scripts sikre at køre igen på ethvert tidspunkt — efter et nedbrud, et tidsudløb eller en manuel afbrydelse — uden at efterlade systemet i en ødelagt tilstand.

#!/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."

Håndtering af fejl, der ikke kan gentages

Det er ikke alle fejl, der bør forsøges igen ved. Det er spild af ressourcer at forsøge igen ved 404 Not Found eller 403 Forbidden — de lykkes aldrig uden menneskelig indgriben. Din gentagelseslogik bør skelne mellem:

  • Midlertidige fejl — netværkstidsudløb, 503 Service Unavailable, DNS-fejl → forsøg igen
  • Vedvarende fejl — 401 Unauthorized, 404 Not Found, ugyldigt input → afslut straks med fejl

Med curl kan du kontrollere HTTP-statuskoden og springe gentagelser over for 4xx-svar. Brug --write-out '%{http_code}' til at hente status separat fra brødteksten.

#!/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"

Vidensprøve: valg af backoff-strategi

Et udrulningsscript downloader en versionsartefakt fra en S3-bucket. Under en nylig hændelse mislykkedes 80 pipeline-instanser samtidigt på grund af et kortvarigt S3-nedbrud. Da S3 kom sig efter 10 sekunder, forsøgte alle 80 instanser igen på samme tidspunkt, hvilket forårsagede endnu en overbelastning og forlængede nedbruddet med 3 minutter.

Hvilken gentagelsesstrategi vil bedst forhindre denne kaskade af en flok, der stormer systemet, ved fremtidige hændelser?

Opsummering: idempotens og gentagelse med backoff

I denne lektion lærte du at designe Bash-scripts, der er sikre at køre igen og modstandsdygtige over for midlertidige fejl.

Mønstre for idempotens:

  • Beskyt enhver handling med et eksistenscheck ([ -f ], [ -d ], id, getent).
  • Brug mkdir -p og andre indbyggede idempotente flag, når de er tilgængelige.
  • Brug låsefiler (atomisk mkdir) for at forhindre samtidige kørsler.
  • Brug tilstandsfiler (markørfiler pr. trin), så du kan genoptage efter en fejl.

Mønstre for gentagelse med backoff:

  • Angiv altid et maksimalt antal forsøg — gentag aldrig for evigt.
  • Brug eksponentiel backoff: fordobl forsinkelsen efter hver fejl.
  • Tilføj jitter (tilfældighed) for at forhindre synkronisering, hvor mange klienter forsøger samtidig.
  • Skeln mellem midlertidige fejl (forsøg igen) og vedvarende fejl (afslut hurtigt).

Ved at kombinere disse mønstre får du scripts i produktionskvalitet: sikre, observerbare og selvreparerende.

Gratis at komme i gang

Lær Bash med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
22
Lektioner
88

Ofte stillede spørgsmål

Er lektionen “Idempotente scripts og retry-logik med backoff” gratis?

Ja — hele teksten til “Idempotente scripts og retry-logik med backoff” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Linux-kommandolinjen og Bash-scripting på ekspertniveau-kurset, skal du opgradere til CoddyKit PRO. Linux-kommandolinjen og Bash-scripting på ekspertniveau-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Idempotente scripts og retry-logik med backoff”?

Design handlinger, der er sikre at køre igen, og tilføj eksponentiel backoff til ustabile eksterne kald. Du øver dig i Linux-kommandolinjen og Bash-scripting på ekspertniveau med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Linux-kommandolinjen og Bash-scripting på ekspertniveau?

Der kræves ingen tidligere erfaring. Linux-kommandolinjen og Bash-scripting på ekspertniveau på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Idempotente scripts og retry-logik med backoff”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Linux-kommandolinjen og Bash-scripting på ekspertniveau-lektion?

Ja. Alle Linux-kommandolinjen og Bash-scripting på ekspertniveau-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Streng tilstand med set -euo pipefail
  2. Trap-håndteringer til oprydning og signaler
  3. Sikre midlertidige filer og låsem mapper
  4. Idempotente scripts og retry-logik med backoff
← Tilbage til Linux-kommandolinjen og Bash-scripting på ekspertniveau