实战揭秘:用 eBPF 揪出隐藏的性能杀手

话题来源: 微服务治理新范式:eBPF如何实现无侵入可观测性?

说起这个话题,我至今还记得那个让人抓狂的深夜。线上业务突然开始间歇性抖沙,延迟偶尔飙到3秒,但现有的监控面板一片祥和——CPU、内存、磁盘IO全是绿的。APM工具显示的调用链路也正常,唯独有几个请求像被打了沉默术一样,响应时间莫名其妙地多了一截。我当时真想砸电脑,直到我祭出了eBPF这把瑞士军刀,才发现原来杀手藏在内核网络栈的最深处。

隐藏的杀手:TCP重传风暴

那天我用tcpretrans-bpfcc这个BCC工具扫了一眼,结果差点没坐稳——每秒有上千个TCP重传包!但奇怪的是,业务日志里完全没提到重传,因为应用层根本感知不到这种内核级别的重试。重传意味着数据包丢了,但为什么丢?我接着用tcpdrop-bpfcc追踪被内核丢弃的包,发现目标端口都是同一台Redis的6379。原来Redis节点所在宿主机的网卡驱动有个bug,在特定流量模型下会随机丢包,而Redis客户端库的默认超时设置是2秒,导致某些请求在重传超时后才返回。

我是怎么定位的?

  1. 安装BCC并启用tcpretrans

在Ubuntu 20.04上,一行命令就能跑起来:

    sudo tcpretrans-bpfcc

输出会列出每个重传的五元组和重传次数。我过滤出目标端口6379,看到重传间隔竟然是200ms、400ms、800ms……典型的指数退避,说明是TCP层在反复重试。

  1. 用tcpdrop抓丢弃事件

接着运行sudo tcpdrop-bpfcc,打印内核丢弃TCP包的原因。大部分显示“参考机型/驱动层丢包”,再配合ethtool -S eth0看网卡统计,才确认是驱动层ring buffer溢出。

  1. 验证根因

手动在Redis节点上用perf record -e skb:kfree_skb采样,发现丢包集中在某个IRQ处理函数中。最终升级网卡驱动后,问题消失。

为什么传统工具看不见?

当时我们已经在用Prometheus监控网络指标,但node_network_drop在那个内核版本下没有暴露驱动层丢包。而应用层的APM只能看到请求变慢,无法区分是内核在重传还是应用在阻塞。eBPF直接挂载到tcp_droptcp_retransmit_skb内核函数,相当于在犯罪现场装了针孔摄像头——内核丢一个包,它就弹一个告警。

一点小建议

生产环境用eBPF时,别一上来就全量跑那种复杂的追踪程序。我通常先跑几个轻量级的BCC工具(tcpretransoomkillbitesize)做“健康扫描”,再根据线索写定制化的eBPF代码。比如那次排查,我只花了10分钟就锁定了网卡驱动问题,而之前用传统方法折腾了整整两天。现在每当我听到“找不到性能瓶颈”时,总会嘴角上扬——别忘了,内核里还藏着很多监控盲区呢。

评论

  • 半夜看到 TCP 重传风暴那段简直头皮发麻,太真实了。

  • 所以这工具在 CentOS 7 这种老内核上能直接跑吗?还是得升级?