---
整个互联网被一根 Rust 的 unwrap 给整不会了!事情发生11月18日,Cloudflare 的工程师小手一抖,给 ClickHouse 升了个级,结果把全球从ChatGPT、麦当劳自助点餐机到漂亮国核电站员工通道全干熄火。别眨眼,听完你就知道什么叫“一个逗号引发的血案”。
Cloudflare 是啥?就是传说中互联网的“门房大爷”,你家网站、APP、游戏、核电站门禁,全得先到他这儿打卡,他点头你才能进。每天处理上亿次请求,号称“如果 Cloudflare 挂掉,半个地球就断网”。
整个故事,要从Cloudflare的“机器人管理”(Bot-Management)系统说起。这个系统就像Cloudflare的“安检门”,时刻检查着进入网络的每一个请求,判断你是人还是恶意机器人。为了做出精确判断,他们需要一个超级强大的分析型数据库来处理海量的威胁情报数据,他们选择的就是大名鼎鼎的ClickHouse。
Cloudflare工程师们有一个例行维护任务:他们需要定期生成一个“特征文件”(Feature File),这个文件里记录了所有已知和可疑机器人的行为特征、IP地址、指纹信息等等。你可以把它想象成一个全球通缉令,它会被分发到Cloudflare的每一个网络节点上,指导它们如何识别和拦截“坏蛋”。
这次,他们只是想对ClickHouse的访问权限做一次“温柔的升级”。原计划是让生成脚本能够看到更多的数据表结构。但是,悲剧的种子就在此刻种下了!
事情的起因比偶像剧还狗血:运维小哥在后台跑了个脚本,想给 ClickHouse 的权限表加点料,让查询更丝滑。
在升级权限之前,这个查询脚本只能看到ClickHouse数据库中的“默认模式”(default-Schema)下的数据,所以一切都很正常,生成的“特征文件”大小适中,大约只有60条记录。
结果脚本一跑,系统表 system.columns 突然把底层分片 r0 的元数据也吐出来了,原本 60 行的配置啪的一下变成 200+ 行,直接翻倍。
当工程师们扩大了查询权限后,脚本突然能访问到底层数据存储的“分片模式”(r0-Schema)了!更要命的是,编写脚本的工程师在查询时,并没有明确加上“只查询默认模式”的限定条件!
后果是什么?ClickHouse很“听话”,既然你没限定,那我就把我能看到的所有元数据都吐给你!这就像你问一个孩子:“你叫什么名字?”他回答:“我叫小明,我爸爸叫大明,我妈妈叫红花,我爷爷奶奶……”
你以为这是简单的“数据长胖”?错!这相当于给门房大爷的钥匙串上突然多塞了两百把钥匙,大爷当场懵圈。
结果就是,每一个原本正常的特征记录,都因为底层分片的元数据被“复制粘贴”了一遍!原本只有60条的“全球通缉令”,一下子暴涨到了200多条!文件体积瞬间翻了好几倍,变成了一个“超大号”的胖文件。
好了,文件变胖了,还没到崩溃的地步。真正的“引爆点”,正在核心服务中等待着它!
Rust的unwrap()为什么是生产环境的禁忌
更刺激的来了,FL2 代理是 Cloudflare 去年才上线的新架构,用 Rust 写的,主打一个“内存安全、性能怪兽”。
在FL2服务的代码中,有一段逻辑是专门用来加载和处理这个特征文件的。为了性能优化,工程师们做了一个“硬性假设”:这个特征文件,永远、永远、永远不会超过200条记录!因此,为了高效地提前分配内存空间,他们的代码中设置了一个内部的“硬限制”(Hard-Limit)。
当FL2进程拿到那个200多条记录的超大号文件时,它发现:“完了,这个文件超过了我的容量上限!”
此时,真正的灾难代码出现了!
代码里有这么一行:
rust
let feats = parse_config(&buf).unwrap();
unwrap 是啥意思?就是“老子不管,这里必须成功,失败我就原地去世”。
结果配置一超 200 行,parse_config 返回 Err,啪!线程直接 panic,FL2 进程原地升天。
服务器重启,拉取同一份毒药配置,再 panic,再重启……无限套娃,史称“Thundering Herd 雷霆万牛”。
“Panic”与“雷霆万钧”效应的叠加
进程瞬间被终止!Cloudflare官方报告中记载了这场“自杀”事件:thread fl2_worker_thread panicked: called Result::unwrap() on an Err value。
因为FL2进程是Cloudflare全球流量路由和安全判断的核心,它一死,就意味着全球无数用户的连接瞬间被切断,终端用户看到的,就是我们熟悉又恐怖的HTTP 500错误!
但事情远没有结束!服务器可不会闲着,它发现核心进程死了,马上触发“自动重启”!但“重启”之后,进程又会重新加载那个“致命”的200多条配置,然后,再次遇到.unwrap(),再次崩溃!
这就是所谓的“启动死循环”(Boot-Loop)!全球成千上万的Cloudflare节点,就像一排多米诺骨牌一样,集体陷入了“崩溃——重启——再崩溃”的无限地狱!
更恐怖的是“雷霆万钧”(Thundering Herd)效应!当大量节点同时崩溃时,那些还在工作的少数“幸运儿”节点,瞬间就要承受天文数字般的流量冲击,它们很快也会因为过载而崩溃,最终导致整个网络的承载能力呈指数级下降!
超越网络:一个bug如何影响了核电站门禁和麦当劳点餐?
这时候全球网民在干啥?
刷 ChatGPT 写论文的,页面直接 500;
打游戏的, launcher 转圈;
最惨的是漂亮国核电站的小哥,刷员工卡提示“网络异常”,被拦在门外吹冷风。
麦当劳点餐机黑屏,小姐姐手写订单,字丑到顾客以为菜单加密。
微博热搜第一:“#半个地球崩了#”,点进去一看,全是段子手在冲业绩。
这场故障的破坏力,简直是“降维打击”!它不仅影响了网络,甚至渗透到了实体世界和关键基础设施!
1. 认证系统的集体失忆
因为Cloudflare的“门神”——Turnstile(无感验证系统)和Access(企业身份认证)——直接依赖这个崩溃的FL2服务,它们也全面瘫痪了!
这意味着什么?
- 你无法登录Salesforce、Slack等SaaS服务。
- 你无法使用ChatGPT等AI应用,因为登录验证失败。
- 无数网站的登录界面、用户后台、甚至是支付流程,全部被卡死!因为“门神”自己倒下了,没人能帮你开门了!
2. 实体世界的“硬伤”
更令人震惊的是,报告证实了这次故障直接影响了物理世界:
- 麦当劳点餐终端失灵:想象一下,你正要点一个巨无霸,结果终端机显示“Error 500”!这说明,连餐饮行业的核心订单和支付系统,都对Cloudflare有深度依赖!
- 核电站门禁系统受影响:最让人胆寒的是,美国的核设施“人员访问数据系统”(PADS)也报告了访问中断!这个系统是用来管理和验证进入核技术设施人员身份的!一个数据库的配置错误,竟然能间接威胁到国家级的关键基础设施安全!
说到这儿,必须给 Cloudflare 的 on-call 工程师递杯冰美式。
人家十分钟内定位到是配置膨胀,二十分钟回滚“Last Known Good”版本,三十分钟把 unwrap 改成 match 并加硬限,一小时后全球逐步恢复。但代价是伦敦节点被迫关掉 WARP 服务,当地网友以为脱欧连网也脱了。
彩蛋来了,出事那会儿,连 downdetector 自己也 503,因为监控网站自己也托管在 Cloudflare 上,堪称“我监控我自己,结果我也挂了”。
程序员圈子的梗图刷爆:一个 unwrap 写在山顶,山脚下尸横遍野,配文“写代码最浪漫的事,就是和你一起 panic 看日出”。
复盘大会更精彩,CTO 连夜发长文:
第一,内部配置也要当外部输入一样做校验,别搞“自己人免检”;
第二,关键路径禁止 unwrap,必须“Fail Open”,宁可放行也别让整个进程暴毙;
第三,给 ClickHouse 查询加 DISTINCT,杜绝重复行。
底下员工评论:“以后代码 Review 先拍桌子,谁再写 unwrap 就请谁喝核电站冷却水。”
说到 Rust,很多小伙伴以为内存安全就等于不会崩溃,其实 unwrap 就是 Rust 界的“不作死就不会死”。官方文档明明白白写着:生产环境请用 match 或 ? 运算符,结果还是有人自信爆棚。就像你谈恋爱,明知道对方情绪不稳定,还非要吼一句“你有本事分手啊”,然后真分了,又哭天抢地。
深刻反思:从.unwrap()事件看现代网络架构的“致命弱点”
最终,Cloudflare通过回滚到正确的配置版本,并紧急打补丁移除那个致命的.unwrap()才得以恢复服务。但这场灾难留给所有技术人员的教训是沉重且深刻的:
教训一:打破内部数据“信任链”
所有核心系统,必须彻底消除对“内部生成数据”的“盲目信任”(Implicit Trust)!
- 严格验证是底线:内部配置、内部消息队列数据,都必须进行和外部恶意输入一样严格的“输入验证”(Input Validation)。一旦发现数据超限、格式错误,必须立即拒绝使用,发出最高优先级警报。
- 隔离与降级:核心逻辑应该与配置的解析和校验逻辑严格分离。即使配置解析失败,核心服务也应该能够优雅地降级,例如继续使用上一个“已知良好”的配置提供服务。
教训二:核心组件必须坚持“宁愿降级,不可奔溃”原则
对于Cloudflare这种全球级的网络基础设施,系统韧性(Resilience)是高于一切的!进程崩溃(Panic)是绝对不可接受的!
- 拒绝.unwrap():在任何关键路径,特别是可能因外部或配置输入失败的路径上,严禁使用.unwrap()或.expect()。正确的做法是使用Rust的match表达式或?运算符来优雅地处理Err。
- “失败即开放”原则(Fail Open):当系统遇到配置超限或不可预知的错误时,它应该选择“宁愿放错,不可杀错”!正确的姿态是“失败即开放”——保持服务运行,继续使用旧配置提供服务,而不是“失败即关闭”(Fail Closed),导致整个进程崩溃,拒绝所有连接。
它提醒我们:最强大的系统,往往倒在最微小的逻辑疏忽上!我们必须重新审视每一个“单点故障”(Single Points of Failure),确保我们构建的互联网,是韧性十足的,而不是脆弱不堪的多米诺骨牌!