自动化:定时任务与无人值守流程
把工作流从「我点一下」变成「它自己跑」,收益是稳定的产出,代价是失败时没人立刻知道。所以自动化的顺序一定是:先手动跑顺 → 再脚本化 → 再挂定时 → 最后才谈无人值守。跳过任何一步,你得到的都只是一个安静出错的定时任务。
什么时候该上定时任务
| 特征 | 适合定时 | 不适合 |
|---|---|---|
| 触发条件 | 时间驱动,如每天/每周固定时刻 | 事件驱动,应该用消息或钩子 |
| 结果用途 | 生成报告、同步数据、清理与归档 | 需要即时判断的线上决策 |
| 失败影响 | 可延后一次重跑,不阻塞业务 | 失败会阻断交易链路 |
| 结果校验 | 有明确产出可检查 | 只有「感觉跑过了」 |
| 人工介入 | 事后看报告即可 | 每步都要拍板 |
三步走:手动 → 脚本 → 定时
先手动跑通至少三次,把每次卡住的地方记下来,再决定哪里需要人工兜底。
阶段一 手动:你在对话里让 AI 跑完整个流程,记录每一步的输入与输出;
阶段二 脚本:把已经稳定的步骤写成脚本,脚本必须自己会失败(非零退出码)、会打日志;
阶段三 定时:脚本连续手动跑一周没问题后,挂到定时任务上;
阶段四 无人值守:告警通道接通、幂等确认无误后,才允许无人值守。
一个靠谱的骨架脚本:有日志、有退出码、有失败告警。
#!/usr/bin/env bash
set -euo pipefail
TASK="weekly-doc-check"
LOG_DIR="/var/log/${TASK}"
mkdir -p "$LOG_DIR"
LOG="${LOG_DIR}/$(date +%Y%m%d-%H%M%S).log"
alert() {
# 失败告警:把摘要发到你的告警通道,详情留在日志里
curl -fsS -X POST "${ALERT_WEBHOOK:?未配置告警地址}" \
-H 'Content-Type: application/json' \
-d "{\"task\":\"${TASK}\",\"status\":\"failed\",\"step\":\"${1}\",\"log\":\"${LOG}\"}" \
>/dev/null 2>&1 || echo "告警发送失败" >&2
}
main() {
echo "[$(date '+%F %T')] start ${TASK}"
./step1_fetch.sh || { alert step1; exit 1; }
./step2_process.sh || { alert step2; exit 1; }
./step3_report.sh || { alert step3; exit 1; }
echo "[$(date '+%F %T')] done"
}
main >>"$LOG" 2>&1 || { alert main; exit 1; }
挂到定时任务上,注意用绝对路径、显式设置工作目录,并注意环境变量在定时任务里不会自动继承:
# crontab -e
# 每周一 07:30 跑一次;PATH 与密钥必须在任务里显式给出
30 7 * * 1 cd /opt/tasks/weekly-doc-check && \
PATH=/usr/local/bin:/usr/bin:/bin \
ALERT_WEBHOOK=http://127.0.0.1:9000/hooks/task-alert \
/bin/bash run.sh
# 系统级计划任务(宿主机层面)等价写法
# schtasks /create /tn "weekly-doc-check" /tr "D:\tasks\run.cmd" /sc weekly /d MON /st 07:30
幂等:重复执行不能把数据搞坏
定时任务最常见的故障不是「没跑」,而是「跑了两次」。幂等的意思是:同一天跑一次和跑三次,最终结果一样。
| 手段 | 做法 | 适用 |
|---|---|---|
| 状态文件 | 记录已完成的任务标记,跑过就跳过 | 单机、小规模 |
| 加锁 | 用锁保证同一时刻只有一个实例 | 防止任务重叠 |
| 唯一键覆盖 | 写入用「主键或唯一键」覆盖而非追加 | 报表、汇总表 |
| 时间窗口过滤 | 只处理上次成功时间之后的数据 | 增量同步 |
# 用文件锁防止上一次还没跑完就叠加一次
exec 9>"/var/lock/${TASK}.lock"
flock -n 9 || { echo "上一次仍在运行,本次跳过"; exit 0; }
# 状态文件:今天已成功就不再重复处理
STATE="/var/lib/${TASK}/last-success-date"
TODAY="$(date +%F)"
[ "$(cat "$STATE" 2>/dev/null)" = "$TODAY" ] && { echo "今日已完成,跳过"; exit 0; }
# ……业务逻辑执行成功后……
echo "$TODAY" > "$STATE"
状态文件要记录「上次成功到哪一步」而不只是「今天跑没跑」,否则中途失败后重跑会漏掉或重复处理数据。
失败告警:先保证有人看得见
| 告警级别 | 触发条件 | 通道 | 期望响应 |
|---|---|---|---|
| 提示 | 任务成功但结果为空或异常偏少 | 汇总消息 | 次日看一眼 |
| 警告 | 单步失败,自动重试后成功 | 汇总消息 | 当天关注趋势 |
| 严重 | 连续 2 次失败、或关键产出缺失 | 即时通知 | 当天处理 |
| 紧急 | 影响了对外交付或下游依赖 | 即时通知 + 电话/值班 | 立即处理 |
告警内容要能自解释,否则等于没告警:
任务名:weekly-doc-check
状态:严重,连续 2 次失败
失败步骤:step2_process.sh
错误摘要:输入目录为空,期望至少 1 个文件,实际 0 个
已尝试:重试 2 次,均在同一位置失败
日志路径:/var/log/weekly-doc-check/20250310-073001.log
建议动作:确认上游导出任务是否正常;确认目录挂载是否丢失
无人值守前,先有人值守跑一周
| 观察项 | 一周内要确认的事 |
|---|---|
| 输入稳定性 | 输入目录真的有数据吗?周末和节假日会不会为空? |
| 耗时波动 | 最长一次跑了多久?有没有和别的任务撞在同一个时间点? |
| 失败模式 | 失败了几次、都失败在同一步吗、是否已经修好 |
| 产物质量 | 自动产出的报告有人看吗?有没有明显错误? |
| 告警有效性 | 告警发出去了吗?收到的人能看懂吗? |
| 人工兜底 | 任务失败后,谁在多久内能重跑一次 |
一周里如果有人每天都要手动补一次,说明流程还没到能无人值守的程度。
常见坑
| 坑 | 后果 | 规避 |
|---|---|---|
| 直接上线无人值守 | 失败没人知道,几天后才发现 | 先有人值守跑一周 |
| 任务无锁、耗时超过周期 | 两个实例同时改同一份数据 | flock + 检查耗时 |
| 定时任务里没有 PATH 与密钥 | 手动能跑、定时报错 | 在任务里显式声明环境 |
| 输出只写「成功」 | 无法判断是否真的处理了数据 | 打印处理行数、产出文件、耗时 |
| 只告警失败,不告警「空结果」 | 静默产出空报告 | 空结果也算异常,按提示级告警 |
| 任务失败后无限重试 | 把下游打崩 | 有限重试 + 退避 + 转人工 |
小结:定时任务的难点从来不是 cron 的五个字段,而是幂等与告警;先手动跑顺三次、再脚本化、再挂定时,连续有人值守跑一周没出现需要人工补跑的情况,才谈无人值守——失败必须有人看得见,重跑必须不产生副作用。