CPU 占用率 45% 的“黑洞”:一次 APISIX “黑盒”插件的 C 级别性能诊断
本文复盘了一次真实的客户案例。其 APISIX 网关集群遭遇了严重的 CPU 瓶颈,传统 perf 工具在“黑盒”插件面前束手无策。本文将详细展示 OpenResty XRay 团队如何使用动态追踪技术,非侵入式地穿透 Lua VM,精确定位到 OpenSSL C 库中一个占 44.8% CPU 的 pkey_rsa_decrypt 函数,并揭示了其对系统并发能力的真实影响。
API 网关是现代微服务架构的“咽喉”,其性能和稳定性至关重要。
近日,我们的一个客户——某大型电商平台——联系我们,报告了一个棘手的生产问题:他们核心业务的 APISIX 网关集群在高峰时段 CPU 占用率持续触顶 (100% Saturation),导致 P99 延迟(99%分位延迟)急剧抖动,这是一个高优先级的紧急事件。
当 perf 遇到“黑盒”
客户的 SRE 团队迅速介入,并尝试了以下手段:
top/htop: 结果只显示openresty进程是 CPU 消耗的主力,没有更多信息。perf record: 客户尝试用perf record和火焰图。在最好的情况下,perf 的火焰图或许能显示pkey_rsa_decrypt这样的 C 函数在消耗 CPU,尽管这在 JIT 环境中经常被luajit_vm_dispatch等 VM 符号淹没。
但这立刻带来了第二个、也是更致命的问题:无法归因,perf 无法将这个 C 函数调用与任何上游的 Lua 代码关联起来。它得到的是一个“孤立的热点”。我们不知道是哪个插件、哪个 API 路由、哪行 Lua 代码触发了这个昂贵的 C 调用。
这就是 perf 在混合语言栈 Lua+C 上的核心局限:它丢失了上下文。 在一个运行着几十个插件的复杂网关上,一个‘孤立的’ C 函数热点是无法指导优化的。
- APM 监控: 客户的 APM 系统(基于
ngx.now和ngx.log)显示,大部分耗时发生在access_by_lua阶段。缩小了范围,但依然太宽泛,access阶段可能挂载了十几个插件。
团队通过逐一排查,锁定在一个用于会话验证的自定义插件 (cb-session-validation) 上。但问题在于,这个插件是由第三方服务商提供的“黑盒”,客户团队没有它的完整源代码。
这是一个典型的“盲区”:
- 宏观上,知道 CPU 爆了。
- 中观上,知道是
access_by_lua阶段。 - 微观上,无法定位到是哪个具体函数导致了消耗。
从“采样”到“全栈动态追踪”
根据 OpenResty XRay 技术专家团队判断,既然 Lua 级别的 Profiler 和系统级的 perf 都无法提供答案,那么瓶颈几乎可以肯定发生在 Lua VM 与 Native C 库的边界上。
这是传统采样工具的“盲区”。perf 擅长分析 C/C++ 或内核,但是没有办法获取 LuaJIT、V8、JVM 的应用语言级别的代码的调用栈,因此能够获取的信息非常有限。开源的 APM 工具的使用门槛很高,只有少数开发者能够熟练使用那些工具,不适合在生产环境部署。
我们建议客户直接在出问题的生产环境中直接用 OpenResty XRay 进行一次“CT 扫描”。
OpenResty XRay 的核心区别在于它不依赖“采样” (Sampling),而是使用“动态追踪” (Dynamic Tracing)。它能非侵入式地实时重建从 NGINX 事件循环,到 LuaJIT VM,再到 C 库乃至内核的完整调用栈,无需重启或修改任何代码。
分析任务启动后,证据清晰地指向了一个惊人的事实:一个名为 pkey_rsa_decrypt 的 C 函数,其 CPU 占用时间高达 44.8%。
拿到证据链
这个 pkey_rsa_decrypt 函数显然来自 OpenSSL 库,用于执行 RSA 私钥解密。但它是如何被调用的?
OpenResty XRay 提供的完整火焰图和调用栈给出了以下证据链:
@access_by_lua(nginx.conf:310):2
http_access_phase@/usr/local/apisix/apisix/init.lua:721
run_plugin@/usr/local/apisix/apisix/plugin.lua:1154
phase_func@/opt/apisix/cb_plugins/apisix/plugins/cb-session-validation.lua:93 <-- 客户的“黑盒”插件
validate_session@/opt/apisix/cblualib/cbmodules/cb-session-validator.lua:283
verify_session@/opt/apisix/cblualib/cbmodules/cb-session-validator.lua:105
load_jwt@/opt/apisix/cblualib/resty/cb_jwt.lua:624
pcall
[builtin#pcall]
@/opt/apisix/cblualib/resty/cb_jwt.lua:250
...
C:pkey_rsa_decrypt [/usr/lib64/libcrypto.so.1.1.1k] <-- 44.8% CPU 瓶颈点
@/usr/src/debug/openssl-1.1.1k-12.el8_9.x86_64/crypto/rsa/rsa_pmeth.c:337
这条证据链的价值在于:
- 信息透明化: 它提供了 perf 缺失的归因上下文,明确地将 C 语言的热点
pkey_rsa_decrypt追溯到了其 Lua 肇事者cb-session-validation.lua插件的第 93 行。 - 连接了 Lua 与 C: 它完美地绘制了从 APISIX 插件到 JWT 库(Lua 代码),最后“刺入”
libcrypto.so(C 库)的完整路径。
在没有源码、无需 GDB、不重启服务的情况下,我们也知道了是哪个文件、哪行代码触发了问题,这才是工程师真正需要的、可立即采取行动的洞察。
从“发生了什么”到“为什么”
定位到 pkey_rsa_decrypt 只是第一步。工程师更关心的是:**为什么它会成为瓶颈?**我们基于 OpenResty XRay 的数据,向客户提出了两个层面的分析和优化假说:
假设 1:过度的加密(算法问题)
validate_session 阶段的非对称解密如此耗时,我们首先怀疑是密钥长度问题。如果客户(或其第三方)使用了 4096-bit 甚至更长的 RSA 密钥,其计算开销是惊人的。
我们用 openssl speed 做了一个快速的基准测试,对比了 RSA 2048-bit 和 4096-bit 的性能:
| 算法 | 签名操作 (sign/s) | 验证操作 (verify/s) |
|---|---|---|
| rsa 2048 bits | 1325.3 | 48404.7 |
| rsa 4096 bits | 211.5 | 13877.3 |
| 性能对比 | 2048 是 4096 的 6.2 倍 | 2048 是 4096 的 3.4 倍 |
数据显示,RSA 2048 的验证速度是 4096 的 3.4 倍以上。而在 2024 年,2048-bit 的密钥在绝大多数场景下被认为是足够安全的。使用 4096-bit 属于“过度安全”,却付出了 3-6 倍的性能代价。
假设 2:冗余的计算(架构问题)
从调用栈看,这个昂贵的解密操作发生在每一个请求的“会话验证”阶段。
这很可能是一个架构缺陷。对于一个合法的、未过期的 Session/JWT,其解密结果(即用户的身份信息)应该是可以被缓存的。
在会话的有效期内(例如 5 分钟),完全没有必要为同一个 token 在每一个请求上都重复执行数万次昂贵的 RSA 解密。
这对系统并发能力意味着什么?
这个 44.8% 的 CPU 占用,本质上是系统吞吐能力的“拦腰斩”。
这意味着网关集群近一半的 CPU 周期,都被浪费在可被缓存、可被优化的重复性 crypto 运算上。这直接导致了 CPU 提前触顶,无法处理更多的并发请求,P99 延迟必然飙升。
复盘总结
这个案例完美地暴露了常规可观测性和 APM 工具的局限性。常规监控手段,无论是 perf 还是 APM 之所以束手无策,是因为瓶颈发生在 VM 与 Native C 库的边界上,而 perf 只能得触及 C 的调用栈,无法深入获得 Lua 调用栈,缺乏最后一公里的洞察力。
如前所述,perf 或许 能发现 pkey_rsa_decrypt 是热点。但在 APISIX 这样的高并发、事件驱动的 JIT 环境中,这几乎是一个无用的信息。
问题在于“归因鸿沟”。perf 无法跨越 LuaJIT VM 的边界。它不知道这个 C 函数是由 VM 上的 哪段 Lua 代码 触发的。在一个有几十个插件、每秒处理数万请求的系统上,你无法知道是插件 A 还是插件 B 造成的。
也就是说:
- perf 看到了 C 函数的繁忙,但无法将它归因到 Lua 请求。
- APM 看到了 Lua 请求的缓慢,但无法“看穿” C 语言的黑盒。
而 OpenResty XRay 的核心优势在于全栈动态追踪。它能重建完整的、跨越“Lua-land”和“C-land”的混合调用栈,精确地告诉你:“cb-session-validation.lua 文件的第 93 行,通过一系列调用,最终执行了 pkey_rsa_decrypt,并消耗了 44.8% 的 CPU。”
对于 APISIX、Kong 这类基于 OpenResty 的高性能中间件,其性能优化的“深水区”往往就在 C 语言层面、FFI 边界或系统调用上。要驾驭这种复杂系统,资深工程师需要一种能提供“新视角”的工具:它必须能够全栈动态追踪,将 Lua 和 C 的世界无缝连接起来。
在这次诊断中,OpenResty XRay 扮演了“CT 扫描仪”的角色,它让客户的团队停止了盲目猜测,转而进行基于数据和证据的精确优化。我们给客户的优化建议也非常明确:
- (治本)架构优化: 在
cb-session-validation.lua中引入lua-resty-lrucache,对解密结果进行进程内缓存 (in-process cache)。 - (降级)算法优化: 立即检查 RSA 密钥长度,若为 4096-bit,评估降级到 2048-bit 的可行性。
如果你的团队也在维护复杂的 OpenResty/APISIX 系统,并且厌倦了在 perf 的结果中大海捞针,欢迎申请试用看看系统中的“性能黑洞”究竟是什么。
关于 OpenResty XRay
OpenResty XRay 是一款动态追踪产品,它可以自动分析运行中的应用,以解决性能问题、行为问题和安全漏洞,并提供可行的建议。在底层实现上,OpenResty XRay 由我们的 Y 语言驱动,可以在不同环境下支持多种不同的运行时,如 Stap+、eBPF+、GDB 和 ODB。
关于作者
章亦春是开源 OpenResty® 项目创始人兼 OpenResty Inc. 公司 CEO 和创始人。
章亦春(Github ID: agentzh),生于中国江苏,现定居美国湾区。他是中国早期开源技术和文化的倡导者和领军人物,曾供职于多家国际知名的高科技企业,如 Cloudflare、雅虎、阿里巴巴, 是 “边缘计算“、”动态追踪 “和 “机器编程 “的先驱,拥有超过 22 年的编程及 16 年的开源经验。作为拥有超过 4000 万全球域名用户的开源项目的领导者。他基于其 OpenResty® 开源项目打造的高科技企业 OpenResty Inc. 位于美国硅谷中心。其主打的两个产品 OpenResty XRay(利用动态追踪技术的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最适合微服务和分布式流量的全能型网关软件),广受全球众多上市及大型企业青睐。在 OpenResty 以外,章亦春为多个开源项目贡献了累计超过百万行代码,其中包括,Linux 内核、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl 等,并编写过 60 多个开源软件库。
关注我们
如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:
翻译
我们提供了英文版原文和中译版(本文)。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!


















