在Linux服务器上跑批量任务,最头疼的情况之一就是:任务跑到一半超时了,但不知道到底成功还是失败,只能重新跑一遍。今天这篇文章手把手教你写一个完整的xargs timeout重试Shell脚本,让你的批量任务在超时时自动重试,省心省力。
为什么需要timeout重试机制
先说个场景:你用xargs并行处理1000个API请求,每个请求给了30秒超时。大部分请求2秒就返回了,但有几个请求卡在20-28秒之间,刚好超过你的超时阈值直接被kill掉了。结果呢?这些请求没执行成功,但任务状态显示已完成,你根本不知道哪几个失败了。
所以我们需要的是:timeout + 重试的组合拳。超时了不怕,再试一次,通常第二次就能成功。
方案一:最简单的基础版重试脚本
先从最简单的场景开始。假设你有一批curl请求,每个请求超时30秒,超时了就重试3次。
#!/bin/bash
# xargs timeout基础重试脚本
MAX_RETRIES=3
TIMEOUT_SEC=30
retry_curl() {
local url="$1"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "[尝试 $attempt/$MAX_RETRIES] 请求: $url"
timeout $TIMEOUT_SEC curl -s -o /dev/null -w "%{http_code}" "$url"
local status=$?
if [ $status -eq 0 ]; then
echo "[成功] $url"
return 0
elif [ $status -eq 124 ]; then
echo "[超时] 第${attempt}次失败,等待2秒后重试..."
else
echo "[失败] 退出码: $status"
fi
attempt=$((attempt + 1))
[ $attempt -le $MAX_RETRIES ] && sleep 2
done
echo "[放弃] $url 在${MAX_RETRIES}次尝试后仍然失败"
return 1
}
cat url_list.txt | xargs -P 5 -I {} bash -c "retry_curl \"\$@\"" _ {}
这个脚本的逻辑很清晰:每个URL最多试3次,每次超时30秒,间隔2秒再试。用xargs的-P 5参数控制5个并发。
关键点解读
- 退出码124:这是timeout命令超时后返回的特定退出码
- -P参数:控制xargs的并发数,这里设为5
- sleep间隔:重试间隔2秒,给远程服务一个喘息的机会
方案二:xargs直接集成timeout的重试版
如果你不想单独写函数,想直接在xargs里处理超时和重试:
#!/bin/bash
TIMEOUT_SEC=30
MAX_RETRIES=3
process_url() {
local url="$1"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
if timeout $TIMEOUT_SEC curl -s -f "$url" > /dev/null 2>&1; then
echo "成功: $url"
return 0
else
echo "第${attempt}次失败: $url"
attempt=$((attempt + 1))
[ $attempt -le $MAX_RETRIES ] && sleep 1
fi
done
echo "最终失败: $url"
return 1
}
export -f process_url
export TIMEOUT_SEC MAX_RETRIES
cat url_list.txt | xargs -P 10 -n 1 bash -c "process_url \"\$1\"" _
注意这里的export -f,它把函数导出给子Shell使用。
方案三:带指数退避的智能重试版
很多专业工具用的都是指数退避策略:第一次等1秒,第二次等2秒,第三次等4秒。
#!/bin/bash
MAX_RETRIES=5
BASE_DELAY=1
TIMEOUT_SEC=20
exponential_backoff_retry() {
local url="$1"
local attempt=1
local delay=$BASE_DELAY
while [ $attempt -le $MAX_RETRIES ]; do
echo "[$(date +%H:%M:%S)] 第${attempt}次尝试: $url"
if timeout $TIMEOUT_SEC curl -sf "$url" > /dev/null 2>&1; then
echo "成功: $url"
return 0
fi
if [ $attempt -lt $MAX_RETRIES ]; then
echo "等待${delay}s后重试..."
sleep $delay
delay=$((delay * 2))
fi
attempt=$((attempt + 1))
done
echo "${MAX_RETRIES}次尝试全部失败: $url"
return 1
}
export -f exponential_backoff_retry
export MAX_RETRIES BASE_DELAY TIMEOUT_SEC
cat urls.txt | xargs -P 8 -n 1 bash -c "exponential_backoff_retry \"\$1\"" _
指数退避的等待策略:1秒、2秒、4秒、8秒、16秒。
方案四:用until循环的简洁写法
#!/bin/bash
TIMEOUT=30
MAX=3
retry_until_success() {
local url="$1"
local count=0
until timeout $TIMEOUT curl -sf "$url" > /dev/null 2>&1; do
count=$((count + 1))
echo "尝试 ${count}/${MAX} 失败: $url"
[ $count -ge $MAX ] && echo "放弃: $url" && return 1
sleep 2
done
echo "成功: $url"
}
export -f retry_until_success
export TIMEOUT MAX
xargs -P 5 -I {} bash -c "retry_until_success \"{}\"" < input.txt
xargs并发数怎么选
- CPU密集型任务:并发数设成CPU核心数
- IO密集型任务(网络请求):可以设大一点,10-20都没问题
- 远程API调用:看对方服务的能力,通常10-20个并发比较稳妥
常见问题排查
Q: timeout后进程确实被杀了,但重试时文件被锁住了怎么办?
在脚本开头加个trap清理临时文件:
trap "rm -f /tmp/lock_*.tmp; exit" INT TERM
Q: 任务中途被Ctrl+C中断,怎么知道哪些成功了哪些失败了?
加上进度追踪,每个成功的写到一个文件里:
DONE_FILE="done_urls.txt"
process_url() {
local url="$1"
if timeout $TIMEOUT_SEC curl -sf "$url"; then
echo "$url" >> "$DONE_FILE"
fi
}
总结
今天介绍了4种xargs timeout重试脚本的写法:基础版、管道式、指数退避版、until循环版。
核心要点就是三个:timeout控制单次执行时间、循环实现重试逻辑、xargs -P控制并发数。把这三个组合好,你就能应对绝大多数批量任务的超时重试需求了。
相关阅读:
- xargs timeout命令退出码处理完全指南
- xargs并行处理任务失败自动重试配置
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论