菜单

探测器五个小时什么都没跑,也没有任何东西告诉我们

2026-09-30MCP Checkup

我们的探测器是一个带 cron 触发器的 Cloudflare Worker。平台每五分钟调用它一次;每次调用领取到期的 server(最多 30 台),探测它们,写入结果。所有对到期 server 的领取和探测都从这次调用开始,没有别的东西会启动它们。有人发起即时检查时,探测器也会探测那一台 server,但结果从不签名,不会成为 run,也不会进目录。平台一旦停止调用,目录就没有任何观测。

发生了什么

2026-09-23 11:42 UTC,我们部署了探测器的新版本。平台只调用了它一次,时间是 11:45:47。那次调用领取了 28 台 server,11:47:48 结束。

然后就没有了。11:50 到 16:45 之间,按计划应当有 60 次调用;平台自己的 Worker 调用记录里一次都没有。

16:47:16 我们又部署了一次。那是按计划发布检查套件 0.7.0,和这次空档毫无关系。三分钟后,16:50:58,第一次调用来了。之后探测器清理积压,每一刻钟跑 90 次而不是 30 次,一直到 18:45,19:00 回到平时的节奏。

事情发生时我们一无所知。20:43 UTC,也就是结束将近四小时之后,按小时统计的 run 数显示 12:00 到 15:59 一行都没有。也正是这个空档,让我们那周发布的一篇文章里,当时目录中 1,299 台 server 有 143 台在所用的时间窗里没有 run。

关于原因,我们能说的

  • 不是我们的代码出错。根本没有调用,代码也就无从出错;空档之前的最后一次调用跑完了。
  • 不是数据库卡住。当天没有任务停在半途,数据库自己的定时任务全程按时运行。
  • Cloudflare 的状态页在 9 月 22、23 日没有列出 Workers 或 Cron Triggers 的事故。
  • 空档恰好始于一次部署之后,终于下一次部署之后。

我们最合理的解读是:11:42 那个版本的 cron 触发器没有在平台侧送达,重新部署让它重新登记。这是推断。从我们这边无法再证明更多,所以我们也没有为一个无法确认的原因写绕开它的代码。

我们加了什么

两个信号。状态页 2026-09-23 上线;心跳在第二天,2026-09-24 17:08 UTC 上线。

向外部服务发心跳。 平台每五分钟调用探测器时,探测器先发一次 ping;只有探测周期真正跑完时才发第二次,周期被拒绝或抛错时不发。Healthchecks.io 上的「死人开关」在 ping 停止到达时给我们发邮件。调度器一旦沉默,就意味着 ping 缺失,而 ping 缺失就意味着一封邮件,而不是等某人几小时后恰好读到一个数字。探测器 ping 的地址是 Worker 持有的密钥,不在我们的代码仓库里。

公开的状态页。 /zh/status 显示最近一次探测运行、最近 60 分钟的运行次数,以及到期但还没被领取的 server 数。它只读一个返回单行汇总的数据库函数,不含任何单台 server 的数据。如果 20 分钟内没有任何运行,页面会用一行字说明;只有在没有到期的 server、且最近一小时内有过运行时才不显示。

这两个信号背后有一份简短的书面流程:任一信号报警、而平台最近 15 分钟没有定时调用时,我们用当前版本重新部署一次;五分钟内仍无调用,就向 Cloudflare 开工单,附上调用记录和我们的 run 统计。

心跳不能说明探测得出的结果可靠。它只说明 Worker 被调用了、周期跑完了。结果新不新,要由 /status 从 run 本身读出来。

之后

从 2026-09-24 17:08 UTC 心跳上线,到 2026-09-30,相邻两次 run 开始之间最长隔了 14 分 4 秒,没有一次超过 20 分钟。

我们发布的结果都写明观测时间,所以观测中断本身也应当是读者看得到的。这一次只有事后才看得到;加上这两个信号,是为了下一次在发生时就能看到。

数字从哪里来

调用时间取自 Cloudflare 控制台里该 Worker 的调用记录,于 2026-09-23 读取,范围是 11:40 到 16:55 UTC。run 与任务数是对生产数据库按小时、按一刻钟分桶的只读查询。状态页的核对用的是 Cloudflare 公开的事故接口,同一晚读取。2026-09-24 以来的数字是对心跳部署以后所有非试用 run 开始时间做的一次只读查询。