事件日期:2026-10-07 | 类型:第三方凭据刷新失败 | 影响:小程序登录 | 状态:已恢复并完成根因定位
Engineering Case StudyRCAPostmortemKubernetes
脱敏说明:本文用于对外技术复盘。真实节点名、Pod 名、出口 IP、AppId、商户信息均已替换为示例值,不改变技术架构、错误码、时间关系与根因判断。
结论先行: 集群扩容后新增工作节点产生了新的公网出口 IP,但该 IP 未同步加入微信开放平台白名单。新增节点上的商户服务实例抢到分布式锁并执行 component_access_token 刷新时,被微信以 61004 access clientip is not registered 拒绝;旧 token 到期后,所有依赖该共享凭据的小程序登录请求开始返回 42001 access_token expired。下一小时由白名单内节点刷新成功,系统自动恢复。
1. 背景与问题
系统以微信第三方平台服务商模式承载多个小程序。用户登录时,业务服务并不是直接使用每个小程序的 AppSecret,而是携带第三方平台的 component_access_token 调用微信 jscode2session 接口。
2026-10-07 13:21,用户在小程序登录页看到如下提示:
微信登录失败:access_token expired
errcode: 42001
问题随后自行恢复。由于故障具有"突然出现、跨实例发生、又自动恢复"的特征,初看容易被误判为微信临时波动或单个缓存过期。
2. 复盘目标与约束
- 确认报错中的 token 类型,区分用户登录 token、授权方 token 与第三方平台 token。
- 还原从集群调度、定时刷新到微信登录失败的完整因果链。
- 解释为什么问题会自动恢复,以及为什么只在部分时间窗口集中爆发。
- 所有排查只读取 Kubernetes 日志和资源状态,不重启 Pod、不修改生产配置、不重放用户请求。
3. 系统架构与影响边界

图 1:业务登录链路与第三方平台凭据刷新链路
业务登录链路和 token 刷新链路通过同一个共享凭据耦合。jycy-user 横向扩容为多个 Pod,但它们最终读取的是同一个平台 token,因此 token 一旦过期,错误会跨 Pod 扩散,而不会局限于某一台业务实例。
4. 核心设计与凭据状态模型
4.1 各存储与组件职责
| 组件/数据 | 职责 | 生命周期 | 本次风险 |
|---|---|---|---|
| Redis:component token | 提供登录请求所需的快速读取 | 按微信 expires_in 提前约 5 分钟过期 | 缓存失效后会回源数据库 |
| 数据库:微信授权信息 | 持久化最新一次成功刷新的 token | 当前未与有效期形成强绑定 | 可能保存并返回已经过期的 token |
| Redis 分布式锁 | 多个 merchant Pod 中只允许一个实例刷新 | 每次整点任务竞争 | 执行节点随机,受节点出口 IP 影响 |
| 微信开放平台白名单 | 限制第三方平台 token 刷新来源 | 需随出口网络变化维护 | 新增节点出口 IP 遗漏 |
逻辑关联关系如下:
小程序 appId → 平台路由(A/B) → componentAppId → component_access_token(Redis 优先,DB 兜底) → 微信 jscode2session
本次事件发生在 A 平台。B 平台同一时段刷新成功,因此故障并非微信整体不可用,也不是所有服务商平台同时失效。
5. 数据流与执行流程
- 每个 merchant Pod 在整点触发 token 刷新任务。
- 所有实例竞争同一个 Redis 分布式锁,抢锁成功者调用微信 Token API。
- 刷新成功后,新 token 写入 Redis,并更新数据库持久化值。
- 用户登录时,user 服务通过 Feign 获取小程序所属平台及当前 token。
- user 服务携带该 token 调用微信
jscode2session。 - token 过期时,微信返回 42001,后端转换为业务异常,前端显示登录失败。

图 3:从整点刷新失败到用户收到 42001 的完整时序
6. Troubleshooting:排查过程
6.1 Symptoms
- 13:21 前端提示
access_token expired。 - 三个 user Pod 在同一分钟内均出现
42001,排除单 Pod 本地状态异常。 - 故障持续约一小时后自行恢复。
6.2 Investigation
第一步:跨 Pod 搜索登录错误。 使用只读 Kubernetes 日志定位工具,在三个 user Pod 中搜索 access_token expired、42001,确认问题分布在全部业务实例。
| 观察项 | 结果 | 判断 |
|---|---|---|
| 13:00--13:59 错误量 | 约 285 条 | 不是单用户或单请求问题 |
| 受影响 Pod | 3/3 个 user Pod | 问题来自共享依赖 |
| 最后一条 42001 | 约 13:59:41 | 与 14:00 定时刷新高度相关 |
| 09:00--12:59 | 没有同类错误 | 业务代码并非持续不可用 |
第二步:回溯 merchant 刷新日志。 在 13:00 找到微信直接返回的真实错误:
{"errcode":61004,
"errmsg":"access clientip is not registered requestIP: 203.0.113.19"}
负责刷新的 Pod 连续重试 5 次均失败。该 Pod 位于新增工作节点 worker-new-04,节点出口 IP 与微信报文中的 requestIP 一致。
第三步:建立节点相关性。 历史日志显示,该新增节点在 05、06、07、08、12、13 点抢到刷新锁时全部失败;在 09、10、11、14 点未抢到锁。错误窗口与锁执行者完全对应。

图 2:刷新失败、旧 token 到期与自动恢复之间的时间关系
6.3 Root Cause
**根因:**集群扩容新增工作节点后,新的公网出口 IP 未加入微信开放平台白名单。由于刷新任务由所有 merchant Pod 同时触发并随机竞争分布式锁,当新增节点上的实例成为执行者时,微信拒绝刷新请求,导致共享 token 最终过期。

图 4:直接原因、促成因素和故障放大机制

图 5:从用户症状追踪到集群变更流程缺口
6.4 Resolution
- 将新增节点实际使用的公网出口 IP 补充到微信开放平台白名单。
- 核对集群内所有可能出口地址,而不是只登记最初的节点 IP。
- 在下一次刷新完成后,确认 Redis 中 token 已更新,登录错误停止增长。
6.5 Verification
| 验证项 | 预期 | 实际结果 |
|---|---|---|
| 整点刷新任务 | 不再出现 61004 | 14:00 正常节点刷新成功 |
| 登录错误 | 42001 停止增长 | 13:59:41 后未再出现 |
| 多 Pod 登录 | 所有实例均可成功登录 | 业务恢复正常 |
| B 平台 | 不受 A 平台故障影响 | 刷新日志持续成功 |
6.6 Lessons Learned
- **错误表象不等于根因:**42001 表示当前 token 已过期,但真正导致过期的是 61004 白名单拒绝。
- **分布式锁只保证单执行,不保证正确执行:**执行者所在节点的网络身份也是业务前置条件。
- **配置错误不适合普通重试:**同一出口 IP 连续重试不会改变结果,只会延迟失败。
- **持久化旧 token 不能等同于可用 token:**数据库兜底必须同时校验 expiresAt。
- **扩容是外部依赖变更:**新增节点、NAT、EIP 都可能改变第三方看到的来源 IP。
7. 关键实现与方案取舍
7.1 当前刷新逻辑的优点
- 使用分布式锁,避免多个实例同时向微信刷新 token。
- 按微信返回的有效期提前过期 Redis 缓存,正常情况下能留出刷新余量。
- 刷新成功后同时更新缓存和数据库,重启后仍可恢复运行凭据。
7.2 当前设计的风险
- 每个 Pod 都运行定时任务,执行节点具有随机性。
- 刷新失败只记录日志,没有阻止过期 token 被继续返回。
- Redis 失效后从数据库读取旧值,并重新缓存固定时长,会放大故障窗口。
- 登录侧收到 42001 后没有触发受控刷新与单次重试。
不建议通过延长 token 缓存时间解决。微信服务端掌握真实有效期,本地延长 TTL 只会让过期凭据停留更久。
8. 后续优化方案

图 6:固定出口、可靠刷新、错误自愈和监控告警的目标架构
8.1 P0:立即实施
- 统一梳理并登记当前所有生产出口 IP。
- 为
61004、连续刷新失败和 token 剩余有效期不足建立告警。 - 把"第三方白名单核对"加入节点扩容、集群迁移、NAT 变更检查单。
8.2 P1:短期改造
- 数据库同时保存
token、expiresAt、refreshAt和最后刷新结果;过期值不得回填 Redis。 - 收到 42001、40014 等明确凭据错误时,删除缓存并触发一次受锁保护的强制刷新;刷新成功后只重试原请求一次。
- 锁竞争失败记录为 INFO,而不是 ERROR;真正刷新失败才触发告警。
8.3 P2:长期治理
- 所有访问微信的工作负载统一经过 NAT 网关和固定 EIP。
- 将 token 刷新从普通业务 Pod 中拆出为专用 CronJob、调度中心任务或具备稳定出口的凭据服务。
- 建立"凭据剩余寿命、刷新成功率、微信错误码分布、登录成功率"的可观测面板。
9. 可复现与验证清单
在测试环境复现时,不需要等待 token 自然过期,可以使用受控方式验证:
- 准备两个具备不同出口 IP 的测试执行节点,其中一个不在测试平台白名单。
- 让未登记节点执行刷新任务,确认微信返回 61004。
- 确认系统不会把数据库中的过期 token 重新写入 Redis。
- 让白名单内节点刷新,确认 Redis、数据库和 expiresAt 同时更新。
- 构造 42001,验证登录侧只触发一次强制刷新和一次重试,不发生重试风暴。
- 核对告警是否包含平台、错误码、执行节点、出口 IP 和失败次数,但不输出 token 明文。
10. 总结
这次故障不是单纯的"token 过期",而是一次由集群扩容、出口 IP 白名单、分布式任务随机执行、旧凭据回填共同导致的系统性问题。自动恢复并不代表风险消失;只要未登记节点再次抢到刷新锁,故障就会周期性复发。
**最终治理方向:**用固定出口消除节点差异,用带有效期的凭据状态模型杜绝过期值回填,用受控强制刷新提升自愈能力,并把第三方白名单纳入基础设施变更流程。