2026.08.02 | youres | 95次围观
为什么批量任务需要超时告警
生产环境运行的Shell脚本,经常遇到这些问题:
- 任务卡死:网络请求无响应,进程一直挂起不退出
- 资源耗尽:数据库查询慢,占用大量内存不释放
- 连锁反应:一个任务超时,后续依赖任务全部堵塞
- 发现滞后:没有告警机制,问题积累到用户投诉才暴露
给脚本加上timeout超时控制、自动重试、邮件告警,能从根本上解决这些问题。
基础方案:timeout控制+简单邮件告警
最简单的做法是在脚本中嵌入timeout命令,超时后发送邮件通知。
核心代码模板
#!/bin/bash
# 邮件配置
MAIL_TO="admin@example.com"
MAIL_FROM="server@example.com"
SMTP_SERVER="smtp.example.com"
# 超时时间(秒)
TIMEOUT_SECONDS=60
# 任务执行函数
run_task() {
# 这里放你的业务逻辑
curl -s "https://api.example.com/data" > /tmp/result.json
}
# 发送邮件函数
send_mail() {
local subject="$1"
local body="$2"
echo "$body" | mail -s "$subject" -r "$MAIL_FROM" "$MAIL_TO"
}
# 执行任务并监控超时
timeout $TIMEOUT_SECONDS run_task
EXIT_CODE=$?
if [ $EXIT_CODE -eq 124 ]; then
send_mail "任务超时告警" "任务执行超过${TIMEOUT_SECONDS}秒,已强制终止"
exit 1
elif [ $EXIT_CODE -ne 0 ]; then
send_mail "任务失败告警" "任务执行失败,退出码:${EXIT_CODE}"
exit 1
fi
echo "任务执行成功"
退出码含义说明
- 124:timeout命令超时,进程被SIGTERM终止
- 137:进程被SIGKILL强制杀死(timeout使用--kill-after时)
- 其他值:被监控命令自己的退出码
进阶方案:超时自动重试+邮件通知
任务超时后直接放弃太可惜,更合理的做法是自动重试几次,多次失败才发邮件告警。
重试脚本完整实现
#!/bin/bash
# 配置参数
TIMEOUT=30 # 单次执行超时时间
MAX_RETRY=3 # 最大重试次数
RETRY_DELAY=5 # 重试间隔(秒)
MAIL_TO="admin@example.com"
# 任务执行函数
run_task() {
# 你的业务逻辑
python3 /opt/scripts/process_data.py
}
# 发送邮件
send_alert() {
local msg="$1"
echo "$msg" | mail -s "批量任务告警 - $(date '+%Y-%m-%d %H:%M:%S')" "$MAIL_TO"
}
# 带重试的任务执行
retry_count=0
while [ $retry_count -lt $MAX_RETRY ]; do
echo "第$((retry_count+1))次执行任务..."
timeout $TIMEOUT run_task
EXIT_CODE=$?
# 成功则退出循环
if [ $EXIT_CODE -eq 0 ]; then
echo "任务执行成功"
exit 0
fi
# 超时或失败,记录日志
if [ $EXIT_CODE -eq 124 ]; then
echo "任务超时,准备重试..."
else
echo "任务失败,退出码:$EXIT_CODE,准备重试..."
fi
retry_count=$((retry_count + 1))
# 未达到最大重试次数,等待后重试
if [ $retry_count -lt $MAX_RETRY ]; then
sleep $RETRY_DELAY
fi
done
# 重试全部失败,发送邮件告警
send_alert "任务已重试${MAX_RETRY}次仍失败,请检查服务器状态
超时时间:${TIMEOUT}秒
重试次数:${MAX_RETRY}次
重试间隔:${RETRY_DELAY}秒
最后退出码:${EXIT_CODE}
时间:$(date '+%Y-%m-%d %H:%M:%S')"
exit 1
邮件服务器配置
脚本中的mail命令需要配置邮件服务器才能正常发送邮件。
CentOS/RHEL配置postfix
# 安装postfix和mailx yum install -y postfix mailx # 启动postfix服务 systemctl start postfix systemctl enable postfix # 配置SMTP中继(以QQ邮箱为例) vim /etc/mail.rc # 在文件末尾添加 set from=your_email@qq.com set smtp=smtp.qq.com set smtp-auth-user=your_email@qq.com set smtp-auth-password=你的授权码 set smtp-auth=login # 测试发送 echo "测试邮件内容" | mail -s "测试标题" recipient@example.com
Ubuntu/Debian配置
# 安装mailutils apt-get install -y mailutils # 配置/etc/postfix/main.cf relayhost = [smtp.qq.com]:587 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_sasl_security_options = noanonymous # 创建认证文件 echo "[smtp.qq.com]:587 your_email@qq.com:授权码" > /etc/postfix/sasl_passwd postmap /etc/postfix/sasl_passwd # 重启服务 systemctl restart postfix
企业级方案:多通道告警+日志记录
生产环境建议配置邮件+钉钉/企业微信双通道告警,并保存执行日志。
完整的企业级脚本
#!/bin/bash
# 企业级批量任务监控脚本
# ========== 配置区 ==========
TASK_NAME="数据同步任务"
TIMEOUT=120
MAX_RETRY=3
RETRY_DELAY=10
LOG_DIR="/var/log/task_monitor"
MAIL_TO="admin@example.com"
DING_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=xxx"
# 创建日志目录
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/$(date '+%Y%m%d').log"
# ========== 函数定义 ==========
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_mail() {
local subject="$1"
local body="$2"
echo "$body" | mail -s "$subject" "$MAIL_TO"
}
send_dingtalk() {
local msg="$1"
curl -s -X POST "$DING_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${TASK_NAME}告警:$msg\"}}"
}
# 任务执行函数(替换为你的业务逻辑)
run_task() {
/opt/scripts/batch_sync.sh
}
# ========== 主流程 ==========
log "========== 开始执行任务 =========="
log "任务名称: $TASK_NAME"
log "超时时间: ${TIMEOUT}秒"
log "最大重试: ${MAX_RETRY}次"
retry=0
while [ $retry -lt $MAX_RETRY ]; do
log "第$((retry+1))次执行..."
timeout $TIMEOUT run_task 2>&1 | tee -a "$LOG_FILE"
EXIT_CODE=${PIPESTATUS[0]}
if [ $EXIT_CODE -eq 0 ]; then
log "任务执行成功"
exit 0
fi
if [ $EXIT_CODE -eq 124 ]; then
log "警告: 任务超时"
else
log "警告: 任务失败,退出码=$EXIT_CODE"
fi
retry=$((retry + 1))
[ $retry -lt $MAX_RETRY ] && sleep $RETRY_DELAY
done
# 全部失败,发送告警
log "错误: 重试${MAX_RETRY}次后仍然失败"
ALERT_MSG="${TASK_NAME}执行失败
时间: $(date '+%Y-%m-%d %H:%M:%S')
超时: ${TIMEOUT}秒
重试: ${MAX_RETRY}次
退出码: ${EXIT_CODE}
日志: ${LOG_FILE}"
# 双通道告警
send_mail "${TASK_NAME}失败告警" "$ALERT_MSG"
send_dingtalk "$ALERT_MSG"
log "告警已发送"
exit 1
常见问题排查
邮件发送失败
- 检查postfix状态:
systemctl status postfix - 查看邮件队列:
mailq - 测试SMTP连通性:
telnet smtp.qq.com 587 - 检查防火墙:确保25或587端口已开放
timeout命令不生效
- 确认coreutils版本:timeout命令来自coreutils包,
rpm -q coreutils - 检查命令语法:timeout必须放在要执行的命令前面
- 进程无法终止:使用
timeout -s SIGKILL强制杀死顽固进程
重试逻辑异常
- 循环变量错误:确保使用
$((...))进行算术运算 - 退出码判断失误:注意
timeout返回124表示超时,不是命令本身退出码 - 信号干扰:添加
trap捕获SIGINT/SIGTERM,避免脚本被意外中断
最佳实践建议
- 分级告警:超时发预警,重试失败发严重告警,区分优先级
- 日志轮转:定期清理日志目录,避免磁盘占满
- 配置外部化:将邮箱、Webhook等配置写入独立配置文件
- 监控执行时长:即使未超时,执行时间异常增长也值得关注
- 测试告警通道:定期发送测试邮件,确保告警通道畅通
总结
给Shell脚本添加timeout超时控制、自动重试、邮件告警,是保障生产环境稳定运行的基础能力。从简单的timeout+mail组合,到企业级的双通道告警+日志记录,可以根据实际需求选择合适方案。关键是确保异常情况能第一时间通知到运维人员,避免问题积累到用户投诉才发现。
相关文章
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论