B站网关事故背后:OpenResty 与 Lua 的稳定性代价
注:本文在大模型辅助下完成。
一、一次看似普通的网关事故,为何会导致全站雪崩?
2021 年 7 月 13 日,Bilibili 发生了一次非常经典的网关事故[1]。
事故发生后,线上出现了非常诡异的现象:
所有 OpenResty worker 进程 CPU 100%
但请求几乎无法处理
进程没有 crash
没有 core dump
- error log 中也没有明显异常
重启无法恢复
回滚代码也无法恢复
最终,整个站点出现大面积故障。
更令人意外的是:
最终定位出的根因,只是一个字符串 "0"。
也就是说,一个看起来几乎不可能引发灾难的数据类型问题,最终却导致:
网关整体失效
请求无法转发
全站雪崩
这次事故后来由 OpenResty 官方团队通过 XRay 工具进行了深度分析[2],也成为 OpenResty 领域最经典的稳定性案例之一。
但真正值得关注的,其实并不是"为什么会出现一个低级 bug",而是:
为什么一个如此微小的问题,居然能够穿透整个网关基础设施,最终演变成全站级事故?
而要理解这个问题,就必须先理解:OpenResty 到底是什么。
二、什么是OpenResty?为什么它如此流行?
很多人会误以为:OpenResty 是一个独立的 API 网关。
实际上并不是。
OpenResty 的本质是"增强版Nginx"。它的核心思想是:以 Nginx 作为高性能网络框架,在 Nginx 内部嵌入 Lua 运行时,允许开发者在 Nginx 请求处理阶段执行 Lua 代码。
也就是说:
OpenResty = Nginx + Lua Runtime
在传统 Nginx 中,开发者主要依赖nginx.conf、rewrite 规则、upstream 配置来控制流量。但 Nginx 的问题是:配置能力强,但编程能力有限。很多复杂场景都很难实现:动态路由、动态鉴权、动态限流、动态灰度、动态 upstream、插件体系。
而 OpenResty 的出现,彻底改变了这一点。它允许开发者在 rewrite、access、balancer、filter 等阶段直接编写 Lua 逻辑。
于是,Nginx 从"配置驱动"变成了"代码驱动"。这也是 OpenResty 在互联网领域快速流行的重要原因。
后来,Kong、Apache APISIX 等知名 API 网关项目,也都选择了OpenResty 作为底层架构。
因为它确实非常灵活。它让网关第一次具备了动态化、插件化、可编程化的能力。
但与此同时,"灵活"本身,也开始成为稳定性问题的来源。这不是说灵活性是错的,而是说:灵活性与稳定性之间,始终存在架构权衡(trade-off)。
三、为什么OpenResty的技术路线会放大稳定性风险?
很多人讨论 OpenResty 时,首先关注的是性能、动态能力、插件生态。但对于网关而言,真正最重要的能力,其实是稳定性。
因为网关不是普通业务系统。它是全站入口、流量枢纽、多业务共享基础设施、高并发长生命周期系统。它一旦出现问题,影响范围往往是全局性的。
因此,网关最怕的,其实不是"功能不够",而是"不可预测"。而 OpenResty 的技术路线,恰恰在某些层面放大了这种"不可预测性"。
1. Lua动态语言的问题
Lua 是典型的动态弱类型语言。例如:
local weight = "0" if weight == 0 then ... end这里 "0" 是字符串,0 是数值。在复杂系统中,这种问题非常容易被忽略。
更关键的是:Lua大量错误只能在运行时暴露,而无法在编译阶段发现。这意味着配置上线后才发现问题、某些边界流量才会触发问题、某些特殊数据才会暴露问题。
对于业务系统而言,这可能只是单请求失败、单实例异常。但对于网关,意味着一个运行时错误,可能直接影响整个流量入口。
2. Lua的"灵活性"本身就是风险来源
Lua 为了灵活性,引入了大量动态机制:table 动态扩展、metatable 元编程、monkey patch、coroutine、动态 require、共享状态。
这些机制在业务开发中很方便。但在网关系统中,灵活往往意味着不可预测。
例如:某个 table 被意外修改、某个共享状态没有同步、某个 coroutine 没有退出、某个模块被热更新覆盖,都可能导致 CPU 飙升、worker 卡死、内存泄漏、请求阻塞。
而且,这类问题往往极难复现,因为它们依赖运行时状态、并发路径、流量模式、特定数据。
3. LuaJIT又进一步增加了复杂性
OpenResty 的高性能,很大程度来自 LuaJIT。LuaJIT 会进行动态热点编译、trace optimization、speculative optimization。
这意味着同一段 Lua 代码,测试环境正常、小流量正常,但高并发线上异常——因为JIT的行为依赖运行时路径。这会让很多问题难复现、难定位、难解释。
而对于网关而言,"无法稳定复现的问题",往往是最危险的问题。
这里需要特别说明:LuaJIT 的JIT 编译器本身在此事故中并没有 bug。官方复盘明确指出,最初怀疑 JIT 问题是因为另一个业务团队的未告知操作产生了干扰。但 JIT 的动态优化特性,确实增加了问题定位的复杂度。
4.插件化体系进一步放大了风险
今天很多基于 OpenResty 的网关(如 Kong、Apache APISIX)都强调插件化。但插件化本身也意味着:多团队共享运行时、插件共享 worker、插件共享 Lua VM、插件运行在同一个网关进程内部。
于是,一个插件问题,就可能拖垮整个网关实例。
这和传统业务系统(微服务隔离、进程隔离、容器隔离)的风险模型完全不同。在OpenResty 架构中,"业务逻辑"与"基础设施"之间,实际上没有真正隔离。
四、回到B站事故:为什么一个"0"能导致整个网关崩溃?
理解了 OpenResty 的技术路线之后,再回头看 B站事故,就会发现:这并不是一次"偶然事故",而是系统性风险的一次具体暴露。
直接原因:类型误用导致死循环
事故最终定位发现:一个字符串 "0" 被错误地当成数值 0 使用。而 lua-resty-balancer 在处理过程中,最终进入了异常逻辑路径。
具体来说,在 lua-resty-balancer 的 _gcd(最大公约数)函数中,代码期望传入的是数值类型。当传入字符串"0" 时,Lua 的取模运算"0" % 0 产生了 nan(非数值),导致无法进入 b == 0 的终止条件,从而陷入无限循环。
随后导致:无限递归/循环 → worker CPU 100% → 整个网关集群全部失效。
系统性原因:动态架构放大了单点缺陷
真正可怕的地方在于,这个问题:
没有 crash
没有 core dump
没有明显 error
- worker 进程仍然"活着"
只是不再处理请求。
而对于网关来说,"活着但无法工作",往往比直接crash更危险。因为:
健康检查可能无法及时发现(进程还在,端口还通)
自动恢复机制可能无法触发
流量还会持续进入故障节点
最终形成:排队 → 超时 → 重试风暴 → 雪崩扩散。
这也是为什么,网关系统最怕的并不是"明确失败",而是"不可预测的异常状态"。
五、OpenResty最大的争议:把"业务逻辑"带进了"基础设施"
很多人认为 OpenResty 的问题只是"Lua 不够安全、动态语言不够稳定"。但实际上,更深层的问题,是"业务动态逻辑进入了基础设施"。
OpenResty 最大的成功在于:它让业务团队可以快速扩展网关能力。但与此同时,它也让动态脚本、业务逻辑、运行时行为、热更新能力直接进入了 L7 网关、流量入口、核心基础设施。
于是,网关开始逐渐业务化、状态化、动态化、不可预测化。最终,基础设施不再是"稳定内核",而变成了"动态运行平台"。
而对于基础设施来说,动态能力越强,稳定性复杂度通常也越高。这也是为什么,近年来越来越多的新一代基础设施开始强调:强类型、静态分析、内存安全、可验证性、沙箱隔离、可预测执行模型。
因为对于基础设施而言,"稳定、可预测、可控制"往往比"灵活"更重要。
当然,这并不意味着 OpenResty 的技术路线是错误的。Kong、APISIX 等项目的成功证明,在需要快速迭代、灵活扩展的场景下,OpenResty的权衡是合理的。但 B站事故说明:当这种灵活性缺乏足够的防御机制时,一个字符串"0",就足以让整个大型网站的网关系统全面失效。
结语
B站网关事故已经过去数年,但它留下的警示依然鲜活:
对于基础设施,"不可预测"比"不够灵活"更致命。
OpenResty 用 Lua 的动态性重新定义了网关的灵活性,但也让我们看到了动态语言在基础设施中的脆弱一面。这不是 Lua 的错,也不是 OpenResty 的错,而是我们在享受灵活性带来的便利时,往往低估了防御性编程和边界校验的必要性。
一个字符串 "0",最终演变成全站雪崩。这个看似荒诞的因果链,恰恰揭示了分布式系统中最深刻的真理:
系统的稳定性,不取决于最强的一环,而取决于最脆弱的边界条件。
参考资料
[1] 2021.07.13 我们是这样崩的,哔哩哔哩技术,2022年7月
[2] OpenResty XRay 分析和解决 B 站重大线上事故,OpenResty软件,2022年7月
作者简介
章淼,博士,1994年进入清华大学计算机科学与技术系学习,2004年获得博士学位,2004年至2006年在清华大学留校任教,在清华期间曾参与中国第一代核心路由器的研制工作。2012年起在百度工作超过十年,聚焦云网络基础架构的研发工作,是BFE开源项目的发起人。在百度期间积极推动软件工程能力提升,曾担任百度代码规范委员会主席,2021年10月被授予百度代码规范委员会荣誉主席。2022年出版《代码的艺术:用工程思维驱动软件开发》。2023年4月起担任瑛菲网络CEO,聚焦研发面向云和大模型场景的现代化流量管理平台。
