本文永久链接 – https://tonybai.com/2025/11/19/cloudflare-18-november-2025-outage

大家好,我是Tony Bai。

2025 年 11 月 18 日,世界标准时间(UTC) 11:20,支撑着全球大量互联网流量的 Cloudflare 网络开始出现严重故障。无数网站和应用的用户,开始频繁地看到那令人心悸的“Internal Server Error (500)”页面。一场席卷全球的互联网宕机事件,就此拉开序幕。

事后,Cloudflare 发布了一份极其详尽、坦诚的故障复盘报告。报告揭示了一个令人震惊、也极具讽刺意味的事实:这场灾难的最终扳机,竟然是新一代代理引擎FL2 中(这里仅针对文中提及的新引擎FL2,受影响的旧引擎FL文中并未提及具体原因),一段本应代表“内存安全”的 Rust 代码中的 unwrap() 调用。

这起事件,如同一颗投入平静湖面的巨石,激起了关于 Rust 安全模型、系统复杂性、以及“快速失败”哲学的层层涟漪。它迫使我们重新审视一个根本性问题:我们所追求的“内存安全”,真的能让我们高枕无忧吗?

故障的多米诺骨牌:从一个权限变更开始

Cloudflare 的报告清晰地描绘了一条如多米诺骨牌般精准倒下的故障链。令人惊叹的是,这一切的源头,并非黑客攻击,也不是硬件故障,而是一次看似无害的内部变更:

  1. 源头:ClickHouse 数据库权限变更 (11:05 UTC)
    为了提升查询安全性和可靠性,Cloudflare 的工程师对 ClickHouse 数据库集群进行了一次权限变更。

  2. 第一个意外:重复的元数据
    这次变更意外地导致了一个用于生成“特征文件”(feature file) 的元数据查询(SELECT name, type FROM system.columns WHERE table = …)开始返回重复的列名。因为该查询忘记了按数据库名进行过滤,而新的权限让它看到了底层 r0 数据库中的重复表结构。

  3. 第二个意外:配置文件体积翻倍
    这个“特征文件”是 Cloudflare 机器人管理 (Bot Management) 系统机器学习模型的核心输入。由于元数据查询返回了双倍的行数,最终生成的特征文件体积也翻了一倍,从约 60 个特征,激增到了超过 200 个。

  4. 第三个意外:触发预分配内存上限
    为了极致的性能,Cloudflare 的核心代理服务(包括基于 Rust 的新一代引擎 FL2)会在启动时,为机器人管理模块预分配一块固定大小的内存,用于加载这个特征文件。这个预分配的上限被设置为 200 个特征。

  5. 最终扳机:Rust 代码中的 unwrap() 恐慌 (Panic)
    当那个体积翻倍的、包含超过 200 个特征的“毒丸”配置文件,被分发到全球的 FL2 服务器上时,灾难发生了。负责加载特征的 Rust 代码,在尝试将超过 200 个特征塞入预分配的 200 大小的缓冲区时,append_with_names 方法返回了一个 Err 结果。然而,调用这段代码的地方,却简单粗暴地使用了 unwrap()。

    // Cloudflare 报告中展示的 Rust 代码片段
    let (feature_values, _) = features
        .append_with_names(&self.config.feature_names)
        .unwrap(); // <- BOOM!
    

    unwrap() 的行为是:如果结果是 Ok(value),则返回 value;如果结果是 Err(error),则立即让当前线程 panic(恐慌)

  6. 雪崩:5xx 错误与全球宕机
    工作线程的 panic,导致了一个未处理的错误。这个错误迅速向上传播,最终导致核心代理系统无法处理依赖于机器人管理模块的流量,并开始向上游返回大量的 HTTP 5xx 错误。多米诺骨牌全部倒下,全球大范围的互联网服务因此中断。

Rust 安全模型的反思:“内存安全”≠“永不崩溃”

这起事件,是对 Rust 安全模型的一次深刻、也是痛苦的“压力测试”。Rust 最引以为傲的“卖点”——内存安全——在这场灾难中,既是“英雄”,也是“恶棍”。

英雄之处:它精确地阻止了更坏的情况

Rust 在这里所做的一切,完全符合其设计哲学。append_with_names 方法正确地检测到了缓冲区溢出的风险,并通过返回一个 Err,阻止了一次潜在的内存损坏。如果这段代码是用 C++ 编写的,一个类似的错误可能会导致缓冲区溢出、数据损坏、甚至远程代码执行等更严重、更难以追踪的安全漏洞。

Rust 成功地将一个未定义的、危险的内存行为,转化为了一个已定义的、可预测的程序崩溃

恶棍之处:“快速失败”的哲学真的普适吗?

然而,问题恰恰出在 unwrap() 这个“捷径”上。unwrap() 和它的兄弟 expect(),是 Rust “快速失败”(Fail-fast) 哲学的体现。它们背后的假设是:“我相信这种情况永远不会发生,如果发生了,那就是一个程序员无法恢复的、灾难性的逻辑错误,整个程序应该立刻死掉,而不是带着错误的状态继续运行。

Cloudflare 的工程师们,显然也相信“特征文件永远不会超过 200 个”。

这次事件血淋淋地告诉我们:

  1. 在分布式系统中,你所做的“永不发生”的假设,几乎总会在某个时刻、以一种你意想不到的方式被打破。
  2. unwrap() 是一把极其锋利的双刃剑。它在原型开发、测试代码、或处理那些真正代表“程序不变量被破坏”的场景时非常有用。但将其用于处理任何可能由外部输入(即使是内部系统的“外部输入”)而失败的操作,都是在埋下一颗定时炸弹。
  3. Rust 的内存安全,并不能替代全面的错误处理和系统韧性设计。 它只能保证你的程序“死得干净”,而不能保证它“不死”。

更深层次的教训:超越语言的“系统性失败”

将锅完全甩给 Rust 或 unwrap() 是不公平的。这场宕机,是一次典型的、由多个层面小失误共同导致的系统性失败 (Systemic Failure)

  • 数据库查询的脆弱性:那个元数据查询,为何如此脆弱,以至于一次权限变更就能使其输出加倍?它缺乏对数据库名的过滤,这是一个早已存在的隐患。
  • 配置发布的“零校验”:一个体积异常的配置文件,为何能在没有任何校验和告警的情况下,被迅速分发到全球网络?配置发布管道缺乏基本的“理智检查”。
  • 边界条件的“想当然”:为什么预分配的内存上限是 200?这个“魔法数字”背后的假设是什么?当假设被打破时,为什么没有一个优雅的降级方案(如拒绝加载新配置,继续使用旧配置),而是直接崩溃?
  • 故障域的耦合:机器人管理模块的一次“错误”的特征文件生成,为何能导致核心代理的瘫痪,并进一步影响到 Workers KV 和 Access 等看似不相关的服务?这暴露了系统各组件之间过紧的故障耦合。

小结:废墟之上,我们学到了什么?

Cloudflare 的这次全球宕机,为整个软件行业都上了一堂极其昂贵的公开课。对于 Rust 社区而言,它提醒我们,Result<T, E> 和完善的 match 模式,才是处理可恢复错误的王道,而 unwrap() 应该像 unsafe 关键字一样,被审慎地、有意识地使用。

但更重要的是,它告诉我们,没有任何一门语言,无论其内存安全模型多么先进,能够将我们从系统性思考的责任中解救出来。构建可靠的、有韧性的分布式系统,是一项超越任何特定语言的、需要防御性编程、纵深防御、以及对“墨菲定律”抱有永恒敬畏的综合性工程挑战。

Cloudflare 在废墟之上,承诺将“加固配置文件的摄入”、“增加全局熔断开关”、“消除核心转储压垮资源的可能性”。这些,才是比争论“unwrap() 是否邪恶”更有价值的、真正能让我们从这次灾难中变得更强大的教训。

Cloudflare的故障复盘报告:https://blog.cloudflare.com/18-november-2025-outage/


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2025, bigwhite. 版权所有.

Related posts:

  1. Rust 的安全神话?数据库 CEO 为何在关键系统中仍选 C++
  2. SQLite 对 Go 和 Rust 说“不”:揭示“安全语言”光环下的工程现实
  3. Rust vs. Go:为什么强强联合会更好
  4. 哲学家与工程师:为何 Rust 和 Go 的“官方之声”如此不同?
  5. Rust 布道者Jon Gjengset深度访谈:在 AI 时代,我们该如何思考编程、职业与未来?