使用心跳进行 Cron 任务监控:实用教程
成功运行的 Cron 任务很容易处理。大声失败的 Cron 任务也容易处理:错误邮件会出现在你的收件箱里。难处理的是那些完全停止运行的 Cron 任务。服务器重启、容器被驱逐、用户账户被禁用、systemd 定时器被配置变更屏蔽。任务从未运行,没有产生任何错误,而你三周后才发现——你以为一直在进行的备份其实已经没了。心跳监控正是为这种失败模式而生。
心跳监控如何工作
心跳监控颠倒了通常的探测关系。不再是外部探针调用你的服务,而是你的任务调用监控。你定义一个宽限窗口(例如「这个任务应每小时 ping 一次,给它 5 分钟的弹性」)。当监控停止收到 ping 时,它会打开一个宕机事件并向你发出告警。
结果就是一个 dead-man 开关。只要任务运行并报告成功,监控就保持沉默。一旦它未能 ping(因为崩溃、服务器关机、有人禁用了定时器),告警就会触发。原本三周的静默备份失败变成了五分钟的事件。
心跳与常规检查的使用场景
心跳并不是 HTTP 监控的替代品。它们覆盖的是另一类任务:任何按计划在带外运行的任务。
- 备份任务:夜间转储、S3 同步、异地 rsync。
- 数据管道运行:ETL 任务、每日报表生成、账单对账。
- 按计划发送的营销邮件:每周通讯、滴灌式营销活动。
- SSL 续期任务:certbot、acme.sh。
- 任何在 cron、systemd 定时器、GitHub Actions 计划、Cloudflare Workers cron 或 AWS EventBridge 中运行的任务。
如何为 bash cron 任务添加心跳
最简单的模式是在脚本末尾调用一次 curl。心跳 URL 对每个监控来说都是唯一的;请勿将其提交到版本控制中。
一种能优雅处理失败的模式:`do_the_work && curl -fsS --retry 3 https://your-monitor.example/heartbeat`。&& 仅在实际工作成功时才触发心跳。--retry 3 处理瞬时网络抖动。-fsS 让 curl 在成功时保持安静、在失败时发出噪声,这正是 cron 所期望的。
在 Python 和 Node 中使用心跳
同样的模式在两种常见语言中的写法。两者都用 try/except 包裹任务,只有在干净运行后才报告成功。
- Python:`try: do_the_work(); requests.post('https://your-monitor.example/heartbeat', timeout=10); except Exception: logger.exception('job failed')`
- Node:`try { await doTheWork(); await fetch('https://your-monitor.example/heartbeat', { method: 'POST' }); } catch (err) { console.error('job failed', err); }`
- 重要提示:不要在 finally 块中发送心跳。Finally 会在成功和失败时都执行。你希望心跳是积极信号,而不是「我跑过了」的信号。
设置宽限窗口
宽限窗口是最容易设置错误的参数。设得太短,任务晚几秒钟运行就会产生误报。设得太长,真正的故障可能数小时都未被察觉。
一个不错的经验法则是:正常任务时长的两倍加 5 分钟。一个通常需要 8 分钟的夜间备份,应设置 8 * 2 + 5 = 21 分钟的宽限窗口。一个需要 2 小时的每周任务,应设置 2 * 2 + 0.5 = 4.5 小时的宽限窗口。宽限窗口保护你免受无害的波动影响,而不是真正的故障。
建议优先设置的常见心跳模式
如果你从零开始,有三个心跳能捕获大部分让人头疼的静默故障。一个每晚备份监控,宽限窗口为 6 小时。一个 SSL 续期监控,对应运行 certbot 或 acme.sh 的 cron 任务,宽限窗口为一周。一个每日报表或账单对账监控,宽限窗口为 90 分钟。这三个监控共同覆盖了那些未被察觉时间最长、后果最严重的故障模式。一旦它们就位,再逐步为每一个其他计划任务添加心跳。