系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

一个 Consul 集群、两个低层缺陷,让 Roblox 在 2021 年 10 月停机 73 小时。 本文是公开报告的摘要与解读,事实以原始报告为准

本文是公开报告的摘要与解读,事实以原始报告为准。

事件概要

2021 年 10 月 28 日至 31 日,Roblox 经历了一次 73 小时的停机。官方复盘于 2022 年 1 月 20 日发布,作者是 Daniel Sturman,并与 Scott Delap、Max Ross 以及 Roblox、HashiCorp 的多位同事合作完成。报告中的时间均为太平洋标准时间(PST)。

Roblox 报告每天有 5,000 万玩家。10 月 28 日 16:35,在线玩家降到正常水平的 50%,随后完全停机;10 月 31 日 16:45 恢复 100% 访问,正好是 73 小时。报告称没有用户数据丢失,也没有未授权访问。基础设施规模超过 18,000 台服务器和 170,000 个容器;Consul 集群有 5 个 voting 节点和 5 个 read-only 节点。

时间线

以下时间均为 PST。

时间 事件
10 月 27 日 14:00 在一个流量路由后端启用 streaming;路由节点增加 50%
10 月 28 日 13:37 Vault 变慢,一台 Consul server CPU 升高;玩家尚未受影响
10 月 28 日 16:35 在线玩家降到正常水平的 50%,随后完全停机
10 月 28 日 19:00 大部分 Consul 节点替换为 128 核 NVMe 机器(2 倍核数),集群仍不健康
10 月 29 日 02:00–04:00 尝试一:快照重置加 iptables 隔离;流量恢复后 KV 写 p50 约为 2 秒
10 月 29 日 04:00–10 月 30 日 02:00 尝试二:禁用非必要 Consul 使用,服务实例从数百降到个位数,健康检查从 60 秒放慢到 10 分钟;10 月 29 日 16:00 重启,10 月 30 日 02:00 复发
10 月 30 日 02:00–12:00 深入分析竞争;从 128 核回退到 64 核没有帮助
10 月 30 日 12:00–20:00 禁用 streaming;15:51 完成 rollout;KV 写 p50 降到 300ms;阻止有问题的 leader 继续当选
10 月 30 日 20:00–10 月 31 日 05:00 停机 54 小时开始重建缓存;61 小时 Consul 和缓存恢复健康
10 月 31 日 05:00–16:45 重启服务;10:00 准备好迎接玩家;DNS 按约 10% 增量放量;16:45 恢复 100%,达到 73 小时

影响面

停机持续 73 小时,影响每天约 5,000 万玩家。报告称在线玩家在 10 月 28 日 16:35 降到正常水平的 50%,随后完全不可用;10 月 31 日 16:45 恢复全部访问。报告明确说没有用户数据丢失,也没有未授权访问。

影响不只在游戏入口。Consul 是服务发现、Nomad 调度和 Vault 密钥管理的依赖;Consul 故障会沿着这些控制面扩散到整个平台。报告提到恢复需要重建缓存、重启服务并重新校验,缓存层平时处理约 10 亿请求/秒,冷启动自动化却主要按增量扩容设计,这使恢复更慢。

根因

报告给出两个“基本无关”的低层缺陷,以及一个架构性放大因素。

第一,Consul streaming 的竞争。Streaming 是 Consul 的一项较新特性,正常情况下能降低 CPU 和网络开销,但在同时存在高读和高写的负载下,它用更少的 Go channel 做并发控制,竞争集中在少数 channel 上,反而阻塞了写。10 月 27 日的流量路由 rollout 和 50% 的节点增加提供了触发负载;双路、128 核的 NUMA 硬件加剧了竞争。

第二,BoltDB freelist 病态。Consul 用 BoltDB 存 Raft 日志(不是当前集群状态)。删除日志后空间被标记为空闲,但文件不会缩小。Roblox 的环境里,4.2GB 的存储只装了 489MB 数据,3.8GB 是空的;7.8MB 的 freelist 包含近 100 万个空闲 page ID。每次追加不超过 16KB,也要把整个 freelist 写一遍,导致 2–3 秒的写延迟和 TCP zero-window 背压。事后压缩数据库解决了这个问题。

第三,共享集群架构。一个 Consul 集群同时支撑多个工作负载,使它成为服务发现、Nomad 调度和 Vault 的单点。Consul 一坏,平台的多条控制链路一起坏。

处置与恢复

早期措施包括替换节点、快照回滚、iptables 隔离、降低负载和把核数从 128 回退到 64,都没有解决问题。10 月 30 日禁用 streaming 后,KV 写 p50 从约 2 秒降到 300ms,竞争明显缓解;团队还阻止有问题的 leader 继续当选,避免慢 leader 拖垮集群。

恢复慢有多重原因。两个低层缺陷基本无关,streaming 在事故中定位,BoltDB 慢 leader 的根因在事后才确认;遥测系统与 Consul 存在循环依赖,最需要观测时反而看不见;团队试过硬件、总流量、重连量和 CPU 核数等错误方向;Consul 实际是单点,完整重启需要重建缓存、按正确容量重启服务并验证;缓存冷启动自动化不适合从零开始,快照恢复还留下了错误的调度数据;一个不健康的节点看起来完全开放,导致缓存放置失败;最后还要控制玩家放量,避免再次压垮系统。

恢复过程是:禁用 streaming,阻止问题 leader,重新部署缓存,重启服务,然后用 DNS 按约 10% 的增量放量,直到 100%。报告说 10 月 31 日 10:00 已准备好迎接玩家,16:45 完成全部放量。

后续改进包括:把遥测与被监控系统解耦,增加 Consul/BoltDB 告警和服务流量可见性;建设多可用区和第二个地理数据中心;把关键服务拆到专用 Consul 集群;迁移不合适的 KV 数据并删除过时记录;升级到 bbolt(测试中,计划 Q1 完成);改进缓存 bootstrap 工具、Nomad 恢复和机器配置速度;streaming 只有在 Roblox 规模测试通过后才重新引入,HashiCorp 也在建设新基准并重新设计 streaming。报告还提到,12 月的流量高峰没有造成重大生产事故,Consul 和 Nomad 表现良好;Roblox 会继续把延迟和成本敏感的核心负载放在自建机房,公有云用于突发容量、DevOps 和分析。

教训

第一,服务发现是控制面,不是普通依赖。Consul 同时承载发现、调度和密钥管理,故障会级联到整个平台;关键控制面要按爆炸半径分片。

第二,新特性要在真实负载模式下测试。Streaming 在常规基准里可能更省资源,但在高读高写混合、超大集群和 NUMA 硬件上可能变成竞争点;发布前要覆盖混合负载和最坏情况。

第三,存储引擎的病态会伪装成网络问题。BoltDB 的 freelist 让 16KB 的写变成 7.8MB 的写,表现为 2–3 秒延迟和 TCP 背压;监控要同时看逻辑数据量、文件大小、freelist 和写 p50/p99。

第四,遥测不能依赖被监控系统。循环依赖让团队在最需要数据时失去可见性;监控、日志和告警要有独立路径。

第五,冷启动是独立的故障模式。平时按增量扩容设计的自动化,在整集群从零恢复时可能完全不适用;要定期演练冷启动、缓存预热和流量放量。

第六,事故中的错误假设会消耗大量时间。替换硬件、加倍核数、快照回滚都看似合理,但没有验证就执行会延长停机;应先做可逆的隔离和测量,再决定大动作。

第七,恢复后的放量要慢。按 10% 增量放量并在每步观察 SLO,可以避免“恢复即再次过载”。

对本站读者的检查清单

恢复手册与同类复盘

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么 10 月 27 日启用 streaming 和增加路由节点,会成为 10 月 28 日故障的触发因素?
  2. BoltDB 的 freelist 为什么会让一次 16KB 的写入变成 2–3 秒的延迟?
  3. 为什么遥测与 Consul 的循环依赖会显著延长恢复时间?
答案
  1. Streaming 在高读高写混合负载下把竞争集中到更少的 Go channel,10 月 27 日的路由 rollout 和 50% 节点增加带来了这种负载模式;竞争阻塞 Consul 写,进而影响依赖它的服务。
  2. BoltDB 删除日志后文件不缩小,freelist 膨胀到 7.8MB、近 100 万个空闲 page ID;每次不超过 16KB 的追加也要重写整个 freelist,造成 2–3 秒写延迟和 TCP zero-window 背压。压缩数据库后解决。
  3. 监控和遥测本身依赖 Consul,Consul 故障时团队失去最需要的观测数据,只能靠猜测和外部信息排查;报告因此把解耦遥测列为后续改进。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令