Linux磁盘I/O高延迟排查必备:用bpftrace从内核揪出真凶

当Linux服务器出现磁盘I/O高延迟,传统工具iostat只能告诉你“慢了”,却说不清“谁”在慢。本文以小白视角,深入浅出解释eBPF和bpftrace的原理,并通过具体命令一步步演示如何追踪块设备请求、定位导致延迟的进程或内核路径,让排查不再靠猜。

Linux磁盘I/O高延迟排查必备:用bpftrace从内核揪出真凶
封面图:ZuCDN · ZuCDN 原创

磁盘I/O慢得像老牛拉车,你该怎么办?——Linux bpftrace

故障定位思路

折腾Linux bpftrace时,我发现最麻烦的往往不是安装,而是配置。线上服务器突然响应变慢,数据库查询超时,应用日志里全是“等待磁盘I/O”的告警。你熟练地敲下iostat -x 1,看到await飙到几百毫秒,%util接近100%——磁盘确实成了瓶颈。可接下来呢?iostat只能告诉你“磁盘慢了”,却无法回答“是哪个进程导致的?”“是读还是写?”“延迟发生在驱动程序层还是硬件层?”

传统排查手段要么信息太粗(如iostatsar),要么侵入性太强(如strace会拖慢进程)。有没有一种工具,既能深入内核追踪每一个I/O请求的生命周期,又几乎不影响线上性能?答案就是bpftrace——基于eBPF的动态追踪利器。

为什么传统工具很难定位根因?

配置前的检查

我们先看一张I/O请求的简化路径图:

  • 用户态进程发起read()/write()系统调用
  • 经过VFS层、文件系统层、块设备层(Block Layer)
  • 最终通过驱动程序提交给磁盘硬件

iostat是从/proc/diskstats读取累计计数器,每秒钟一次采样。它能给出平均延迟、队列长度、IOPS等,但无法告诉你:

  • 延迟的分布是均匀还是两极分化?
  • 高延迟是发生在排队等待还是硬件处理?
  • 具体哪个进程在大量写数据?

于是我们常常陷入“调优参数靠感觉”的困境。

eBPF和bpftrace到底是什么?

配置前的检查

eBPF(extended Berkeley Packet Filter)是一项革命性的内核技术,它允许用户在Linux内核中安全地运行沙箱化的程序,而不需要修改内核源码或加载内核模块。你可以把它想象成“内核里的JavaScript”——编写的小程序可以在特定事件(系统调用、函数入口/出口、网络包到达、块设备请求等)触发时执行,收集数据。

bpftrace则是基于eBPF的高级追踪语言,类似于awk的语法。你只需写一两行命令,就能定义要追踪的内核探针、提取变量、输出统计结果。它属于只读追踪,不会修改内核行为,生产环境也可以放心使用(但注意高频率追踪可能增加少量CPU开销)。

Linux bpftrace:安装bpftrace

实际操作要点

大多数现代发行版(Ubuntu 18.04+、CentOS 7+、Debian 10+)都可以直接安装:

# Ubuntu / Debian
sudo apt-get install bpftrace

# CentOS / RHEL 8+
sudo dnf install bpftrace

# 也可以从源码或官方GitHub release安装

安装后验证:sudo bpftrace --version

实战:用bpftrace追踪磁盘I/O延迟

1. 查看块设备请求的延迟分布(直方图)

这是最直接的切入方式。bpftrace提供了一款生产级脚本blkflow.bt(位于/usr/share/bpftrace/tools/),不过为了说明原理,我们自己写一个简单的one-liner:

sudo bpftrace -e 'kfunc:blk_account_io_done { @ = hist(args->rq_end_io_time - args->rq_start_time_ns); }' -c 'sleep 5'

解释

  • kfunc:blk_account_io_done:内核函数,在块设备I/O完成时被调用。我们用内核动态插桩(kfunc)追踪它。
  • args->rq_end_io_time - args->rq_start_time_ns:计算请求完成时间减去开始时间,得到延迟(纳秒)。
  • @ = hist(...):将延迟数据放入一个直方图变量@中。
  • -c 'sleep 5':追踪5秒后退出。

输出示例:

@:
[1M, 2M)             120 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[2M, 4M)              45 |@@@@@@@@@@@@@@@@@@@                               |
[4M, 8M)              12 |@@@@@                                              |
[8M, 16M)              3 |@                                                |
[16M, 32M)             1 |                                                    |

这里单位是微秒(1M=1000微秒=1毫秒)。可以看到大部分请求在1-2ms完成,但少数请求延迟高达16-32ms。如果直方图出现明显的“长尾”,说明存在周期性高延迟,可能是磁盘硬件队列深度或文件系统锁的问题。

2. 找出延迟最高的具体进程

光知道延迟分布还不够,我们要找到“肇事”进程。bpftrace可以同时捕获进程的PID和进程名:

sudo bpftrace -e 'kfunc:blk_account_io_done {
    $lat = args->rq_end_io_time - args->rq_start_time_ns;
    if ($lat > 10000000) {  // 延迟超过10ms才记录
        printf("PID %d (%s) delay %lld usn", pid, comm, $lat / 1000);
    }
}' -c 'sleep 10'

这个脚本会实时打印出那些I/O延迟超过10ms的进程信息。你可以结合ps或者top判断该进程为什么会有大量磁盘访问。

3. 区分读延迟和写延迟

磁盘读和写的延迟特征不同,写操作可能因为回写缓存、磁盘缓存策略而表现不同。我们可以通过args->rq_data_lenargs->rq_opf来区分读写:

sudo bpftrace -e 'kfunc:blk_account_io_done {
    $op = args->rq_opf & 1;
    $lat = args->rq_end_io_time - args->rq_start_time_ns;
    if ($op == 0) { @read_hist = hist($lat); }
    else { @write_hist = hist($lat); }
}' -c 'sleep 10'

分别输出读延迟直方图和写延迟直方图,帮你判断性能瓶颈类型。

4. 按设备号统计延迟

如果系统有多块磁盘(比如SSD+HDD),或者使用LVM、RAID,你可能想知道哪一块盘延迟高。可以按设备主次设备号分组:

sudo bpftrace -e 'kfunc:blk_account_io_done {
    @dev_lat[args->rq_dev] = hist(args->rq_end_io_time - args->rq_start_time_ns);
}' -c 'sleep 5'

设备号可以通过ls -l /dev/sda查看(比如8:0)。

从数据到根因:三步定位法

我的处理经验

拿到bpftrace的输出后,按照以下思路分析:

  1. 看延迟长尾:如果直方图有远离主峰的小尾巴,说明存在偶尔的慢请求。慢请求通常由磁盘内部碎片整理、NCQ队列管理等引起,可以尝试调整磁盘调度器(如mq-deadline改为none)或增加IOPS。
  2. 看延迟最高的进程:如果是数据库进程(例如mysqld、postgres),可能是某个慢查询全表扫描;如果是日志进程(rsyslogd),可能是大量日志写入导致磁盘饱和。结合iostat -x 1 -p sda确认%util是否很高。
  3. 区分读写行为:如果写延迟明显高,检查磁盘是否启用了Write Cache(hdparm -W /dev/sda),或者考虑调整文件系统的挂载参数(如noatime)。如果读延迟高,检查是否有大量随机读(可以再用bpftrace追踪平均请求大小)。

生产环境安全回滚

配置前的检查

bpftrace脚本是只读的,不会修改内核数据。但注意:

  • 高频率追踪(如每次I/O都printf)会增加CPU开销,建议添加过滤条件(如只追踪延迟>阈值)或设置采样。
  • 使用-c指定运行时间,或者用Ctrl+C手动停止,不要一直跑在后台。
  • 如果怀疑某个bpftrace脚本导致系统异常,直接重启服务或卸载ceph等工具,实际上只需要停止bpftrace进程即可恢复,无需回滚内核。

总结

验证与回滚

bpftrace让Linux磁盘I/O延迟排查从“黑盒统计”升级为“白盒跟踪”。你不再需要猜测,而是可以直接看到内核里每一个I/O请求的出生和死亡。本文演示的只是冰山一角——你还可以追踪VFS层、文件系统、SCSI驱动等更深层次的函数。掌握bpftrace,等于拥有了一把透视服务器内核的钥匙。

下次再遇到磁盘I/O高延迟,别急着怀疑硬件坏了,先跑一个bpftrace直方图,让数据说话。后续只要定期检查关键指标,Linux bpftrace就不会变成维护负担。

延伸阅读