NEW

免费试用已开放

立即开始

API 网关 · 长连接

企业微信掉线与自愈怎么设计

更新于 2026-08-169 分钟

掉线本身不产生损失,产生损失的是三段时间:从掉线到你知道、从你知道到有人开始处理、从账号恢复到数据补齐。多数团队只优化了第一段 —— 加一块面板,在线率飘红。第二段和第三段才是真正吃掉可用性的地方,而且它们通常没有 owner,复盘时也很少被单独拿出来算。这篇按这三段时间拆检测、告警、恢复和补偿四件事,重点在后两件;账号托管一侧按 wecomapi 的形态来说。

把掉线拆成三段时间,不是三种状态

状态怎么建模是另一个话题,站内讲协议登录那篇已经拆过。这里换一把尺子:T1 是掉线到系统知道,T2 是系统知道到有人动手,T3 是账号恢复到数据补齐。三段的优化手段完全不同,混在一起谈「稳定性」得不出任何可执行的结论。

  • T1 靠检测信号的组合。压到分钟级不难,继续往秒级压,边际收益很快就没了。
  • T2 靠告警设计和归属人。最容易被忽略,也最容易做到 —— 这一段几乎不需要写代码。
  • T3 靠补偿。最难,而且能不能做出来,取决于你在掉线之前有没有埋好水位。

一个常见的错觉是 T1 越小越好。真实情况是,T1 从十分钟压到一分钟带来的收益,通常远不如把 T2 从两小时压到十分钟 —— 前者是技术活,后者是流程活,而后者的分子大得多。资源有限的时候,先修 T2。

这三个数还有一个共同点:都能被测出来,而且测法很便宜。在测试环境把一个实例强制下线,掐表记三个数就行。没做过这个演练的团队,报出来的可用性都是估的 —— 而估出来的数在故障复盘会上一句话就会被推翻。

检测:三个信号,用途不能混

三个信号都要有,但它们的时延、误报率和能发现的问题类型完全不同,拿其中一个当全部就会留下盲区。

  1. 1主动探活:定期查实例状态。时延可控,是状态的权威来源,但探活成功只说明这一刻连得上,不说明业务链路是通的。
  2. 2出向调用失败:真实业务调用返回的错误。最准,但它是被动的 —— 一个只在工作日发消息的账号,周末掉了不会有人知道。
  3. 3事件静默:一段时间内没收到任何入站事件。它能抓住前两个都抓不到的一类问题 —— 状态显示在线、调用也不报错,但事件就是不来了。代价是需要一条按小时、按星期几区分的基线,否则夜里必然误报。

关键的一步是把三个信号合成一个状态,而不是让它们各自维护一份判断。做法是让每个信号只去修改同一条共享的状态记录:wecomapi 的实例状态作为探活的权威来源,出向失败与事件静默只在这条记录上补充原因和时间戳。记录里至少要有最后一次成功接收事件的时间、最后一次成功出向调用的时间,以及当前状态的原因。

写同一条记录还有个附带好处:任何一个信号误报时,另外两个的时间戳会立刻把它证伪 —— 事件静默报警但十秒前刚发送成功,那就是基线的问题,不是账号的问题。有了互证,你不需要为每个信号单独把阈值调得很保守,而阈值一保守,T1 就跟着变长。

状态变更一定要存原因和时间戳,别只存一个枚举值。复盘的时候,「什么时候掉的、上一次成功是什么时候」比「现在是什么状态」有用得多,而前者事后补不出来。

告警:能自动恢复的不许打扰人

分级只需要两档,因为动作只有两种:程序能自己解决的,和必须有人做点什么的。第三档「观察级」等于没有,它唯一的作用是训练值班的人学会忽略告警。

判断哪一档不看严重程度,看解决动作里有没有人。企业微信登录态失效需要重新扫码,这类重试一万次也不会好,必须立刻停止重试并升级到人;瞬时抖动和短时不可用则相反,退避探活就能过去,期间发出的任何通知都是噪声。两类混在一起的直接后果,就是面板全绿、重试还在跑、客户已经等了两小时。

真正需要设计的是抑制与合并,这部分决定了告警会不会被人看。三条规矩:

  • 同一个实例在一次故障里只发一条,恢复时补一条收尾。不做抑制的系统,一次掉线能刷出几十条,第二次就没人看了。
  • 多个实例同时掉线要合并成一条,并且升一级处理。多号同时掉,绝大多数不是账号的问题,是你这边的共因 —— 出口网络、一次发版、一次配置或凭证变更。合并之后这个特征才看得出来,逐条发出去只会把它盖住。
  • 告警正文必须带三样:哪个实例、归属人、下一步动作。缺任何一样,收到的人第一反应都是转发给别人,T2 就耗在这一轮轮转发里。

抑制做完还剩最后一件事:给告警链路本身加心跳。告警系统坏掉的时候是静默的,你不会收到一条「告警发不出去了」的告警,而那段时间在面板上和「一切正常」长得一模一样。定期发一条已知的合成告警走完整条链路,是唯一能发现这个问题的办法,成本是每天几条消息。

恢复:探活成功不等于可以放量

恢复的那一瞬间是第二次故障的高发期,原因不复杂:积压的任务在同一秒被放出来,内容集中、目标集中、速率出现尖峰,很容易再撞上限制,然后又是一轮异常和积压。把恢复写成一次布尔翻转,等于把这个尖峰做成了默认行为。

稳妥的做法是给恢复加一个半开阶段,借断路器那套思路:探活成功之后先不放开出向,只允许低风险动作试探一小批,观察一个窗口没问题,再逐步放量。放量曲线要压在正常速率之下,因为你还要顺带消化积压 —— 恢复期的总量本来就比平时高,速率再拉满就是自找第二次。

低风险动作怎么挑:优先选只读的,或者对同一个内部对象重复执行也无副作用的动作,别拿真实客户的消息去做探针。探针发错一条,你换回来的信息是「链路通了」,赔进去的是一次没法撤回的打扰,这笔交换不划算。

示意:恢复是三段,不是一次布尔翻转typescript
// 示意代码,状态语义与可订阅事件类型以线上文档为准

async function onProbeOk(id: string) {
  setPhase(id, "half-open");
  const ok = await trySmallBatch(id, { limit: 5 });  // 只发少量低风险动作
  if (!ok) return backoff(id);

  await observe(id, { minutes: 5 });                 // 观察窗口,别跳过
  setPhase(id, "online");
  drainBacklog(id, { rate: normalRate * 0.5 });      // 放量曲线低于正常值
}

// 状态变更由 wecomapi 的事件回调送进来,先快速 ACK,再驱动上面这段

另一件必须做在恢复路径上的事:把「恢复」本身也记成一次事件,带上掉线时长和恢复方式(自动探活恢复,还是人工重新扫码)。这几个数攒一个月,你才能说出自己的自愈率到底是多少、T2 的真实中位数是几分钟,而不是凭印象说「最近还挺稳」。没有这组数,之后所有关于要不要投入的讨论都只能靠拍脑袋。

补偿:出站按时效丢,入站按水位补

出站相对好办:掉线期间的任务按实例挂起、设过期时间,强时效的过期即丢弃并记录,幂等的恢复后重放。真正的坑在入站。

入站的问题是空洞不会自己叫。掉线期间对方发来的消息、群成员变更这些事件,你的系统没有收到,事后也不会有一条「你漏了三条」的通知。所以补偿只能由你自己发起,而它依赖一样东西:掉线之前有没有在记「最后一次成功接收事件的时间」。用 wecomapi 的事件回调接入时,这个水位可以在 ACK 之后顺手写下,成本几乎为零;没埋的话,事后你连要补哪一段都说不出来。

  1. 1以水位为起点、恢复时间为终点划出空洞区间,并在两端各留一段重叠窗口 —— 边界上的事件最容易两头都不算。
  2. 2对区间内受影响的会话主动回拉一次增量做对账,能补的补上。补偿写入必须幂等,否则一次重跑就会把用户的时间线搞成双份。
  3. 3对账范围要限死:只补受影响的实例、只补空洞区间内有过活动的会话。全库对账在实例数上去之后跑不完,而跑不完的对账等于没有对账。
  4. 4补不齐的部分不要假装完整。在会话上打一个「这段时间可能有缺失」的标记,让接手的人知道要多问一句。系统承认自己不确定,比它自信地给出一份不完整的记录安全得多。
  5. 5给补偿本身设预算上限。一次长时间掉线会产生大量补偿请求,不限速就会在恢复的同时把自己再压一次,这类二次事故通常比原始故障更难向业务解释。

最后一句判断:不要把系统建在「事件会自动补推」这个假设上。任何投递机制都不会为长时间中断后的完整回补背书,你自己的对账能力才是那条兜底线。这条兜底线的建设成本很低,但它必须在第一次长时间掉线之前就存在 —— 事后再补,缺的那段数据是补不回来的。

本文讲的是时间维度上的设计取舍,不涉及具体接口定义。实例状态取值、可订阅事件与精确字段以 wecomapi 线上文档为准,示意代码只表达阶段关系。

常见问题

掉线之后自动重连就行了吗?
分两类。瞬时抖动和短时不可用可以靠有上限的退避探活扛过去;需要重新扫码的那一类,重试再多次也不会好,只会污染错误率指标,还让真正的异常淹没在噪声里。判据只有一条:解决这个异常的动作里有没有人。有人,就立刻停止重试并升级到人,重试本身不是恢复。
怎么区分是账号掉了,还是我们自己的问题?
看是单号还是多号。单个实例异常、其他实例正常,先按账号侧处理;多个实例同时异常,几乎都是你这边的共因,优先查出口网络、最近一次发版、配置或凭证变更。所以告警一定要能按实例聚合,逐条独立发出去会把这个特征彻底盖住。
掉线期间漏掉的事件,平台会补推吗?
不要把设计建立在补推承诺上。工程上正确的做法是自己埋水位、恢复后主动回拉增量对账,并且允许结果不完整、把不完整明确标出来。具体的投递语义与可订阅事件类型以 wecomapi 文档为准,不同能力的行为也可能不一致,别用一个能力的观察去推断另一个。

准备好动手了?

精确字段、鉴权与端点以线上文档为准;可在控制台创建密钥后联调。

相关文章