eBPF 实战:一次诡异的 Nginx 高延迟,我用 5 分钟在内核里找到了真凶

eBPF 实战:一次诡异的 Nginx 高延迟,我用 5 分钟在内核里找到了真凶

没有玄学,只有内核里的铁证如山。
P99延迟无故飙升,CPU空闲,网络通畅,文件I/O正常------那一刻,我怀疑自己遇到了灵异事件。

01 诡异的下午:什么都没变,服务却慢了10倍

那天下午,监控告警突然响起:线上Nginx网关的P99延迟从稳定的50ms飙升至500ms以上。

第一反应是查看常规指标:

  • CPU:idle 30%以上,没爆。
  • 内存:充足,无swap。
  • 网络:带宽未打满,TCP重传率正常。
  • 磁盘I/O:util 5%,几乎没有读写。
bash 复制代码
# 传统三板斧
top        # CPU空闲
free -m    # 内存充足
iostat -x 1 # I/O空闲
ss -s      # 连接数正常,TIME-WAIT不多

诡异之处:服务没重启,流量没突增,代码没发布,但延迟就是高了。这种"三无"问题,就像家里没开大功率电器,电表却飞转,让人无从下手。

02 常规手段失效:黑盒排查的尽头

既然外围指标正常,只能钻进内核看个究竟。此时,eBPF是我最后的希望------无需修改代码,无需重启服务,直接在内核里装"监控探头"。

第一板斧:BCC工具集,快速画像

我首先祭出BCC工具集(BPF Compiler Collection),几个命令迅速定位异常进程:

bash 复制代码
# 追踪新进程创建(怀疑有短时进程耗资源)
execsnoop    # 无异常,没有频繁的进程创建

# 追踪文件打开(怀疑读配置频繁)
opensnoop -n nginx  # 正常,只打开几次

# 追踪慢文件系统操作(看是不是I/O慢)
ext4slower   # 无慢查询,排除文件系统

一通操作下来,依旧毫无头绪。Nginx worker进程既没有疯狂开文件,也没有频繁fork,那延迟到底卡在哪里?

第二板斧:bpftrace,给内核函数上"秒表"

既然宏观工具看不清,我决定用bpftrace直接测量内核函数耗时。在Linux内核网络栈中,accept系统调用是TCP连接建立的最后一步,如果这里变慢,请求必然延迟。

写一个bpftrace脚本,测量Nginx worker进程执行accept函数的耗时:

bash 复制代码
#!/usr/bin/env bpftrace

uprobe:/usr/sbin/nginx:ngx_event_accept
{
    @start[pid] = nsecs;
}

uretprobe:/usr/sbin/nginx:ngx_event_accept
/@start[pid]/
{
    $time = (nsecs - @start[pid]) / 1000;  # 微秒
    @accept_us = hist($time);
    delete(@start[pid]);
}

执行脚本,等待几秒,查看直方图:

bash 复制代码
bpftrace accept_latency.bt
^C
@accept_us:
[0]                 3120 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1]                 1023 |@@@@@@@@@@@@@@@                                     |
[2, 4)               321 |@@@@                                                |
[4, 8)                87 |@                                                   |
[8, 16)               12 |                                                    |
[16, 32)             522 |@@@@@@@@                                            |  <-- 居然有大量耗时超过16微秒的调用!
[32, 64)            1502 |@@@@@@@@@@@@@@@@@@@@@@@                             |  <-- 甚至超过32微秒!

真相初现 :正常情况下,accept只是一次内存拷贝,应该稳定在1微秒以内。但此时出现了大量超过16微秒的调用,最慢的甚至超过64微秒------accept函数本身变慢了

03 顺藤摸瓜:谁在拖延accept?

accept慢,说明内核在接受连接时被阻塞了。继续向下追踪:在Linux内核中,accept最终会调用inet_csk_accept。我再次用bpftrace挂载这个内核函数:

bash 复制代码
bpftrace -e 'kprobe:inet_csk_accept { @start[pid] = nsecs; }
    kretprobe:inet_csk_accept /@start[pid]/ {
        @accept_latency = hist(nsecs - @start[pid]); delete(@start[pid]); }'

结果与用户态一致:内核函数同样存在延迟尖峰。这说明,阻塞发生在内核更深处

查阅内核源码(顺带一提,Nginx社区也有关于listen socket和eBPF的讨论),怀疑对象指向监听队列锁竞争 。在inet_csk_accept函数中,获取accept_queue锁的竞争可能导致进程睡眠等待。

为了验证锁竞争,我尝试挂载内核中的锁函数:

bash 复制代码
# 追踪锁等待时间(理论示例,实际需根据内核版本调整)
bpftrace -e 'kprobe:spin_lock { @lock_wait = hist(nsecs - ...); }'

虽然锁的追踪较为复杂,但结合上下文,高并发下,多个worker进程争抢同一个监听套接字的队列锁,导致部分进程休眠等待 ,从而引发accept延迟。这才解释了为什么P99飙升而CPU空闲------线程在等锁,不是在跑指令。

04 破案:SO_REUSEPORT的代价

真凶锁定 :Nginx采用了SO_REUSEPORT多进程监听同一端口,内核负载均衡将新连接分发到多个worker。但在极端并发下,内核内部的队列锁(如listen socket的syn queue和accept queue锁)成为争抢热点 。当某个worker运气不佳,被调度到与其它worker同时争锁,就会进入休眠等待,导致accept调用延迟数十微秒,进而拉高整体P99延迟。

05 复盘:eBPF为何能破案?

回顾整个过程,eBPF的价值在于提供了无侵入的内核上下文观测能力

  1. 全栈可见:从用户态函数(ngx_event_accept)到内核态函数(inet_csk_accept),再到潜在的锁竞争,完整追踪调用链路。
  2. 精准量化:不是泛泛地看CPU使用率,而是精确测量每一个关键函数的耗时分布。
  3. 低 overhead:生产环境也可用,只采集必要数据,不拖垮系统。

写在最后

那次故障之后,我对"黑盒"二字有了新的理解。所谓黑盒,不是内核真的不可知,而是我们手头没有合适的工具去观察它。eBPF就像一束光,照进了内核的每一个角落。

当你下次再遇到"什么都没变,但服务就是慢了"的灵异事件时,不妨问问自己:是时候看看内核里发生什么了。因为在这个时代,没有真正的黑盒,只有尚未使用的eBPF。


小提示 :如果你也遇到了类似问题,不妨从execsnoopopensnoopxfsslower等BCC工具开始,再用bpftrace深入函数级耗时分析。内核里没有秘密,只看你愿不愿意去寻找。

相关推荐
悠悠1213811 分钟前
微服务通信别只看性能:REST 与 gRPC 的 6 个选型判断和一次 502 故障复盘
运维·服务器
景熙552338 分钟前
15.Java 8 Stream 流入门到实战
java·开发语言·数据结构
wno7041 小时前
Spring Security权限控制
java·python·spring
杨运交2 小时前
[071][验证码模块]基于Spring拦截器的验证码认证设计思想
java·后端·spring
Geek-Chow2 小时前
CountDownLatch in Java
java
zx_741484812 小时前
【Linux入门】Shell 函数、正则表达式与文本处理:cut 与 awk
linux·运维·正则表达式
Zenova EdgeOS2 小时前
Linux dmesg 工业边缘实战:内核日志过滤、持久化与故障定位
linux·运维·边缘计算·工业边缘·dmesg·内核日志
SL_staff2 小时前
从RBAC到场景化授权:《无忧·企业文档》三级权限模型的技术实践解析
java·开源·产品
传奇开心果编程3 小时前
【springboot基础语法学与练】第 1 课:从零开始
java·spring boot·后端·学习
wdfk_prog3 小时前
ROS教程08:从 TransportTCP::connect() 追到 TCPROS Connection Header、序列化与 Socket 数据传输
运维·缓存·docker·容器·ros