了解 OpenResty XRay 是如何做到帮助企业定位应用程序存在的问题以及优化其效率的。

了解更多 LIVE DEMO

对于像 OpenResty 和 Nginx 这样的非阻塞 Web 服务器来说,消耗大量 CPU 资源是很常见的。这要归功于操作系统特性如 epollkqueue 的 I/O 多路复用功能。有时,对于 DevOps 和 SRE 人员来说,快速精确地找出在线服务器或多个服务器中消耗最多 CPU 时间 的请求 URI 或请求主机名是很有帮助的。在本文中,我们将演示如何使用 OpenResty XRay 中的动态追踪工具,实时分析未经修改的 OpenResty 和 Nginx Web 服务器以获取此类统计数据。

我们将使用标准动态追踪工具和通过类 SQL 语言(称为 YSQL)创建的自定义工具,展示真实世界的示例,包括由 OpenResty XRay 自动生成的数据和图表。

系统环境

在此,我们以 Red Hat Enterprise Linux 7 系统为例。任何受 OpenResty XRay 支持的 Linux 发行版都应同样适用,如 Ubuntu、Debian、Fedora、Rocky、Alpine 等。

我们使用未经修改的开源 OpenResty 二进制构建作为目标应用。您可以使用任何 OpenResty 或 Nginx 二进制文件,包括您自己编译的版本。不会向现有的运行进程插入代码,也不要求软件编译带上特殊的编译选项,或者加载特定的插件或者共享库文件。这正是 动态追踪 技术的优势所在。它是真正非侵入式的。

我们还在同一系统上运行 OpenResty XRay 的 Agent 守护进程,并已 安装和配置openresty-xray-cli 包中的命令行工具。

CPU 占用最高的请求主机名

使用标准工具

最便捷的方法是直接运行标准工具 ngx-cpu-hottest-hosts

我们首先需要找出 OpenResty 或 Nginx 服务器实例的主进程 PID。

$ ps aux | grep nginx:
root     1691450  0.0  0.0  28868  4140 ?        Ss   Jul05   0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
nobody   3055159  1.5  0.0  40868  4096 ?        S    14:38   4:58 nginx: worker process

主进程的 PID 是 1059。我们使用这个 PID 来追踪该进程组中的所有进程,包括 Nginx 工作进程。

$ orxray analyzer run ngx-cpu-hottest-hosts -p -1691450 -t 10
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/582346 for charts

在此,我们使用 orxray 命令行工具来运行标准工具 ngx-cpu-hottest-hosts,针对由主进程 PID 1059 指定的进程组,通过 -p 选项进行操作。请注意 PID 前的减号,它表示我们要实时跟踪该进程的整个进程组。同时,-t 选项指定了我们希望跟踪的秒数。作为一般经验法则,当目标应用不太繁忙时,我们应使用较长的采样时间窗口;而对于较繁忙的应用,则使用较短的窗口。

上述输出显示了一个链接,指向 OpenResty XRay 的 Web 控制台,我们可以在那里看到为此次运行生成的精美图表。不过,您的 Web 控制台 URI 可能会有所不同。

我们可以看到,最热门的是 openresty.org 主机名。其次是 openresty.com。请记住,我们这里只计算了命中操作系统 CPU 分析器 的请求,而不是所有请求。因此,只有相对数值才有意义。例如,考虑到它们的样本计数分别为 733 和 612,openresty.org 消耗的 CPU 时间比 openresty.com 多约 19%。

有时,我们可能只想分析单个 Nginx 工作进程,这种情况可能发生在某个工作进程消耗的 CPU 时间比其他进程多,或者我们想要最小化引入的跟踪开销时。在这种情况下,我们可以使用该工作进程的 PID 作为 orxray 命令的 -p 选项的值,如下所示:

$ orxray analyzer run ngx-cpu-hottest-hosts -p 3055159 -t 10

这次请务必省略 PID 前的减号(-)。

默认情况下,该工具分析当前机器上的进程。如果您想分析其他服务器上的进程,可以添加 -a agent_ID 选项来指定您想要运行的服务器。只需使用 orxray agent list 命令获取您的 OpenResty XRay Web 控制台可见的代理 ID 列表。

使用 YSQL 创建自定义工具

使用名为 YSQL 的类 SQL 语言创建自定义动态跟踪工具可以获得最大的灵活性,这更有趣。YSQL 语言从不用于查询任何关系数据库;相反,它始终被编译为动态跟踪工具,用于对实时进程和运行中的应用进行实时检查和分析。

让我们创建一个名为 my-cpu-hottest-hosts.ysql 的纯文本文件,内容如下。请随意使用您喜欢的代码编辑器。

select count(*) count, host
from cpu.profile inner join ngx.reqs
group by host
order by count desc
limit 10;

SQL 查询大部分是自解释的。最有趣的部分是 from 子句,它使用 inner join 将 Nginx 请求与操作系统的 CPU 分析器进行计数。CPU 分析器对应于虚拟表 cpu.profilehost 列是来自 HTTP host 头的值。我们添加了 limit 10 子句,因为我们只关心前 10 个。

现在让我们运行这个 YSQL 工具。假设工作进程的 PID 是 3055159,我们有以下命令。

$ run-ysql -p 3055159 ./my-cpu-hottest-hosts.ysql -t 10
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/583188 for charts

请注意,这次我们使用 run-ysql 命令行工具。

我们可以浏览网页链接,查看与上面标准工具类似的输出图表。

单行 YSQL

我们还可以将 YSQL 作为单行命令运行,无需创建本地文件。

$ run-ysql -p 3055159 -t 10 -e 'select count(*) count, host from cpu.profile inner join ngx.reqs group by host order by count desc limit 10;'

CPU 开销最高的请求 URI

我们还可以追踪目标进程中 CPU 占用最高的请求 URI。

使用标准工具

我们可以运行标准工具 ngx-cpu-hottest-uris:

$ orxray analyzer run ngx-cpu-hottest-uris -p 3055159 -t 10
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/622478 for charts

我们可以看到 CPU 占用最高的前两个请求 URI 分别是 openresty.org/openresty.com/en

创建自定义 YSQL 工具

这次的 YSQL 查询略有不同。我们使用 uri 列代替。

select count(*) count, host, uri
from cpu.profile inner join ngx.reqs
group by host, uri
order by count desc
limit 10

现在让我们运行这个 YSQL 工具。假设工作进程的 PID 是 3055159,我们有以下命令。

$ run-ysql -p 3055159 ./my-cpu-hottest-uris.ysql -t 10
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/622204 for charts

我们可以浏览网页链接,查看与上述自定义工具类似的输出图表。

深入探究

主机名或 URI 占用更多 CPU 资源的一个自然原因是它们的请求数量比其他的多。我们可以通过在一个时间窗口内按主机名或 URI 分组计算所有请求来验证这一点。

请求最多的最繁忙主机名

使用标准工具

我们可以使用标准工具 ngx-req-counts-by-hosts 进行计数。

$ orxray analyzer run ngx-req-counts-by-hosts -p 3055159
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/584324 for charts

我们可以按照指示浏览网页:

我们可以看到,排名第一的域名 openresty.org 也拥有最多的请求。但第二位是 doc.openresty.com 而不是 openresty.com。这意味着平均而言,openresty.com 的每个请求可能比 doc.openresty.com 的请求消耗更多的 CPU 时间。

使用 YSQL 创建自定义工具

仅出于演示目的,我们可以创建一个简单的 YSQL 工具文件,以创建一个模拟标准工具 ngx-req-counts-by-hosts 的自定义工具:

select count(*) count, host
from ngx.reqs
group by host
order by count desc
limit 10;

请注意 from 子句。我们不再与虚拟表 cpu.profile 进行 inner join。因此,现在我们统计的是在采样时间窗口内 Nginx 或 OpenResty 处理的所有请求。

现在让我们针对 PID 为 3055159 的 Nginx 工作进程运行这个 YSQL 工具。

$ run-ysql -p 3055159 ./top-10-hosts-req.ysql
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/584615 for charts

我们将得到一个类似于上面所示条形图的图表。

网络数据量最大的最繁忙主机名

我们还有 ngx-req-size-by-hosts 标准工具,用于采样具有大量累积网络流量数据(或请求大小,包括请求头和请求体)的最繁忙请求主机名。

$ orxray analyzer run ngx-req-size-by-hosts -p 3055159
Start tracing...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/584675 for charts

我们可以看到,openresty.orgdoc.openresty.com 也占据了大部分网络数据量,这与请求计数的情况类似。

一个自定义的 YSQL 工具可能如下所示:

select sum(req_size) request_size, host
from ngx.reqs
group by host
order by request_size desc
limit 10;

来自 ngx.reqs 虚拟表的 req_size 列代表总请求大小(请求头 + 请求体)。但它不包含任何 TLS/SSL 握手流量。

查找瓶颈并进行优化

为了分析具体的性能瓶颈并获得优化建议,我们可以进一步使用 C 层面和 Lua 层面的 CPU 火焰图工具。

OpenResty XRay 可以自动分析任何繁忙的应用(不仅限于 OpenResty 和 Nginx 应用!),我们的人类专家还可以提供包含可执行建议的丰富分析报告。这样,普通用户甚至不需要知道何时何地运行什么工具。他们也不需要解释分析器的输出。

直接在 Web 控制台中运行

用户可以选择直接在 OpenResty XRay 的 Web 控制台中执行本教程中涉及的任何工具。它们甚至可以在高 CPU 使用率等有趣事件发生时自动触发。openresty-xray-cli 中的命令行实用程序对于演示目的很方便。它们也易于自动化,并可由 DevOps 和 SRE 人员集成到其他系统中。

跟踪容器内的应用

OpenResty XRay 工具支持透明地跟踪容器化应用。Docker 和 Kubernetes(K8s)容器都可以透明地工作。与普通应用进程一样,目标容器不需要任何应用或额外权限。OpenResty XRay Agent 守护进程应该在目标容器外部运行(例如直接在主机操作系统中或在其自己的特权容器中)。

让我们看一个例子。我们首先使用 docker ps 命令检查容器名称或容器 ID。

$ docker ps
CONTAINER ID   IMAGE                                       COMMAND                  CREATED         STATUS          PORTS     NAMES
4465297209d9   openresty/openresty:1.19.3.1-2-alpine-fat   "/usr/local/openrest…"   18 months ago   Up 11 minutes             angry_mclaren

这里的容器名称是 angry_mclaren。然后我们可以找出此容器中目标进程的 PID。

$ docker top angry_mclaren
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                3310154             3310133             0                   14:22               ?                   00:00:00            nginx: master process /usr/local/openresty/bin/openresty -g daemon off;
nobody              3310209             3310154             0                   14:22               ?                   00:00:00            nginx: worker process

openresty 工作进程的 PID 是 3310209。然后我们像往常一样针对这个 PID 运行 OpenResty XRay 分析器。

$ orxray analyzer run ngx-cpu-hottest-hosts -p 3310209 -t 10
Start tracing...
...
Go to https://x5vrki.xray.openresty.com.cn/targets/68/history/600752 for charts

OpenResty XRay 还能够自动检测长时间运行的进程,并将其识别为特定类型的"应用"(如 “OpenResty”、“Python” 等)。

工具的实现方式

所有工具都是使用 Y 语言 实现的。OpenResty XRay 通过 Stap+1eBPF2 后端执行这些工具,这两种后端都使用了 100% 非侵入式的动态追踪技术,基于 Linux 内核的 uprobeskprobes 功能。YSQL 查询首先被编译成 Y 语言,然后进一步编译成可执行的动态追踪工具。

我们不需要目标应用和进程的任何配合。我们不使用也不需要任何日志数据或指标数据。我们直接以严格的只读方式分析运行中进程的进程空间。而且,我们绝不会向目标进程注入任何字节码或其他可执行代码。这是 100% 干净和安全的。

工具的开销

本教程中演示的动态追踪工具非常高效,适合在线执行。

当工具未运行且不进行主动采样时,对系统和目标进程的开销严格为。我们从不向目标应用和进程注入任何额外的代码或插件;因此,不存在固有的开销。

在采样期间,在典型的服务器硬件上,请求延迟平均仅增加不到 1 微秒(us)。对于每个 CPU 核心每秒处理数万个请求的最快 OpenResty/Nginx 服务器来说,最大请求吞吐量的降低也仅约为 4%。

关于作者

章亦春是开源 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. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

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


  1. Stap+ 是 OpenResty Inc 对 SystemTap 的大幅增强版本。 ↩︎

  2. 这实际上是 OpenResty Inc 大幅增强的 eBPF 实现,称为 ORBPF。 ↩︎