← 中文资源

简体中文版本

Curl 退避重试瞬时错误

为 curl 组合多层重试:内建 retry 标志打底、429 与 5xx 触发外层循环、优先遵循服务端 Retry-After、指数退避封顶次数。

阅读英文原文

示例说明

curl 自带的重试面向传输层抖动;HTTP 层的 429 与 5xx 需要 -f 转成退出码 22 后由外层循环接手。限流响应携带的 Retry-After 头是服务端的礼貌建议——尊重它比本地盲退避更有效:下一次重试恰好落在服务端放行窗口。指数翻倍的本地退避作为无头时的兜底。

技术要点

  • -f 把 4xx/5xx 转为退出码 22
  • Retry-After 头优先于本地退避
  • 双重退避封顶防无限循环

代码解读

fetch_report 的内层 curl 用 –retry 与 –retry-connrefused 处理连接拒绝;失败后 22 码分支再发一次 HEAD 式请求从响应头提取 Retry-After(awk 清理 CR),有效数字替换退避基数。外层循环最多四次,wait 从一秒起指数翻倍。开头的参数展开校验 API_TOKEN 必填。

使用建议

重试只对幂等请求安全,POST 要带幂等键。Retry-After 可能是 HTTP 日期格式需另解析。脚本重试与 curl 内建重试的乘积效应要评估总耗时。

代码示例

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

readonly URL="https://api.example.com/v1/report"
readonly MAX_RETRIES=4

fetch_report() {
  local attempt=0
  local wait=1

  while (( attempt < MAX_RETRIES )); do
    local body
    if body=$(curl -sf --max-time 10 \
        -H "Authorization: Bearer ${API_TOKEN}" \
        --retry "$(( MAX_RETRIES - attempt ))" \
        --retry-delay "${wait}" \
        --retry-connrefused \
        "${URL}"); then
      echo "$body" | jq -r '.rows | length'
      return 0
    fi

    local code=$?
    if (( code == 22 )); then
      # 429/5xx:优先读服务端的 Retry-After
      local retry_after
      retry_after=$(curl -s -o /dev/null -D - "${URL}" \
        | awk -F': ' 'tolower($1)=="retry-after" {gsub(/\r/, "", $2); print $2}')
      if [[ "${retry_after}" =~ ^[0-9]+$ ]]; then
        wait=$(( retry_after + 1 ))
      fi
    fi

    (( attempt++ )) || true
    (( attempt < MAX_RETRIES )) || break
    echo "第 ${attempt} 次失败,${wait}s 后重试" >&2
    sleep "${wait}"
    wait=$(( wait * 2 ))   # 指数退避
  done

  echo "Error: 重试耗尽仍无法获取报告" >&2
  return 1
}

: "${API_TOKEN:?环境变量 API_TOKEN 未设置}"
fetch_report