使用 eBPF 跟踪 Nginx 请求
引言
Nginx 是世界上最流行的 Web 服务器和反向代理之一,以其高性能、稳定性和低资源消耗而闻名。它广泛用于提供静态内容、负载均衡以及作为动态应用的反向代理。为了保持其性能优势,监控和优化 Nginx 的运行尤为重要,尤其是在处理大量请求时。利用 eBPF(扩展的伯克利包过滤器),可以深入了解 Nginx 的性能表现,识别瓶颈并进行优化,而无需修改源代码或重启服务。
eBPF 是一项革命性技术,允许开发人员在 Linux 内核中运行自定义程序。最初设计用于网络数据包过滤,但 eBPF 现已发展为一个多功能工具,广泛应用于跟踪、监控和分析系统行为。通过利用 eBPF,您可以跟踪 Nginx 的关键函数,测量延迟,识别瓶颈,进而优化系统性能。
背景:Nginx 和 eBPF
Nginx
Nginx 采用事件驱动架构,使其在资源占用极少的情况下能够高效处理成千上万的并发连接。这种高效性依赖于其请求处理、响应生成和事件处理等多个性能关键函数。了解这些函数在不同负载下的表现对于优化 Nginx 的使用至关重要。
eBPF
eBPF 程序在 Linux 内核的安全沙盒环境中运行。这些程序可以附加到各种钩子上,如系统调用、跟踪点,甚至可以通过 uprobes(用户级探针)附加到用户空间的函数。这使得 eBPF 成为一个强大的系统可观测性工具,可以收集详细的性能数据并实时执行策略。
eBPF 的一个常见用例是跟踪函数执行时间,以测量延迟。这对于了解 Nginx 中特定函数的执行时间特别有用,有助于诊断性能问题、优化资源使用,并提高 Nginx 部署的整体效率。
Uprobes
Uprobes 是一种用于跟踪用户空间应用程序函数的探针,它通过附加到特定用户空间函数的入口和出口点,可以捕获精确的时间信息。然而,需要注意的是,在内核模式 eBPF 运行时使用 uprobes 可能会带来一定的性能开销。为此,您可以考虑使用基于 LLVM JIT/AOT 的用户模式 eBPF 运行时 bpftime。这种运行时可以在用户空间中运行 eBPF 程序,与内核模式 eBPF 兼容,并有可能降低开销。
Nginx 的性能关键函数
以下是 Nginx 中一些性能关键的函数,可以通过 eBPF 进行监控:
- ngx_http_process_request:负责处理传入的 HTTP 请求。监控此函数有助于跟踪请求处理的开始。
- ngx_http_upstream_send_request:当 Nginx 作为反向代理时,负责向上游服务器发送请求。
- ngx_http_finalize_request:完成 HTTP 请求的处理,包括发送响应。跟踪此函数可以衡量整个请求处理的时间。
- ngx_event_process_posted:处理事件循环中的队列事件。
- ngx_handle_read_event:负责处理来自套接字的读取事件,对监控网络 I/O 性能至关重要。
- ngx_writev_chain:负责将响应发送回客户端,通常与写事件循环结合使用。
使用 bpftrace 跟踪 Nginx 函数
为了监控这些函数,我们可以使用 bpftrace
,一种 eBPF 的高级跟踪语言。以下是一个用于跟踪几个关键 Nginx 函数执行时间的脚本:
#!/usr/sbin/bpftrace
// 监控 HTTP 请求处理的开始
uprobe:/usr/sbin/nginx:ngx_http_process_request
{
printf("HTTP 请求处理开始 (tid: %d)\n", tid);
@start[tid] = nsecs;
}
// 监控 HTTP 请求的完成
uretprobe:/usr/sbin/nginx:ngx_http_finalize_request
/@start[tid]/
{
$elapsed = nsecs - @start[tid];
printf("HTTP 请求处理时间: %d ns (tid: %d)\n", $elapsed, tid);
delete(@start[tid]);
}
// 监控向上游服务器发送请求的开始
uprobe:/usr/sbin/nginx:ngx_http_upstream_send_request
{
printf("开始向上游服务器发送请求 (tid: %d)\n", tid);
@upstream_start[tid] = nsecs;
}
// 监控上游请求发送完成
uretprobe:/usr/sbin/nginx:ngx_http_upstream_send_request
/@upstream_start[tid]/
{
$elapsed = nsecs - @upstream_start[tid];
printf("上游请求发送完成时间: %d ns (tid: %d)\n", $elapsed, tid);
delete(@upstream_start[tid]);
}
// 监控事件处理的开始
uprobe:/usr/sbin/nginx:ngx_event_process_posted
{
printf("事件处理开始 (tid: %d)\n", tid);
@event_start[tid] = nsecs;
}
// 监控事件处理的完成
uretprobe:/usr/sbin/nginx:ngx_event_process_posted
/@event_start[tid]/
{
$elapsed = nsecs - @event_start[tid];
printf("事件处理时间: %d ns (tid: %d)\n", $elapsed, tid);
delete(@event_start[tid]);
}
运行脚本
要运行上述脚本,先启动 Nginx,然后使用 curl
等工具生成 HTTP 请求:
# bpftrace /home/yunwei37/bpf-developer-tutorial/src/39-nginx/trace.bt
Attaching 4 probes...
事件处理开始 (tid: 1071)
事件处理时间: 166396 ns (tid: 1071)
事件处理开始 (tid: 1071)
事件处理时间: 87998 ns (tid: 1071)
HTTP 请求处理开始 (tid: 1071)
HTTP 请求处理时间: 1083969 ns (tid: 1071)
事件处理开始 (tid: 1071)
事件处理时间: 92597 ns (tid: 1071)
该脚本监控了几个 Nginx 函数的开始和结束时间,并打印了每个函数的执行时间。这些数据可以用来分析和优化 Nginx 服务器的性能。
测试 Nginx 的函数延迟
为了更详细地分析函数延迟,您可以使用 funclatency
工具,该工具可以测量 Nginx 函数的延迟分布。以下是如何测试 ngx_http_process_request
函数的延迟:
# sudo ./funclatency /usr/sbin/nginx:ngx_http_process_request
tracing /usr/sbin/nginx:ngx_http_process_request...
tracing func ngx_http_process_request in /usr/sbin/nginx...
Tracing /usr/sbin/nginx:ngx_http_process_request. Hit Ctrl-C to exit
^C
nsec : count distribution
0 -> 1 : 0 | |
524288 -> 1048575 : 16546 |****************************************|
1048576 -> 2097151 : 2296 |***** |
2097152 -> 4194303 : 1264 |*** |
4194304 -> 8388607 : 293 | |
8388608 -> 16777215 : 37 | |
Exiting trace of /usr/sbin/nginx:ngx_http_process_request
结果总结
上述结果显示了 ngx_http_process_request
函数的延迟分布。大多数请求在 524,288 至 1,048,575 纳秒内处理完成,少部分请求处理时间更长。这些信息对于识别性能瓶颈和优化 Nginx 请求处理至关重要。
通过使用 funclatency
,您可以:
- 识别性能瓶颈:了解哪些函数执行时间最长,并将优化工作重点放在这些函数上。
- 监控系统性能:定期监控函数延迟,确保在高负载下 Nginx 服务器的最佳性能。
- 优化 Nginx 配置:利用延迟测量得出的洞察调整 Nginx 设置或修改应用程序,以提高整体性能。
您可以在 bpf-developer-tutorial 仓库 中找到 funclatency
工具。
结论
通过 eBPF 跟踪 Nginx 请求可以为您的 Web 服务器提供宝贵的性能洞察,使您能够监控、分析和优化其操作。使用 bpftrace
和 funclatency
等工具,您可以测量函数执行时间、识别瓶颈,并根据数据做出决策来改进 Nginx 部署。
如果您有兴趣了解更多关于 eBPF 的知识,包括更多高级示例和教程,请访问我们的 https://github.com/eunomia-bpf/bpf-developer-tutorial 或查看我们的网站 https://eunomia.dev/tutorials/。