5.6.5 任务可靠性、幂等与告警策略

任务可靠性、幂等与告警策略#

任务调度的核心目标是“按时执行、执行正确、可追踪、可恢复”。可靠性设计应从任务本身、调度系统、运行环境和监控告警四个维度入手,确保任务在失败时可定位、可重试、可回滚且不产生副作用。

原理草图:可靠性与告警链路#

文章图片

可靠性设计要点#

  • 失败可见性:统一输出日志(stdout/stderr),关联执行 ID;关键步骤记录耗时与结果。
  • 可恢复性:失败后自动重试、手动重跑与中断恢复;必要时引入检查点文件。
  • 资源隔离:长任务与高频任务分离;对 CPU、内存、磁盘 IO 设置限额或限流。
  • 依赖显式化:任务间依赖显式声明;对上游服务做健康检查与降级。

幂等设计原则(含示例)#

  • 输入幂等:以输入参数作为唯一键(日期/批次号/业务 ID)。
  • 状态幂等:修改操作“查后改”;写操作使用去重表或唯一约束。
  • 文件幂等:输出“临时文件 + 原子替换”;分区目录覆盖策略。
  • 外部系统幂等:接口请求携带幂等键或业务唯一号。

示例:带锁与幂等文件输出

#!/usr/bin/env bash
# /opt/jobs/report_daily.sh
set -euo pipefail

JOB=report_daily
DATE="${1:-$(date +%F)}"
RUN_ID="$(date +%s)"
LOCK="/var/lock/${JOB}.lock"
OUTDIR="/data/report/${DATE}"
TMP="/data/report/${DATE}.tmp"
LOG="/var/log/${JOB}.log"

exec >>"$LOG" 2>&1
echo "[$(date)] RUN_ID=$RUN_ID DATE=$DATE"

# 幂等锁:避免并发重复执行
if ! mkdir "$LOCK" 2>/dev/null; then
  echo "lock exists, exit"
  exit 0
fi
trap 'rmdir "$LOCK"' EXIT

# 输入幂等:已生成则退出
if [ -f "${OUTDIR}/done.flag" ]; then
  echo "already done for $DATE"
  exit 0
fi

# 业务处理
mkdir -p "$TMP"
echo "report for $DATE" > "$TMP/report.txt"

# 原子替换
mkdir -p "$OUTDIR"
mv "$TMP/report.txt" "$OUTDIR/report.txt"
touch "$OUTDIR/done.flag"
rmdir "$TMP"

echo "success"

重试与补偿策略(含示例)#

  • 重试规则:指数退避(1m/3m/5m/10m),区分可重试与不可重试错误。
  • 最大次数:设置最大重试次数与超时上限,超过后告警并转人工处理。
  • 补偿机制:失败后触发补偿任务(回滚、补发、对账)。

示例:重试包装器

#!/usr/bin/env bash
# /opt/jobs/retry_wrapper.sh
set -euo pipefail

cmd="$*"
max=4
sleep_seq=(60 180 300 600)

for i in $(seq 1 $max); do
  if $cmd; then
    echo "success on attempt $i"
    exit 0
  fi
  echo "failed attempt $i"
  if [ $i -lt $max ]; then
    sleep "${sleep_seq[$((i-1))]}"
  fi
done
echo "failed after $max attempts"
exit 1

告警策略与分级(含示例)#

  • 告警分级:P1(影响业务)、P2(延迟或部分失败)、P3(性能下降)。
  • 触发条件:任务失败、超时、重试超限、执行时长异常、输出为空或异常增量。
  • 告警渠道:邮件/短信/IM,多通道冗余。
  • 告警抑制:重试阶段仅提示,达到阈值再告警;同类告警合并与静默。

安装与命令:邮件告警(mailx)

# Debian/Ubuntu
apt-get install -y mailutils

# RHEL/CentOS
yum install -y mailx

示例:失败告警脚本

#!/usr/bin/env bash
# /opt/jobs/alert_on_fail.sh
set -euo pipefail

JOB="$1"
LOG="/var/log/${JOB}.log"
MAIL_TO="ops@example.com"

if [ ! -f "$LOG" ]; then
  echo "log not found" | mail -s "[P2] $JOB log missing" "$MAIL_TO"
  exit 1
fi

if tail -n 1 "$LOG" | grep -q "success"; then
  exit 0
fi

tail -n 50 "$LOG" | mail -s "[P1] $JOB failed" "$MAIL_TO"

任务审计与可追踪性#

  • 审计字段:任务名、调度时间、执行时间、执行节点、参数、结果、耗时、错误码。
  • 追踪链路:任务与下游系统调用关联,便于故障回溯。
  • 一致性检查:对账脚本或采样核验,保障批处理一致性。

示例:审计记录(CSV)

#!/usr/bin/env bash
# /opt/jobs/audit_append.sh
JOB="report_daily"
RUN_ID="$(date +%s)"
START="$(date +%F\ %T)"
HOST="$(hostname)"
PARAMS="$*"
RESULT="OK"
DURATION=12

echo "$RUN_ID,$JOB,$START,$HOST,$PARAMS,$RESULT,$DURATION" >> /var/log/job_audit.csv

systemd 定时任务可靠性配置示例#

/etc/systemd/system/report_daily.service

[Unit]
Description=Daily report job
After=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/jobs/retry_wrapper.sh /opt/jobs/report_daily.sh
User=ops
Group=ops
StandardOutput=append:/var/log/report_daily.log
StandardError=append:/var/log/report_daily.log

/etc/systemd/system/report_daily.timer

[Unit]
Description=Run report daily

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

启用与验证

systemctl daemon-reload
systemctl enable --now report_daily.timer
systemctl list-timers | grep report_daily
journalctl -u report_daily.service -n 50

排错清单(含命令解释)#

  • 任务未执行:检查定时器是否启用
    systemctl list-timers(列出 timer 状态)
  • 脚本权限问题:检查可执行权限
    ls -l /opt/jobs/report_daily.sh(确认执行位)
  • 锁未释放:检查锁目录
    ls -ld /var/lock/report_daily.lock(确认异常残留)
  • 日志为空:检查重定向与输出
    tail -n 50 /var/log/report_daily.log(查看最后输出)
  • 重复执行:检查幂等标记
    ls /data/report/$(date +%F)/done.flag(是否已生成)

练习#

  1. 为一个每小时执行的脚本添加幂等锁与 done.flag,验证重复执行不会产生重复结果。
  2. 使用 retry_wrapper.sh 包装一个会随机失败的脚本,观察重试与最终退出码。
  3. 将任务失败邮件改为 IM webhook(如企业微信/Slack),并保留邮件作为备份通道。
  4. 设计一个补偿脚本:当报告生成失败时,自动清理临时目录并记录审计。
  5. 在 systemd 定时任务中加入超时限制(TimeoutStartSec=),并观察超时告警逻辑。