三个定时任务被删那天,系统一声不吭

2026-09-20 那天早上,我打开邮箱,没有日报。

查了一圈,三个 Windows 定时任务全被删了。不是报错、不是超时,是没有进程运行过------00:00 那条日志根本不存在。

更难受的是:哨兵没报警、告警邮件没发、health.json 一片安静。整套监控都好好的,只是它监控的那台机器上,没有任何东西活着来报告"我挂了"。

那天是靠人发现没收到日报,才发现系统停了。

本机自检有个覆盖不到的洞

原来的体检逻辑长这样:每轮跑完,检查日志、检查哨兵日期、不达标就发告警邮件。

它能证明的是:"我这一轮在跑"。

它证明不了的是:"这一轮根本没有发生"。

这是两件事。任务被删、机器整夜没醒、断电、系统被重装------这些情况下没有任何本机进程能说话。你写在本机上的监控,和要监控的那个东西,会一起消失。

补一个进程去监控另一个进程也没用,它们死在同一台机器上。

方向反过来:让外部等你报平安

改法是把方向调个个儿------本机每轮收尾时,主动向外部 POST 一次"我还活着",外部服务如果超过 26 小时没收到,就告警。

python 复制代码
# deadman_ping.py(节选)
def build_payload(sentinel_path=None, now=None):
    return {
        "sentinel": read_sentinel(sentinel_path),
        "sent_at": (now or datetime.now()).astimezone().isoformat(timespec="seconds"),
        "host": socket.gethostname(),
    }

def ping(url, payload, timeout=10, token=""):
    body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    req = urllib.request.Request(url, data=body, method="POST", headers=headers)
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return resp.status

外部那端用 Cloudflare Worker Cron 或者任意 uptime 心跳服务都行,逻辑就一句:"最后一次心跳超过 26 小时 → 发信"。

成本近零,但覆盖的正好是本机自检的盲区。 单点从"本机自检"变成"本机自检 + 外部反向监控"。

三个不显眼的坑

坑一:不要因为心跳失败就中断主流程。

心跳是旁路。网络抖一下、Worker 部署中、token 过期,都不该让日报发不出去。调用方用 FailSoft 包着,失败只记一行日志,不改退出码。

监控设施反过来把业务搞挂,是最不划算的一种故障。

坑二:读哨兵文件必须去 BOM。

哨兵是 PowerShell 的 Set-Content -Encoding UTF8 写的。PS 5.1 会带 BOM,直接用 utf-8 读出来是 \ufeff2026-10-20------这个脏字符会进入载荷、日志、告警正文。实测在 GBK 控制台还会直接 UnicodeEncodeError,把心跳本身打断。

python 复制代码
with open(path, "r", encoding="utf-8-sig") as f:
    first = (f.read().strip().split() or [""])[0]
return first.lstrip("\ufeff")

坑三(最隐蔽):谁有资格续心跳。

这条是后来加的。系统里除了日报主链,还有个每 6 小时跑一次的 Discover 任务,收尾时也会走同一段代码。如果随便谁都能 ping,那么日链停摆了,Discover 还在每 6 小时替它报平安------26 小时死信告警永远不会触发,监控形同虚设。

所以加了 H3 判据:只有哨兵日期等于今天(日链确实产出过日报),才续这一次心跳。

powershell 复制代码
$deadmanReady = ((($dsv.Trim() -split "\s+")[0]) -eq $todayStr)
if ($cfg.deadman -and $cfg.deadman.ping_url -and $deadmanReady) { ... }

一个心跳如果谁都能发,它就不说明任何事。

最后

这套东西技术上没什么含量,86 行,一个 POST。但它解决的那类问题很特殊:系统不是坏了,是没在跑------而所有"检查系统有没有坏"的手段,都默认了系统正在跑。

以后再设计监控,我会先问一句:如果明天整台机器一个进程都没起来,谁能告诉我?

相关推荐
用户3217576683424 分钟前
ComfyUI AI 漫剧本地量产流水线搭建教程|8G 显存本地部署、分镜生成、角色一致性、视频生成
人工智能
荆棘宠物24 分钟前
大模型 API 流式输出的工程细节:SSE 分帧、中断与重试边界
人工智能
神鹰手哥_起飞版24 分钟前
别让大模型写 HTML:一个省 token 的分工实验
人工智能
泡海椒25 分钟前
jquick-pdf 热力图(HEATMAP)实战:24 格营收矩阵嵌入评级报告的模板逐行拆解
后端
AI日报派送佬25 分钟前
高效目标检测利器:X-AnyLabeling 加载自定义 YOLO 模型自动标注实操指南
人工智能·yolo·目标检测
对象存储与RustFS27 分钟前
Velero 接 RustFS 实操:K8s 集群备份落到自建对象存储
后端·docker·云原生
猛码象限29 分钟前
【实践随笔】我们在用 AI,还是 AI 在用我们?
人工智能
leo在掘金30 分钟前
2.8万亿MoE跑昇腾:0 Day适配改了啥
后端·架构
小兔子31 分钟前
proxy_pass 末尾写不写路径,是两套规则:上线后接口路径莫名多了一段
后端