Linux IO性能瓶颈排查全攻略:从理论到实战的系统性解决方案

引言

当你发现生产环境某些IO进程读写效率下降,例如:

  1. MySQL查询耗时增高
  2. 文件读写效率变慢

此时我们就可以考虑Linux系统是否存在IO性能瓶颈了,而以下便是笔者整理的一套比较普适的IO性能瓶颈通用排查方法论,同时为了更好地复现这个问题,笔者也用Java写了一个多线程执行数据读写的程序,读者可以查看如下代码结合注释了解一下这个逻辑:

java 复制代码
/**
 * 启动磁盘I/O操作以模拟高I/O负载
 * 通过创建多个I/O任务线程来模拟高磁盘I/O负载
 */
private static void startDiskIOOperations() {
    log.info("开始高I/O磁盘操作...");
    log.info("在另一个终端中运行 'iostat -x 1' 来监控磁盘利用率。");

    // 创建固定线程数的线程池
    executor = Executors.newFixedThreadPool(NUM_THREADS);

    // 提交多个任务以连续写入磁盘
    for (int i = 0; i < NUM_THREADS; i++) {
        executor.submit(new IOTask(i));
    }

    log.info("磁盘I/O操作已启动,使用 {} 个线程", NUM_THREADS);
}

/**
 * 执行连续写入操作以模拟高I/O的任务
 * 该类负责执行磁盘I/O操作,通过不断写入和清空文件来模拟高I/O负载
 */
static class IOTask implements Runnable {
    private final int taskId;

    public IOTask(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public void run() {
        // 每个线程写入自己的临时文件
        String filename = "/tmp/disk_io_test_" + taskId + ".tmp";

        try (FileOutputStream fos = new FileOutputStream(filename)) {
            log.info("线程-{} 正在写入 {}", taskId, filename);

            // 连续将数据写入文件并在每次写入后清空文件
            while (!Thread.currentThread().isInterrupted()) {
                performDiskIOOperation(fos, taskId);
                ThreadUtil.sleep(500);
            }
        } catch (IOException e) {
            log.error("线程-{} 发生错误: {}", taskId, e.getMessage());
        }
    }
}

/**
 * 执行磁盘I/O操作:写入指定大小的数据然后清空文件
 * 该方法会连续写入数据到文件,然后清空文件内容,用于模拟高I/O负载
 * @param fos 文件输出流
 * @param taskId 任务ID
 * @throws IOException IO异常
 */
private static void performDiskIOOperation(FileOutputStream fos, int taskId) throws IOException {
    long startTime = System.currentTimeMillis();

    // 写入数据(分块写入)
    long bytesWritten = 0;
    while (bytesWritten < WRITE_SIZE) {
        fos.write(DATA);
        bytesWritten += DATA.length;
    }
    fos.flush(); // 强制写入磁盘

    // 清空文件内容
    fos.getChannel().truncate(0);
    long endTime = System.currentTimeMillis();

    // 打印本次操作的耗时
    log.info("线程-{} 完成一次写入和清空操作,耗时: {} ms", taskId, (endTime - startTime));
}

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

详解Linux系统IO性能问题排查通用方法论

检查服务器负载

当我们认为存在IO瓶颈时,首先要做的就是基于top指令查看当前服务器wa即CPU等待IO任务完成的占比,一般情况下20%以下算是一个比较合理的正常阈值,超过30%-40%则表明系统可能存在严重的IO瓶颈。以笔者的服务器为例,可以看到wa的值远大于正常范围,说明当前CPU基本处于等待IO任务完成的情况:

shell 复制代码
Tasks:  34 total,   1 running,  33 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.5 us,  2.6 sy,  0.0 ni,  5.3 id, 90.5 wa,  0.0 hi,  1.1 si,  0.0 st 
%Cpu1  :  0.0 us,  2.2 sy,  0.0 ni, 24.9 id, 72.4 wa,  0.0 hi,  0.5 si,  0.0 st 
%Cpu2  :  1.1 us,  0.6 sy,  0.0 ni,  0.6 id, 97.7 wa,  0.0 hi,  0.0 si,  0.0 st 
%Cpu3  :  0.5 us,  2.7 sy,  0.0 ni, 16.8 id, 80.0 wa,  0.0 hi,  0.0 si,  0.0 st 
%Cpu4  :  0.6 us,  1.7 sy,  0.0 ni,  0.0 id, 97.8 wa,  0.0 hi,  0.0 si,  0.0 st 
%Cpu5  :  0.0 us,  3.9 sy,  0.0 ni, 18.8 id, 77.3 wa,  0.0 hi,  0.0 si,  0.0 st

查看IO使用率

明确系统存在IO性能瓶颈的情况下,我们就需要更进一步定位问题,以笔者为例,一般会执行iostat指令,如下所示,大意为:

  1. -x:显示更多扩展信息(包括设备利用率、等待时间等)

  2. 每1秒输出1次,持续输出

    iostat -x 1

从输出结果来看,对应sdd盘使用率%util(IO利用率)飙到100%且iowait达到了78.2%,很明显这块磁盘存在一些异常IO读写任务:

bash 复制代码
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.0%    0.0%    1.0%   78.2%    0.0%   20.8%

Device            r/s     rMB/s   rrqm/s  %rrqm r_await rareq-sz    w/s     wMB/s   wrqm/s  %wrqm w_await wareq-sz    d/s     dMB/s   drqm/s  %drqm d_await dareq-sz  f_await  aqu-sz  %util
sda            0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00    0.00    0.00   0.00
sdb            0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00    0.00    0.00   0.00
sdc            0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00  0.00      0.00     0.00   0.00    0.00     0.00    0.00    0.00   0.00
sdd            4.00      0.04     0.00   0.00  122.25    10.24 171.00    190.81     1.00   0.58 3884.17     1.12  0.00      0.00     0.00   0.00    0.00     0.00    0.00  664.68 100.00

关键指标解读:

  • %util:设备利用率,接近100%表示设备繁忙,可能存在IO瓶颈
  • r_awaitw_await:平均读写请求等待时间,数值越高说明IO响应越慢
  • aqu-sz:平均请求队列大小,数值较大说明IO请求堆积严重
  • await:平均服务时间(读写等待时间之和)

明确定位IO进程

基于上述过程我们已经明确sdd盘存在IO异常,此时我们就可以通过iotop来查看具体进程了,需要补充的是iotop默认是没有安装的,读者可以参考网上教程自行安装,以笔者的Ubuntu系统为例,对应的安装指令为:

复制代码
sudo apt install iotop

最后键入sudo iotop -o查看正在执行IO操作的进程,此时就可以非常明确地看到笔者Java进程对应执行异常IO操作的线程和读写速率了:

bash 复制代码
Total DISK READ:         0.00 B/s | Total DISK WRITE:       142.99 M/s
Current DISK READ:      11.92 K/s | Current DISK WRITE:     336.21 M/s
    TID  PRIO  USER     DISK READ DISK WRITE>    COMMAND                                                                                              
3253712 be/4 sharkchi    0.00 B/s   18.26 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-3]
3253713 be/4 sharkchi    0.00 B/s   18.26 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-4]
3253711 be/4 sharkchi    0.00 B/s   18.25 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-2]
3253715 be/4 sharkchi    0.00 B/s   18.25 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-6]
3253714 be/4 sharkchi    0.00 B/s   18.24 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-5]
3253710 be/4 sharkchi    0.00 B/s   17.50 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-1]
3253717 be/4 sharkchi    0.00 B/s   17.50 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-8]
3253716 be/4 sharkchi    0.00 B/s   16.74 M/s java -jar web-cache-1.0.jar --app.startup.method=1 [pool-2-thread-7]

补充:其他有用的IO分析工具

在实际排查中,除了上述工具外,还有其他一些有用的工具可以辅助分析:

  1. pidstat -d 1:显示每个进程的IO统计信息
  2. iotop -a:按IO累计使用量排序
  3. vmstat 1:显示虚拟内存统计,包括IO相关指标
  4. lsof +D /path/to/directory:列出打开指定目录下文件的进程

小结

我们来简单小结一下IO性能瓶颈的排查套路:

  1. 通过top命令查看%wa(iowait)指标,判断CPU是否存在异常等待IO的情况
  2. 使用iostat -x 1查看IO使用率和响应时间等详细指标,定位具体磁盘设备
  3. 使用iotop显示正在执行IO任务的进程和线程,明确问题程序
  4. 结合其他工具如pidstatvmstat等进行深入分析

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

参考

【双语视界】Linux上IO性能问题的故障排除:www.bilibili.com/video/BV14d...

本文使用 markdown.com.cn 排版

相关推荐
南囝coding3 小时前
《独立开发者精选工具》
前端·后端·开源
IT_陈寒4 小时前
JavaScript 性能优化的 7 个致命陷阱:我从 P5 到 P8 的核心突破都在这里!
前端·人工智能·后端
舒克日记4 小时前
基于springboot的民谣网站的设计与实现
java·spring boot·后端
风象南4 小时前
除了JSON/XML,你还应该了解的数据描述语言ASN.1 —— 附《SpringBoot实现ASN.1在线解析工具》
后端
JaguarJack4 小时前
深入理解 PHP-FPM 的最佳配置
后端·php
Kiri霧5 小时前
在actix-web应用用构建集成测试
后端·rust·集成测试
Victor3565 小时前
Redis(67)Redis的SETNX命令是如何工作的?
后端
Victor3565 小时前
Redis(66)Redis如何实现分布式锁?
后端
凤山老林6 小时前
新一代Java应用日志可视化与监控系统开源啦
java·后端·开源
Kiri霧13 小时前
Rust开发环境搭建
开发语言·后端·rust