脚本如何追踪定时任务状态

wen 实用脚本 28

本文目录导读:

脚本如何追踪定时任务状态

  1. 核心方法:日志 + 标记位(最通用)
  2. 利用流程控制工具(推荐生产环境)
  3. 分布式/跨机器场景:心跳 + 外部存储
  4. 特殊情况处理
  5. 总结:我应该选哪种?

追踪定时任务状态通常需要结合任务本身的特性(是否有返回值、是否独立进程)以及监控系统,以下是几种主流且实用的脚本追踪方案:

核心方法:日志 + 标记位(最通用)

这是最基础、最可靠的方案,适用于任何语言编写的脚本。

原理: 在脚本的关键节点(开始、成功、失败、异常)写入带有时间戳的日志,并更新一个状态文件或数据库记录。

脚本示例 (Shell/Bash):

#!/bin/bash
TASK_NAME="daily_report"
STATUS_DIR="/var/log/task_status/"
PID_FILE="/var/run/${TASK_NAME}.pid"
LOG_FILE="/var/log/${TASK_NAME}.log"
# 1. 记录开始状态
echo "[$(date '+%Y-%m-%d %H:%M:%S')] TASK_STARTED" >> "$LOG_FILE"
echo "RUNNING" > "${STATUS_DIR}${TASK_NAME}.status"
echo $$ > "$PID_FILE"  # 记录进程ID,方便 kill
# 2. 执行实际业务逻辑
python3 /app/scripts/generate_report.py
# 假设 $? 获取退出码
EXIT_CODE=$?
# 3. 更新最终状态
if [ $EXIT_CODE -eq 0 ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] TASK_SUCCESS" >> "$LOG_FILE"
    echo "SUCCESS" > "${STATUS_DIR}${TASK_NAME}.status"
else
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] TASK_FAILED (code: $EXIT_CODE)" >> "$LOG_FILE"
    echo "FAILED" > "${STATUS_DIR}${TASK_NAME}.status"
fi
# 清理PID文件
rm -f "$PID_FILE"

追踪方式:

  • 手动查看: cat /var/log/task_status/daily_report.status
  • 监控脚本: 另一个守护进程定期读取 .status 文件,如果超过预期执行时间且状态为 RUNNING,则报警。
  • 日志聚合:LOG_FILE 发送到 ELK (Elasticsearch, Logstash, Kibana)、Splunk 等。

利用流程控制工具(推荐生产环境)

对于复杂的任务依赖、重试、超时管理,不要自己写脚本追踪,而是用成熟的工具。

A. 使用 Systemd Timer (Linux)

Systemd 自带状态追踪,非常适合系统级定时任务。

示例: report.service (任务单元)

[Unit]
Description=Daily Report Generator
[Service]
Type=oneshot
ExecStart=/usr/local/bin/generate-report.sh
# 标准输出/错误记录到 journal
StandardOutput=journal
StandardError=journal

示例: report.timer (定时触发器)

[Unit]
Description=Run daily report at 2am
[Timer]
OnCalendar=daily
Persistent=true  # 如果错过执行时间,立即补跑
[Install]
WantedBy=timers.target

追踪命令:

# 查看所有 timer 状态及最近运行时间
systemctl list-timers --all
# 查看特定服务的最后几次运行日志
journalctl -u report.service --since "1 hour ago"

优点: 无需额外代码,systemctl 直接提供 ActiveLast triggerNext trigger 状态。

B. 使用工作流引擎 (Airflow, Prefect, Dagster)

适用于几十上百个任务,需要依赖关系和重试逻辑的场景。

追踪方式: 这些工具自带 Web UI,显示任务的 运行中成功失败重试中已跳过 等状态,并提供详细的日志链路,脚本本身只需返回 0/非0 退出码即可。

分布式/跨机器场景:心跳 + 外部存储

当脚本运行在不同的服务器上,需要统一监控时,需要将状态写入共享存储。

方案: 脚本周期性地向 Redis数据库 写入“心跳”记录。

示例 (Python 脚本):

import redis, os, time
r = redis.Redis(host='monitor_host', db=0)
task_id = "data_cleanup"
def update_status(status, msg=""):
    # 使用 Hash 存储结构化状态
    r.hset(f"task:{task_id}", mapping={
        "status": status,  # running, success, failed
        "message": msg,
        "pid": os.getpid(),
        "timestamp": time.time(),
        "host": os.uname().nodename
    })
    # 设置过期时间,防止僵尸记录
    r.expire(f"task:{task_id}", 3600)
try:
    update_status("running", "开始处理数据...")
    # 模拟业务逻辑
    time.sleep(30)
    # 可以在循环中更新进度:r.hset(f"task:{task_id}", "progress", "50%")
    update_status("success", "任务完成")
except Exception as e:
    update_status("failed", str(e))

外部监控脚本 (使用另一个脚本定期检查):

import redis
r = redis.Redis(...)
task_info = r.hgetall("task:data_cleanup")
if task_info.get(b'status') == b'running' and (time.time() - float(task_info[b'timestamp'])) > 600:
    alert("任务 data_cleanup 已运行超过10分钟,可能卡死!")

特殊情况处理

情况1:任务被直接 Kill 或系统重启

问题: 脚本来不及更新状态文件。 解决方案:

  • 使用 Systemd:它会自动追踪服务状态,被 Kill 后会显示 failed
  • 使用 PID 文件 + 计数器:如果监控脚本发现 PID 文件存在但进程不存在,则认为任务异常中断。

情况2:脚本是后台长期运行的 Daemon(而非一次性的 Cron Job)

方案: 上述的心跳机制最适合,单独写一个健康检查端点(如 HTTP API),返回 {"status": "healthy", "last_heartbeat": "...}"

我应该选哪种?

你的场景 推荐方案
1-2个简单脚本,在单个 Linux 服务器上 日志 + 状态文件 (Shell 脚本)
多个系统级任务,希望零代码管理 Systemd Timer
任务有依赖链、失败重试、UI 监控 Airflow / Prefect
脚本运行在多个服务器,需要统一监控 Redis 心跳 (Python/Go)
只需知道“有没有跑完” 退出码 $? + 外部监控定时检查最后一次执行时间

最后的重要提醒:

  • 永远不要只靠脚本定时任务,一定要在任务内部埋点。
  • 幂等性:确保你的脚本被重复执行不会导致数据错误(即使状态追踪失败,重跑也要安全)。
  • 报警要即时:建议在脚本异常时直接发邮件/飞书/钉钉/Datadog,不要只写日志等别人看。

抱歉,评论功能暂时关闭!