示例说明
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