从 9万 QPS 到 6千:一次压测暴露的 15 倍性能黑洞,我们如何用 OpenResty XRay 定位根因
一个微秒量级的反向代理环节,在特定条件下产生了 93% 的性能损耗,且常规监控指标呈现出虚假的“一切正常”。这是一个典型的观测盲区,也是系统性雪崩的隐患。本文复盘了一次从 15 倍性能差距优化至 10% 预期损耗的深度调优。相比单纯的修复,我们更关注如何用 OpenResty XRay 穿透编译器行为与连接管理的底层瓶颈。在现代高并发架构中,对工程细节的掌控力,直接决定了基础设施的稳定性边界。
93% 损耗背后的观测盲区
在任何成熟的工程团队中,性能验收通常是一个确定性的、流程化的环节。尤其对于网关这类基础组件,其架构(Client → OpenResty Gateway → Upstream)已经是一个被反复验证的成熟模式。理论上,一个仅做轻量级转发的网关,其性能开销应是可预测且极低的。
然而,在对新版网关进行基准压测时,我们遭遇了惊人的性能衰退:
- 上游服务基线 (直连压测): 94,706 QPS
- 新版网关 (反向代理): 6,301 QPS
性能凭空蒸发了 93%。一个简单的反向代理,竟然造成了近 15 倍的性能差距。系统表面上风平浪静:没有错误日志,配置也符合常规认知,问题显然已经下沉到我们日常观测的视野之外。
OpenResty XRay 揭示连接复用问题
我们使用 OpenResty XRay 对网关进行 CPU 性能分析,运行 lj-c-on-cpu 工具生成火焰图:
CPU 火焰图被两大块异常宽阔的平台所占据:connect() 和 close() 系统调用。
在高性能网络服务中,这两者本应是几乎不可见的“背景噪音”。它们的异常凸显,指向一个致命的问题:上游连接没有被复用。每一个进来的请求,都在触发一次全新的 TCP 握手和挥手。在高并发场景下,这无异于一场“连接风暴”,内核将大量时间消耗在创建和销毁连接上,而非真正的数据处理。
通过 OpenResty XRay 提供的完整调用栈,我们迅速锁定了问题根源:
- Connect 路径:
ngx_http_proxy_handler→ngx_http_upstream_connect→connect() - Close 路径:
ngx_http_upstream_finalize_request→ngx_close_connection→close()
诊断清晰明了:我们的 upstream 配置块中,遗漏了 keepalive 指令。这是一个看似微小、却足以在生产环境中引发雪崩的配置疏忽。
我们立即启用了上游 keepalive。效果立竿见影:性能提升:从 6,301 QPS 到 21,923 QPS,提升 3.48 倍!
connect() 和 close() 的尖峰已然消失。第一个性能黑洞被填补,但故事还未结束。
对比分析发现编译选项问题
尽管性能大幅回升,但资深的工程师直觉告诉我们,事情没那么简单。我们将当前版本的性能(21,923 QPS)与上一个稳定版本(24,115 QPS)进行对比,发现仍然存在约 10% 的性能差距。
这 10% 的差距从何而来?它虽然不像 15 倍的鸿沟那样触目惊心,但对于追求极致性能的基础设施而言,任何不明不白的衰退都是不可接受的。
- 当前版本的火焰图“更深”:调用栈的层级明显多于稳定版本。
- 特定函数调用增多:例如,
ngx_http_gunzip_body_filter这样的函数在调用链中频繁出现,而在稳定版本中则几乎不可见。
查看 ngx_http_gunzip_body_filter 的代码:
static ngx_int_t
ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
int rc;
ngx_uint_t flush;
ngx_chain_t *cl;
ngx_http_gunzip_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
if (ctx == NULL || ctx->done) {
return ngx_http_next_body_filter(r, in);
}
}
理论上,Nginx/OpenResty 的 filter 链大量使用了尾调用(tail-call),在优化编译下,这些调用应该被编译器“拉平”,几乎没有额外开销。而当前版本的火焰图形态,恰恰暗示着编译器优化可能失效了。
编译器选项对指令执行效率的影响
顺着这条线索,我们核查了两个版本的构建脚本,发现:
当前开发版本使用了 -O0 编译选项。
原因是在开发过程中,为了方便调试某个问题,临时将编译优化关闭了。但在功能开发完成后,忘记恢复为优化编译选项。
-O0 意味着:
- 函数内联失效 - 小函数调用无法被内联
- 尾调用优化失效 - 调用栈变深
- 冗余代码未消除 - 更多无用指令
- 寄存器使用效率低 - 更多内存访问
这些看似微小的编译器行为差异,累积起来,不多不少,恰好“偷”走了我们 10% 的性能。
我们将编译选项恢复为标准的 -O2,重新构建并部署。
结果显示性能完全恢复,与稳定版本持平。至此,两个性能问题被彻底解决。
15 倍 QPS 差异的工程性复盘
这次从 94K QPS 到 6K QPS 的性能排查,暴露了现代复杂系统中两个深刻的挑战:
“隐形上下文”的漂移:性能不仅由代码逻辑决定,更取决于构建与运行时环境的“隐形上下文”——例如编译选项、内核参数和运行时配置。这些上下文的变化往往在 Code Review 中难以察觉,却能导致灾难性的后果。本次的
keepalive缺失和-O0编译选项就是最好的例证。突破观测盲区:为何你需要动态追踪?当问题下沉至内核与编译器层面,传统的 Logs & Metrics 往往保持静默。 本案例证明,面对系统级瓶颈,我们需要的是 OpenResty XRay 这样具备穿透力的工具:
- 全链路采样:穿透应用层直达内核,快速识别宏观瓶颈(如连接风暴)
- 异常模式识别:通过火焰图快速识别非预期的 connect/close 路径。
- 基线对比:定位微观层面的细微差异(如编译器行为)
性能问题往往不是非黑即白的 Bug,而是灰度的效率流失。 在复杂的系统架构中,仅凭经验猜测是低效的。我们需要通过 OpenResty XRay 构建起从应用逻辑到内核调用的完整可观测性,让我们有能力去度量、分析和优化那些隐藏在系统深处的“灰色地带”,从而确保系统的长期稳定与卓越性能。
关于 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. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:
翻译
我们提供了英文版原文和中译版(本文)。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!



















