Penguasaan Baris Perintah Linux & Penskripan Bash · Pelajaran

Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan

Reka bentuk operasi yang selamat untuk dijalankan semula dan tambahkan penangguhan eksponen bagi panggilan luaran yang tidak stabil

Pelajaran 4 daripada 413 langkah

Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan ialah pelajaran Penguasaan Baris Perintah Linux & Penskripan Bash percuma di CoddyKit. Ini ialah pelajaran 4 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Penguasaan Baris Perintah Linux & Penskripan Bash, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Penguasaan Baris Perintah Linux & Penskripan Bash merangkumi sejumlah 4 pelajaran.

Apakah Idempotenan dan Mengapa Ia Penting

Idempotenan bermaksud menjalankan operasi yang sama beberapa kali menghasilkan keputusan yang sama seperti menjalankannya sekali. Dalam penskripan Bash, perkara ini penting kerana skrip boleh ranap, rangkaian boleh terputus dan manusia boleh menjalankan semula sesuatu secara tidak sengaja.

  • Skrip yang tidak idempoten dan mencipta pengguna dua kali mungkin gagal atau menghasilkan data pendua.
  • Skrip idempoten membuat pemeriksaan dahulu: adakah perkara ini sudah wujud?
  • Skrip idempoten selamat digunakan dalam tugas cron, saluran paip CI dan gelung percubaan semula.

Peraturan emas: buat pemeriksaan sebelum bertindak. Setiap operasi yang memusnahkan atau mencipta sesuatu hendaklah dilindungi oleh ujian prasyarat.

Melindungi Penciptaan File dan Direktori

Corak idempotenan yang paling biasa ialah memeriksa sama ada sumber sudah wujud sebelum menciptanya. Bash menyediakan arahan satu baris yang ringkas untuk tujuan ini.

  • [ -d dir ] — benar jika direktori wujud
  • [ -f file ] — benar jika file biasa wujud
  • mkdir -p — mencipta direktori hanya jika tiada (idempotenan terbina dalam)

Utamakan bendera terbina dalam seperti -p dan --no-clobber berbanding pemeriksaan manual apabila tersedia — bendera ini bersifat atom dan selamat daripada keadaan perlumbaan.

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

Pengurusan Pengguna dan Kumpulan Idempoten

Tugas pentadbiran sistem seperti menambah pengguna atau kumpulan mestilah idempoten — menjalankan semula skrip pada mesin yang sama tidak sepatutnya menghasilkan ralat atau pendua.

  • id username mengembalikan 0 jika pengguna wujud
  • getent group groupname memeriksa kewujudan kumpulan
  • Bungkus setiap operasi dalam perlindungan supaya skrip selamat untuk dijalankan semula

Corak ini menjadi asas alat pengurusan konfigurasi seperti Ansible — setiap tugas ialah operasi yang dilindungi dan idempoten.

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

Menggunakan File Kunci untuk Menghalang Pelaksanaan Serentak

Malah skrip idempoten boleh menyebabkan masalah jika dua tika berjalan serentak. File kunci memastikan hanya satu tika berjalan pada satu-satu masa.

  • Cipta file kunci semasa permulaan; buangnya semasa keluar.
  • Gunakan trap untuk membersihkan kunci walaupun skrip terganggu.
  • mkdir pada satu laluan bersifat atom pada kebanyakan sistem file Linux — lebih selamat daripada touch untuk penguncian.

Tanpa kunci, tugas cron yang perlahan dan pelaksanaan semula secara manual boleh bertembung lalu merosakkan keadaan dikongsi.

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

Menjejaki Langkah yang Selesai dengan File Keadaan

Untuk skrip berbilang langkah (pemindahan, pemasangan, penyediaan), Anda boleh menjejaki langkah yang telah selesai menggunakan file keadaan. Setiap langkah memeriksa file keadaan sebelum dilaksanakan dan menulis kepadanya selepas selesai.

  • Murah dan mudah alih — tiada pangkalan data diperlukan.
  • Membolehkan skrip yang gagal menyambung semula dari tempat ia berhenti.
  • Simpan keadaan di lokasi yang boleh diramal seperti /var/lib/myapp/ atau ~/.myapp/state/.

Corak ini digunakan oleh alat utama seperti apt, cloud-init dan rangka kerja pemindahan pangkalan data.

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

Pengenalan kepada Logik Percubaan Semula

Panggilan luaran — permintaan HTTP, carian DNS dan panggilan API awan — sememangnya tidak boleh dipercayai. Satu kegagalan tidak sepatutnya menghentikan keseluruhan skrip Anda. Logik percubaan semula mencuba semula arahan yang gagal secara automatik.

  • Percubaan semula naif: ulang gelung sebanyak N kali sehingga arahan berjaya.
  • Sentiasa tetapkan bilangan maksimum percubaan semula untuk mengelakkan gelung tanpa penghujung.
  • Catat setiap percubaan supaya kegagalan boleh didiagnosis.

Fungsi percubaan semula paling ringkas membungkus sebarang arahan dan mencubanya semula sehingga bilangan tetap dengan kelewatan malar. Ini memadai untuk banyak kegunaan, tetapi mempunyai kelemahan kritikal apabila beban tinggi — seperti yang diterangkan seterusnya.

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

Masalah Gerombolan Mengamuk

Apabila banyak klien mencuba semula secara serentak selepas kegagalan, mereka menghasilkan gerombolan mengamuk — semuanya mencuba semula pada selang tetap yang sama, membebankan pelayan tepat pada saat yang sama dan menjadikan pemulihan mustahil.

  • 100 skrip mencuba semula setiap 5 saat → 100 permintaan serentak setiap 5 saat.
  • Pelayan memang sudah bergelut; beban yang disegerakkan menjadikannya lebih teruk.
  • Penyelesaiannya ialah undur eksponen: gandakan tempoh menunggu selepas setiap kegagalan.
  • Tambahkan jitter (gangguan rawak) untuk menyahsegerakkan percubaan semula merentas klien.

Undur eksponen dengan jitter ialah piawaian industri yang digunakan oleh SDK AWS, klien Google Cloud dan setiap sistem teragih utama.

Melaksanakan Penangguhan Eksponen

Penangguhan eksponen meningkatkan masa menunggu secara eksponen selepas setiap kegagalan: 1s, 2s, 4s, 8s, 16s... Ini memberikan masa kepada sistem jauh untuk pulih sambil mengurangkan beban keseluruhan.

Formulanya: delay = base * (2 ^ attempt)

  • Tetapkan had (kelewatan maksimum) supaya masa menunggu tidak meningkat tanpa batas.
  • Parameter untuk dilaraskan: base_delay, max_delay, max_attempts.

Fungsi ini boleh digunakan semula — hantarkan apa-apa perintah kepadanya sebagai argumen.

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

Menambahkan Jiter pada Penangguhan

Jiter menambahkan kerawakan pada kelewatan penangguhan. Walaupun menggunakan penangguhan eksponen, jika semua klien bermula pada masa yang sama, mereka masih akan mencuba semula secara serentak. Jiter memecahkan penyegerakan ini.

Dua strategi jiter yang biasa:

  • Jiter penuh: sleep random(0, cap) — penyebaran maksimum, beban puncak paling rendah.
  • Jiter sama rata: sleep cap/2 + random(0, cap/2) — menjamin masa menunggu minimum dan mengelakkan percubaan bertalu-talu dengan serta-merta.

Dalam Bash, gunakan $RANDOM (0–32767) untuk menjana nombor rawak. Skalakan nombor itu kepada julat kelewatan anda menggunakan aritmetik modulo.

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

Menggabungkan Idempoten dan Percubaan Semula dalam Aliran Kerja Sebenar

Dalam skrip produksi, logik idempoten dan percubaan semula berfungsi bersama. Aliran kerja penggunaan biasa mungkin:

  1. Mendapatkan kunci (menghalang pelaksanaan serentak)
  2. Memeriksa fail keadaan (melangkau langkah yang telah selesai)
  3. Menggunakan percubaan semula dengan penangguhan untuk panggilan luaran (muat turun, API, DNS)
  4. Menandakan langkah sebagai selesai hanya selepas kejayaan disahkan
  5. Melepaskan kunci melalui trap

Gabungan ini menjadikan skrip selamat untuk dijalankan semula pada bila-bila masa — selepas ranap, tamat masa, atau pembatalan manual — tanpa meninggalkan sistem dalam keadaan rosak.

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

Mengendalikan Ralat yang Tidak Boleh Dicuba Semula

Tidak semua ralat patut dicuba semula. Mencuba semula 404 Not Found atau 403 Forbidden adalah membazir — ralat tersebut tidak akan berjaya tanpa campur tangan manusia. Logik percubaan semula anda harus membezakan antara:

  • Ralat sementara — tamat masa rangkaian, 503 Service Unavailable, kegagalan DNS → cuba semula
  • Ralat kekal — 401 Unauthorized, 404 Not Found, input tidak sah → gagal serta-merta

Dengan curl, semak kod status HTTP dan langkau percubaan semula untuk respons 4xx. Gunakan --write-out '%{http_code}' untuk menangkap status secara berasingan daripada isi kandungan.

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

Semakan Pengetahuan: Pemilihan Strategi Penangguhan

Skrip penggunaan memuat turun artifak keluaran daripada baldi S3. Semasa insiden baru-baru ini, kesemua 80 kejadian saluran paip gagal serentak disebabkan gangguan S3 yang singkat. Apabila S3 pulih selepas 10 saat, kesemua 80 kejadian mencuba semula pada saat yang sama, lalu menyebabkan beban berlebihan sekali lagi dan memanjangkan gangguan selama 3 minit.

Strategi percubaan semula manakah yang paling baik untuk mencegah rangkaian kesan kumpulan besar ini dalam insiden akan datang?

Imbas Kembali: Idempoten dan Percubaan Semula dengan Penangguhan

Dalam pelajaran ini, anda mempelajari cara mereka bentuk skrip Bash yang selamat untuk dijalankan semula dan tahan terhadap kegagalan sementara.

Corak idempoten:

  • Lindungi setiap operasi dengan pemeriksaan kewujudan ([ -f ], [ -d ], id, getent).
  • Gunakan mkdir -p dan bendera idempoten terbina dalam yang lain jika tersedia.
  • Gunakan fail kunci (mkdir atomik) untuk menghalang pelaksanaan serentak.
  • Gunakan fail keadaan (fail penanda bagi setiap langkah) untuk membolehkan sambungan semula selepas kegagalan.

Corak percubaan semula dengan penangguhan:

  • Sentiasa tetapkan bilangan percubaan maksimum — jangan mencuba semula selama-lamanya.
  • Gunakan penangguhan eksponen: gandakan kelewatan selepas setiap kegagalan.
  • Tambahkan jiter (kerawakan) untuk mencegah penyegerakan kumpulan besar.
  • Bezakan ralat sementara (cuba semula) daripada ralat kekal (gagal dengan segera).

Menggabungkan corak ini menghasilkan skrip bertaraf produksi: selamat, boleh diperhatikan dan mampu memulihkan diri.

Percuma untuk bermula

Pelajari Bash dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
22
Pelajaran
88

Soalan Lazim

Adakah pelajaran “Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan” percuma?

Ya — teks penuh “Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Penguasaan Baris Perintah Linux & Penskripan Bash, tingkat taraf kepada CoddyKit PRO. Kursus Penguasaan Baris Perintah Linux & Penskripan Bash merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan”?

Reka bentuk operasi yang selamat untuk dijalankan semula dan tambahkan penangguhan eksponen bagi panggilan luaran yang tidak stabil Anda berlatih Penguasaan Baris Perintah Linux & Penskripan Bash menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan Penguasaan Baris Perintah Linux & Penskripan Bash?

Tiada pengalaman terdahulu diperlukan. Pembelajaran Penguasaan Baris Perintah Linux & Penskripan Bash di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 4 daripada 4.

Berapa lamakah pelajaran “Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran Penguasaan Baris Perintah Linux & Penskripan Bash ini?

Ya. Setiap pelajaran Penguasaan Baris Perintah Linux & Penskripan Bash menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Mod Ketat dengan set -euo pipefail
  2. Pengendali Trap untuk Pembersihan dan Isyarat
  3. Fail Sementara Selamat dan Direktori Kunci
  4. Skrip Idempoten dan Logik Percubaan Semula dengan Penangguhan
← Kembali ke Penguasaan Baris Perintah Linux & Penskripan Bash