The Complete Backend Engineer The Complete Backend Engineer
首页
  • Java基础

    • JavaScript
  • Java并发编程

    • 《JavaScript教程》
    • 浅谈Java并发安全发布技术
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 深入源码解析synchronized关键字
    • 浅谈Java并发编程中断的哲学
    • 深入理解Java中的final关键字
    • 深入剖析Java并发编程中的死锁问题
    • 浅谈池化技术的优雅关闭
    • synchronized关键字使用指南
    • 浅谈并发编程等待通知模型
    • 浅谈传统并发编程的优化思路
    • JS设计模式总结
  • JVM相关

    • 从零开始理解JVM的JIT编译机制
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 计算机组成原理

    • 浅谈CPU流水线的艺术
  • 操作系统

    • Linux性能问题排查最佳实践
    • Linux上IO性能问题的故障排除实践
    • 浅谈Linux权限管理
    • 从操作系统底层浅谈程序栈的高效性
  • CSS
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • JavaScript
  • Java并发编程

    • 《JavaScript教程》
    • 浅谈Java并发安全发布技术
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 深入源码解析synchronized关键字
    • 浅谈Java并发编程中断的哲学
    • 深入理解Java中的final关键字
    • 深入剖析Java并发编程中的死锁问题
    • 浅谈池化技术的优雅关闭
    • synchronized关键字使用指南
    • 浅谈并发编程等待通知模型
    • 浅谈传统并发编程的优化思路
    • JS设计模式总结
  • JVM相关

    • 从零开始理解JVM的JIT编译机制
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 计算机组成原理

    • 浅谈CPU流水线的艺术
  • 操作系统

    • Linux性能问题排查最佳实践
    • Linux上IO性能问题的故障排除实践
    • 浅谈Linux权限管理
    • 从操作系统底层浅谈程序栈的高效性
  • CSS
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

  • 运维

    • Linux性能问题排查最佳实践
    • Linux上IO性能问题的故障排除实践
      • 引言
      • 详解Linux系统IO性能问题排查通用步骤
        • 1. 检查服务器负载
        • 2. 查看IO使用率
        • 3. 明确定位IO进程
        • 其他有用的IO分析工具
      • 小结
      • 参考
  • CSS

  • 计算机基础
  • 运维
sharkchili
2025-12-12
目录

Linux上IO性能问题的故障排除实践

# 引言

在监控工具盛行的今天,学会传统的系统性能瓶颈排查手段也是必要的技术储备,它可以让你保持对系统指标的敏感度,而以下便是笔者整理的一套比较普适的IO性能瓶颈通用排查方法论,同时为了更好地复现这个问题,笔者也用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));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注下方公众号获取我的联系方式,添加时备注加群即可加入。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 详解Linux系统IO性能问题排查通用步骤

# 1. 检查服务器负载

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

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
1
2
3
4
5
6
7

# 2. 查看IO使用率

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

  1. -x:显示更多扩展信息(包括设备利用率、等待时间等)
  2. 每1秒输出1次,持续输出
iostat -x 1
1

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

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
1
2
3
4
5
6
7
8

关键指标解读:

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

# 3. 明确定位IO进程

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

sudo apt install iotop
1

最后键入sudo iotop -o查看正在执行IO操作的进程,对应输出结果如下,对应字段含义分别是:

  1. Total DISK READ: 总磁盘读
  2. Total DISK WRITE: 总磁盘写
  3. Current DISK READ: 瞬时磁盘读
  4. Current DISK WRITE: 瞬时磁盘写
  5. TID:线程号
  6. PRIO:io进程优先级
  7. USER:进程所属用户
  8. DISK READ :该线程读取速率
  9. DISK WRITE:该线程写入速率
  10. COMMAND:执行线程的命令(对应java进程启动指令)

此时就可以非常明确地看到笔者Java进程对应执行异常IO操作的线程和读写速率了,可以看到笔者的大量进程都存在写入操作,占用大量磁盘写入带宽:

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]
1
2
3
4
5
6
7
8
9
10
11

# 其他有用的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. 结合其他工具如pidstat、vmstat等进行深入分析

当然,对于老司机而言,可能大部分都会在top指令完成后,明确存在io性能瓶颈且当前服务器是java进程专用后,就会直接通过iotop定位问题了,而这就是所谓的经验。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注下方公众号获取我的联系方式,添加时备注加群即可加入。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

【双语视界】Linux上IO性能问题的故障排除:https://www.bilibili.com/video/BV14d35zhEjs/?from_spmid=united.player-video-detail.drama-float.0&plat_id=411&share_from=season&share_medium=iphone&share_plat=ios&share_session_id=2A52FF3A-393E-4D48-98A0-712F961E6C3A&share_source=WEIXIN&share_tag=s_i&spmid=united.player-video-detail.0.0&timestamp=1758040810&unique_k=gAzBCJY&vd_source=bf04f9a485aa892c0242fbfdfca25589 (opens new window)

编辑 (opens new window)
上次更新: 2025/12/16, 14:07:43
Linux性能问题排查最佳实践
CSS教程和技巧收藏

← Linux性能问题排查最佳实践 CSS教程和技巧收藏→

最近更新
01
从操作系统底层浅谈程序栈的高效性
01-07
02
从零开始理解JVM的JIT编译机制
01-06
03
浅谈Linux权限管理
12-26
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×