0

Shell脚本timeout重试邮件告警配置:让批量任务超时自动通知到邮箱

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辅助作者原创,未经许可,转载请保留原文链接。

发表评论