Linux磁盘I/O瓶颈深度剖析:从系统负载到应用线程的完整链路追踪 1. 从系统负载异常到I/O瓶颈定位当Linux服务器突然变慢时很多工程师的第一反应是查看CPU和内存使用情况但往往会忽略一个更常见的性能杀手——磁盘I/O瓶颈。我遇到过最典型的情况是系统监控显示CPU使用率不高但load average却持续飙升这时候**%waI/O等待指标**往往能揭示真相。上周处理的一个生产环境案例就很说明问题某电商平台的订单服务在促销期间出现响应延迟top命令显示%wa高达85%而CPU空闲率%id还有70%。这种明显的CPU等I/O现象通过以下命令组合可以快速验证# 查看整体CPU状态重点关注%wa top - 14:31:20 up 35 min, 4 users, load average: 2.25, 1.74, 1.68 Tasks: 71 total, 1 running, 70 sleeping, 0 stopped, 0 zombie %Cpu(s): 2.3%us, 1.7%sy, 0.0%ni, 0.0%id, 96.0%wa, 0.0%hi, 0.0%si, 0.0%st # 使用iostat确认磁盘压力每2秒刷新共5次 iostat -x 2 5 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.13 510.12 1.67 18.81 232.08 4231.47 218.01 0.07 3.50 1.68 3.66 0.25 90.52关键指标解读%util90.52%表示磁盘接近饱和超过70%就需要警惕await3.50ms表示平均I/O响应时间机械盘应5msSSD应1mswkB/s4231.47KB/s的写入速度对SATA盘已是较大压力2. 定位高I/O进程的实战技巧确定磁盘瓶颈后接下来要找出罪魁祸首。iotop是最直观的工具但生产环境往往没有安装权限。这时候可以组合使用以下方法2.1 使用iotop直接观察# 需要root权限安装 yum install iotop -y # 按IO排序实时查看 iotop -oP Total DISK READ: 8.00 M/s | Total DISK WRITE: 20.36 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND 15758 be/4 root 7.99 M/s 8.01 M/s 0.00 % 61.97% bonnie -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp2.2 无iotop时的替代方案# 方法1通过/proc目录统计 for x in find /proc -maxdepth 1 -type d -name [0-9]*; do echo $x cat $x/io 2/dev/null | grep read_bytes | awk {print $2}; done | sort -n -k2 | tail -n 10 # 方法2使用pidstatsysstat工具包 pidstat -d 1 5 Linux 5.4.0-91-generic (hostname) 01/15/2023 _x86_64_ (8 CPU) 07:15:02 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 07:15:03 PM 999 1234 1024.00 0.00 0.00 mysqld我曾用第二种方法发现一个MySQL进程的写入量异常1MB/s进一步排查发现是临时表配置不当导致大量磁盘写入。3. 深入线程级I/O分析找到高I/O进程后真正的挑战才开始。现代应用多为多线程架构我们需要定位到具体线程。这里分享一个真实案例的排查过程3.1 使用ps和top定位线程# 查看进程的线程列表按CPU或IO排序 top -H -p 73739 ps -eLf | grep mysqld | sort -n -k 7 | tail3.2 通过procfs获取线程IO# 查看线程级IO统计需要内核2.6.20 cat /proc/73739/task/74770/io rchar: 48752567 wchar: 549961789 read_bytes: 49020928 write_bytes: 5499617283.3 使用perf进行性能分析# 记录线程的系统调用 perf trace -t 74770 -o /tmp/trace.log # 分析结果发现大量write系统调用 2852.690 ( 0.040 ms): write(fd: 159, buf: 0xd7a30020, count: 65536) 65536 2853.029 ( 0.035 ms): write(fd: 64, buf: 0xcd51e020, count: 65536) 65536配合lsof检查文件描述符lsof -p 73739 | egrep 64u|159u mysqld 73739 mysql 64u REG 8,0 211872724 6979323031 /mysql/tmp/#6979323031 (deleted) mysqld 73739 mysql 159u REG 8,0 212143246 7046482357 /mysql/tmp/#7046482357 (deleted)发现是MySQL在频繁写入临时文件这通常与复杂查询有关。4. 从系统调用到SQL语句的完整追踪当定位到是数据库问题后我们需要建立从磁盘I/O到SQL语句的完整证据链4.1 查询MySQL会话信息SELECT * FROM performance_schema.threads WHERE THREAD_OS_ID 74770\G *************************** 1. row *************************** THREAD_ID: 95 NAME: thread/sql/one_connection PROCESSLIST_ID: 9 PROCESSLIST_INFO: SELECT b.id,a.k FROM sbtest_a a LEFT JOIN sbtest_b b ON a.idb.id GROUP BY a.k ORDER BY b.c DESC4.2 分析执行计划EXPLAIN SELECT b.id,a.k FROM sbtest_a a LEFT JOIN sbtest_b b ON a.idb.id GROUP BY a.k ORDER BY b.c DESC\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: a Extra: Using temporary; Using filesort *************************** 2. row *************************** id: 1 select_type: SIMPLE table: b type: eq_ref执行计划显示使用了临时表和文件排序这正是导致高I/O的元凶。4.3 优化方案实施针对这个案例我们采取了以下措施增加tmp_table_size和max_heap_table_size为b.c字段添加索引重写SQL避免filesortSELECT b.id, a.k FROM sbtest_a a JOIN (SELECT id, c FROM sbtest_b ORDER BY c DESC) b ON a.idb.id GROUP BY a.k优化后磁盘I/O使用率从90%降至15%效果立竿见影。