在高并发的 Web 服务中,开发者和运维人员常常会遇到一个棘手的问题:应用的实际性能远未达到预期。一个典型的场景是,单个 nginx worker 进程每秒处理的请求数(RPS)不足 300,而服务器的系统负载却已接近其逻辑 CPU 的核心数。更深入地探查,可能会发现 Nginx 的事件循环(Event Loop)存在长达数十毫秒的严重阻塞。这些问题如同“隐形的性能杀手”,悄无声息地影响着系统的整体吞吐能力。

传统的监控工具虽然能展示 CPU 使用率高、响应延迟增加等表面现象,但往往难以深入到代码执行层面,精确定位到导致阻塞的具体函数或操作。

OpenResty XRay 是一款强大的动态追踪产品,非常适合分析处理这类复杂的 off-CPU 问题。它能够在不侵入应用代码、不影响生产环境稳定性的前提下,对系统进行深度分析。在本文中,我们将通过一个真实案例,详细介绍如何使用 OpenResty XRay 定位并解决 OpenResty 应用中的 off-CPU 性能瓶颈。

性能“疑云”:CPU 资源争用浮出水面

我们分析的第一步,是使用 OpenResty XRay 的 C 级别 off-CPU 火焰图来探查进程的等待事件。

Screenshot

分析结果显示,除了预期的 epoll_wait 网络IO等待之外,还有相当一部分 off-CPU 时间消耗在了 mpi_mul_hlpfree 等纯 CPU 计算相关的函数上。

这表明,进程在准备好运行时,却未能及时获得 CPU 时间片,这是 CPU 资源争用的典型特征。

通过专门的分析器,我们确认了问题的根源:nginx 配置文件中缺少了 worker_cpu_affinity 指令。

no cpu affinity set.
no cpu affinity set.

该配置的缺失,导致多个 nginx worker 进程在不同 CPU 核心间被 Linux 内核频繁调度,产生了不必要的上下文切换开销,从而降低了 CPU 的有效利用率。

真凶现形:阻塞的 Lua IO 操作

解决了 CPU 争用问题后,我们继续对 off-CPU 时间进行分析。

Screenshot

我们发现,绝大部分的 off-CPU 阻塞时间都指向了同一个源头:用户自定义的 customize.lua 文件第 15 行。

该行代码调用了一个名为 file_list 的函数,此函数内部使用了 Lua 标准库提供的 io.popenread 函数来执行 shell 命令。

这些都是同步阻塞式 IO 操作,它会暂停整个 Nginx 事件循环,直到外部命令执行完毕并返回结果。这正是导致系统吞吐能力低下的核心原因之一。

量化分析:文件 IO 性能影响

阻塞的⽂件 IO 操作

样本一

除了 io.popen 之外,我们还通过 C 级别虚拟⽂件系统读写次数⽕焰图,发现了另外两个潜在的性能瓶颈:

Screenshot

从上面这张火焰图可以看出,apr_generate_random_bytes 函数占用了高达 59.8% 的文件读取次数。

Screenshot

而根据第二张火焰图,apr_sdbm_fetch 函数占用了 24% 的文件读取次数。

样本二

Screenshot

从上⾯这张图可以看到 Apache 运行时 apr_generate_random_bytes 这个 C 函数占了 51.8% 的⽂件读取次数。

Screenshot

而第二张图显示函数 apr_sdbm_fetch 一共占了 20.7% 的⽂件读取次数。

性能评估

为了精确评估这些文件 IO 操作的影响,我们通过专门的延时工具来测量 apr_generate_random_bytes 的延时分布:

3110 samples' latency: min=10, avg=17, max=1494 (us)
value |-------------------------------------------------- count
    2 |                                                      0
    4 |                                                      0
    8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  1540
   16 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   1499
   32 |@@                                                   64
   64 |                                                      4
  128 |                                                      1
  256 |                                                      1
  512 |                                                      0
 1024 |                                                      1
 2048 |                                                      0
 4096 |                                                      0

我们看到这个函数最大延时可达 1494 微秒,或者说接近 1.5 毫秒,还是相当可观的。这会阻塞 Nginx 事件循环,影响当前所有的并发连接上的请求延时。

而对于 apr_sdbm_fetch,有时也会接近 1 毫秒:

```
1570 samples' latency: min=5, avg=10, max=953 (us)
value |-------------------------------------------------- count
    1 |                                                      0
    2 |                                                      0
    4 |@@@@@@@@@@@@@@@@@                                   383
    8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  1080
   16 |@@@@                                                100
   32 |                                                      4
   64 |                                                      2
  128 |                                                      0
  256 |                                                      0
  512 |                                                      1
 1024 |                                                      0
 2048 |                                                      0
```

对于追求高并发和低延迟的 Nginx 而言,任何毫秒级的同步阻塞都是不容忽视的。

全面评估事件循环的阻塞程度

最后,我们对 Nginx 事件循环的整体阻塞情况进行了全面评估。

在 20 秒的采样周期内,我们捕捉到了 43952 个阻塞样本,其中单次阻塞时长的最大值竟高达 75165 微秒,也就是超过 75 毫秒

```
distribution of epoll loop blocking latencies (us): 43952 samples: min/avg/max: 754/0/75165
 value |-------------------------------------------------- count
     0 |                                                      31
     1 |                                                     149
     2 |                                                      33
     4 |                                                     166
     8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@                        6094
    16 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  10839
    32 |@@@@@@@@@@@@@@@                                     3278
    64 |@@@@@@@@@                                           2060
   128 |@@@@@@@@@@@                                         2530
   256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@     10082
   512 |@@@@@@@@@@@                                         2544
  1024 |@@@@@                                               1162
  2048 |@@@@@@@@@@                                          2343
  4096 |@@@@@@@@@@                                          2377
  8192 |                                                     199
 16384 |                                                      55
 32768 |                                                       8
 65536 |                                                       2
131072 |                                                       0
262144 |                                                       0
```

这个数据清晰地解释了为何应用的性能如此低下。当事件循环被阻塞 75 毫秒时,意味着这个 worker 进程在这段时间内完全无法处理任何新的请求。

结合下面这个样本的分析:

```
found 3237 in reqs and 3238 done reqs in 3.103 sec (1043.07 r/s and 1043.39 r/s).
in reqs:
  pid 3101: 255.21 r/s
  pid 3102: 285.82 r/s
  pid 3103: 227.82 r/s
  pid 3106: 274.22 r/s
done reqs:
  pid 3102: 286.47 r/s
  pid 3103: 227.50 r/s
  pid 3101: 254.89 r/s
  pid 3106: 274.54 r/s
```

单个 worker 进程的 RPS 仅在 227-286 之间,进程的 RPS 是⾮常低的,每秒只处理了不到 300 个请求。而机器负载已经接近 4,已经是逻辑 CPU 核的个数。这与我们观察到的严重阻塞情况完全吻合。

总结

在深入分析的过程中,我们发现了多个影响系统性能的关键问题:

  • nginx worker 进程间的 CPU 资源争用导致了大量的上下文切换,严重影响了 CPU 的有效利用率。
  • 阻塞的 Lua IO 操作,如 io.popenread,在 customize.lua 文件中被频繁调用,成为了系统性能的最大拖累。
  • apr_generate_random_bytesapr_sdbm_fetch 函数的文件 IO 操作也对事件循环造成了显著的阻塞影响。
  • Nginx 事件循环的单次阻塞时长高达 75 毫秒,直接导致了单个 worker 进程的 RPS 仅在 227-286 之间,远低于预期。

这些问题的存在,正是导致系统性能低下的根本原因。通过 OpenResty XRay 的精准诊断,我们不仅识别了这些瓶颈,还为后续的优化提供了明确的方向。

使用 OpenResty XRay 告别 Nginx 性能噩梦

OpenResty XRay 是您解决复杂性能问题的终极利器。凭借其无与伦比的动态追踪能力,我们不仅能精准定位性能瓶颈,更能为企业带来深远的价值:

  • 突破性的诊断效率:告别传统的漫长排查过程,OpenResty XRay 将问题定位时间从数天缩短至数小时,让您的团队始终快人一步。
  • 极致的硬件资源优化:通过消除性能瓶颈,单机处理能力实现数倍提升,助您以更少的服务器资源承载更大的业务负载。
  • 无缝的业务连续性保障:主动识别并消除潜在的性能隐患,确保系统稳定运行,避免因过载导致的服务中断。
  • 全面的技术债务清理:系统性地揭示并解决历史遗留的性能问题,提升代码质量和系统架构的整体健康度。

在这个以毫秒计较胜负的数字经济时代,应用性能直接影响用户体验和商业成功。OpenResty XRay 以其先进的动态追踪技术,彻底颠覆传统的性能优化方式,为您的技术决策提供坚实的科学依据。

如果您的应用正面临性能挑战,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、LuaJITGDBSystemTapLLVM、Perl 等,并编写过 60 多个开源软件库。

关注我们

如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

我们提供了英文版原文和中译版(本文)。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!