ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

磁盘I/O %util飙高?从iostat到根因定位的实战排查指南

磁盘I/O %util飙高?从iostat到根因定位的实战排查指南 用iostat一看磁盘那行%util直接飙到95%以上甚至常年99%系统响应慢得跟放幻灯片一样top里一排排D状态进程这种情况遇到过一次就忘不掉。更折磨人的是你很难说清楚到底是业务量真的大、还是哪块配置出了问题、还是磁盘本身就快不行了——三者表现出来的症状几乎一模一样。这篇文章我不打算讲教科书理论就结合我自己在服务器上、虚拟化环境里、还有数据库机器上处理这类问题的实际经历把从“看到%util高”到“定位根因再到收尾”的完整流程和思路拆开聊。说实话这套方法论到现在我还在用大部分环境下的I/O问题都能套进去。1. 先把指标体系张罗起来再判断那块盘是真有罪还是替罪羊1.1 单看%util很容易被误导必须把几个关键指标一起看很多新手第一次用iostat看到%util这一列数值很高脑子里的第一反应就是“磁盘要不行了”“要扩容了”。这个理解其实不完全对。%util的含义是设备在采样周期内处理I/O请求所占用的时间百分比它反映的是“设备的忙碌程度”但这个“忙碌”背后的原因可能完全不同必须结合其他指标来交叉判断。我自己的习惯是拿到iostat输出后至少同时看这几列%util设备忙碌度超过80%基本可以判定为繁忙r/s和w/s每秒读请求数和写请求数用来判断是读多还是写多rkB/s和wkB/s每秒读写数据量用来判断请求大小await平均I/O响应时间包括队列等待时间这个是最直观的体验指标svctm平均服务时间早期版本有这个字段新版本已去掉但概念仍然值得参考。你会发现一个很有意思的现象有些情况下%util已经99%了但await并不高只有十几毫秒另一种情况是%util只有30%await却到了几百毫秒。这两种情况的处理思路是完全不同的。1.2 读懂“高%util”背后的两种真实场景用生活化的方式来理解去银行办业务排队窗口只有一个%util就是窗口工作人员的忙碌度它永远是接近100%的因为只要还有业务他就不停手。此时系统快不快取决于一笔业务要办多久也就是await。场景一就是典型的“窗口效率低但业务量并没有爆炸”。%util高、await也很高。这种情况说明磁盘本身处理请求的速度跟不上了可能是磁盘硬件老化、接口速率限制、或者请求模式太碎片化。场景二是“业务量真的太大了窗口忙不过来还排长队”。%util高、await升高得更明显甚至出现大量“D状态”进程不可中断睡眠等待磁盘I/O这时通常是业务并发已经超出了磁盘的承受能力。这两种场景的处理方式一个偏重“调优”一个偏重“扩容或拆分”方向完全不同。所以先别急着动把指标证据集齐了再说话。2. 观测手段别只用iostat一次要养成“动态观测”的习惯2.1 跑一轮连续采样把峰值、持续性和平均线都看在眼里很多人排查I/O就执行一条iostat命令看完一个数字就下结论。但I/O是一个波动性很强的指标有时候刚好赶上业务高峰看一眼当然是高的有时候刚好在凌晨备份窗口你看到的数值其实不代表常态。在生产环境里做判断最忌讳用瞬时数据代表全部情况。我的习惯是先做一轮iostat -x 1 5每秒一条、连续采集5次看趋势而不是看单点。如果每次输出的%util都在高位且稳定说明是持续性的高负载如果只在某些次采样中很高其他时候降下来说明是间歇性抖动要结合业务时间窗口去分析是不是有定时任务、备份脚本、日志切割在作怪。2.2 补充pidstat、iotop、dstat这些帮手从“设备视角”落到“进程视角”iostat告诉你的是“这个设备有多忙”但它没办法告诉你“是谁让它这么忙”。要回答这个问题需要用到另外一套工具。pidstat -d可以按进程维度查看I/O统计信息包括每秒读写的KB数和总的I/O操作数iotop则像一个“top版”的I/O监控能实时显示每个进程的I/O读写速度。dstat可以组合查看CPU、磁盘、网络、内存的联动情况特别适合排查“是不是某个组件拖累了整个系统”的关联问题。我自己在排查时会按这个顺序来先说“设备有问题”再说“是读还是写”最后说“是哪个进程挑起来的”。这三步缺一不可直接跳到排查进程会容易漏掉中间环节。3. 从读/写模式入手判读出I/O特征再决定调优方向3.1 读多写少与写多读少完全是两个世界的处理思路判断出是读多还是写多能让排查方向立刻缩小一大半。最简单的方法是看iostat输出里的rkB/s和wkB/s如果读的量远超写的量那问题大概率出在“数据没进内存缓存”或者“业务本身要频繁读取大量冷数据”上。读多写少的场景优先考虑的思路包括应用层有没有做缓存、数据库的buffer pool是不是太小、文件系统层有没有使用适当的预读机制、是不是存在大量随机读小文件的情况。写多读少的场景则更麻烦一些因为写I/O往往比读I/O更难被缓存吸收。写多的时候要先确认是不是存在频繁的fsync操作比如数据库的redo log、binlog刷盘、有没有日志写得太频繁太碎的问题、文件系统的日志模式是不是过于保守比如ext4默认的dataordered模式。3.2 随机I/O和顺序I/O的判断直接决定了硬件选型和参数调整另外一个特别重要的区分维度是“随机”还是“顺序”这个特征其实藏在单次请求的数据量里。观察rkB/s和r/s这两个值如果每次读请求平均只有4K、8K这种小数据量那基本就是随机读比如数据库按主键查询如果单次请求是几百KB甚至1MB以上那通常是顺序读比如全表扫描、视频流读取、大文件拷贝。随机I/O和顺序I/O对存储设备的考验完全不同。机械硬盘的随机I/O能力很弱顺序I/O却很可观SSD虽然两者都强但在随机写场景下也存在写放大和垃圾回收的问题。如果机器上是机械盘%util常年居高不下先看看磁盘的读写模式基本上可以八九不离十地猜到结果。3.3 一个实操案例从iostat四条数据实现对思路的完整落地我处理过一个比较典型的案例一台跑着MySQL的物理服务器每到业务高峰就卡顿iostat显示sda的%util到了98%rkB/s约15000r/s却高达3000多单次读大小只有5KB左右这说明业务在做非常密集的小数据量随机读。当时MySQL配置的innodb_buffer_pool_size只有2GB而实际数据量到了30GB缓存命中率很低大量查询直接穿透到磁盘。在业务无法立刻扩容的情况下先调整了buffer pool到可用内存的70%左右同时开启了innodb_adaptive_flushing减少刷脏页带来的额外I/O压力。这一步操作后%util从98%下降到40%左右起码让系统先喘过气来。这个案例想表达的是%util高不是终点它背后一定有一个业务模式和配置之间的错配点找到那个点才是关键。4. 别放过系统层面的“隐形黑手”D状态进程和日志刷盘是重灾区4.1 用D状态进程定位到具体的等待者再顺藤摸瓜找源头top命令里偶尔能看到一些进程的状态是D这是“不可中断睡眠”通常意味着进程正在等待I/O完成。D状态进程过多说明有大量线程在I/O上排队——但注意它们只是受害者真正的元凶要让pidstat出来指认。pidstat -d输出的KB_rd/s、KB_wr/s和cswch/s上下文切换数很有参考价值。当某个进程的写I/O特别大、而且上下文切换也非常频繁时基本可以判断它正在制造大量并发写操作把磁盘队列塞满。高并发写还有一个容易被忽视的地方它会拖累所有其他进程。因为磁盘设备的队列是全局共享的一个进程疯狂写I/O其他进程的读请求也要排队等待这时候你看到系统“莫名很卡”但其实没有明显的CPU瓶颈七成是这么来的。4.2 数据库的刷盘频率和日志落盘策略通常是压垮磁盘的最后一根稻草数据库类应用是磁盘I/O问题最集中的领域因为这个群体对I/O的依赖度极高。MySQL的doublewrite、redo log刷盘、binlog同步PostgreSQL的WAL写入这些机制在保证数据安全性的同时都消耗着大量写I/O。如果你用的是MySQL这几处配置值得仔细过一遍innodb_flush_log_at_trx_commit默认是1代表每次事务提交都刷盘安全性最高但也最费I/O设置为2可以让OS缓存每次帮忙缓冲性能显著提升但掉电会丢1秒内的数据sync_binlog同样是1最安全也最费I/O和上面那个值配合使用时可以形成一个安全性的组合拳同时也可以考虑折中设置为0或Ninnodb_doublewrite为保证数据页写入的原子性每次写操作会先写doublewrite buffer再落到实际表空间这个机制在普通机械盘上成本很高需要谨慎评估。曾经有一台机器磁盘%util常年80%但业务量并不大。后来排查发现是某业务系统在代码里做了极度频繁的小事务提交每次都触发刷盘操作。这种“频繁的小事务”模式对I/O的杀伤力远大于偶尔的大事务因为它让磁盘的队列一直处于被占用的状态。4.3 日志应用的write和fsync可能比业务本身更烧磁盘如果说数据库刷盘是明面上的消耗大户那么日志系统就是暗处的消耗大户。很多应用的日志框架每写一条日志就调用一次write甚至fsync高层应用可能感受不到差别但内核和磁盘却在承担极其沉重的压力。对于这种场景处理思路一般是提升日志批量写入能力减少直接落盘频率把日志写到独立的磁盘分区上避免和业务I/O争抢如果条件允许把日志目录放到SSD上如果用的是rsyslog或syslog-ng这类系统日志服务确认它们的异步写入配置是否合理。之所以要单独说日志这块是因为日志通常是“杀死磁盘I/O的最隐蔽凶手”。我曾经遇到一台服务器磁盘%util很高但业务进程的I/O看起来都很正常最后用lsof挨个排查打开了哪些文件才发现是某个容器的stdout日志量太大了数据卷把所有I/O都吃掉了。5. 深入硬件与底层机制识别“隐形降速”与“假性繁忙”5.1 机械盘、SSD、网络存储不同介质的%util高含义完全不同同样是%util飙升机械盘上的含义很可能与SSD上的含义大相径庭因为它们的底层工作原理完全不同。机械硬盘的磁头在盘片上寻道、旋转随机小I/O的寻道时间占了响应时间的大头所以当它%util很高时通常是真的到了性能极限。SSD没有机械寻道%util反映的是控制器处理请求的时间占比高%util可能是因为垃圾回收GC在后台运行或者是写放大效应实际写入的数据量远大于逻辑写入量。网络存储如NFS、iSCSI的%util还包括了网络延迟所以%util高时第一怀疑对象可能是网络链路而不是远端存储。如果判断出底层是SSD并且写I/O很多可以考虑检查一下固态盘的剩余空间和磨损情况。SSD在剩余空间不足时垃圾回收会变得非常频繁这会让I/O性能大幅下降表现出来就是%util异常升高但实际业务请求量并没有增加。5.2 RAID降级、坏道重映射、接口降速硬件层故障往往以“%util高”方式呈现有一类%util升高是硬件健康问题带来的这类问题最容易被忽视因为常规监控不会直接报错。RAID阵列中的一块盘掉线、进入降级模式后整个阵列的写性能会显著下降因为需要实时计算校验数据机械盘出现大量坏道时重映射操作会让I/O响应时间瞬间暴涨导致%util飙升SATA/SAS链路如果出现松动或信号问题接口会不断重试表现出来也是I/O变慢。所以在排查%util高的问题时我通常会顺手做两件事一是查看dmesg里有没有磁盘相关的报错信息比如I/O error、reset、CRC错误等二是查看RAID卡的状态确认阵列是否处于正常状态。这一步虽然简单但往往能帮你躲开一大段无效排错。5.3 I/O调度器和文件系统挂载参数内核层的小配置能带来大差别Linux内核的I/O调度器负责决定磁盘请求的处理顺序这直接决定了随机读写的排队方式。不同调度器在不同场景下表现差异明显比较常见的几种noop最简单的FIFO队列适合纯SSD或者底层存储已经做了大量调度的场景deadline为每个请求设置截止时间重读轻写优先保证读请求传统机械盘场景表现不错mq-deadline多队列版本的deadline现代内核默认之一适用面比较广cfq完全公平队列通过时间片方式让所有进程获得比较均衡的I/O机会但高并发下延迟可能偏高新内核已逐步弃用。修改I/O调度器的方法很简单临时生效用echo mq-deadline /sys/block/sda/queue/scheduler永久生效需要配置内核启动参数或者在udev规则里设置。文件系统挂载参数同样值得检查。比如ext4/nfs常常会根据挂载选项决定是否启用atime更新relatime和noatime可以显著减少读操作触发的元数据写操作barrier1或者默认的dataordered模式保证顺序性但也会增加写成本。这些细节单个看起来影响不大但在高并发下积少成多完全可能从量变引起质变。6. 常规“三板斧”不够用了再讲几个压箱底的针对性方案6.1 应用层优化把I/O次数降下来才是根本出路无论底层设施怎么调如果应用层没有减少不必要的I/O调用那么一切优化都是治标不治本。应用层优化最常见的方向是缓存和批量。缓存的意义在于把多次重复读变成一次读。数据库的查询缓存、前端应用的本地缓存、搜索引擎的缓存都是一样的原理。批量化的意义在于把多次小I/O合并成一次大I/O比如SQL里本来一次提交一条insert改成一条insert里插入多行日志本来是逐条写改成攒够一定条数再刷盘。有数据支撑地讲一次写8KB和八次写1KB后者对磁盘造成的压力大约是前者的好几倍因为每次写I/O都有传输命令、寻址、校验等固定开销。用小事务批量刷新代替高频小事务是数据库场景里性价比极高的优化。6.2 系统层调优内存换I/O、缓存换延迟的操作清单系统层能做的调优本质上是用计算资源和内存资源去换磁盘I/O资源。常见的几种成熟做法包括加大文件系统缓存把脏页比例调高让写操作在内存中多待一会再落盘调整内核的vm.dirty_ratio和vm.dirty_background_ratio前者是脏页达到多少比例时强制同步写后者是达到多少比例时后台开始写适当调大可以让突发写更平滑为数据库等应用预留足够大的buffer pool让热点数据尽量留在内存中对于读多的场景合理调整vm.vfs_cache_pressure参数避免内核过早回收目录项和inode缓存。这里要提醒一点内存换I/O是有限度的如果内存本身就紧张强行调高缓存比例可能导致swap使用急剧上升反而出现新的性能问题。6.3 架构层拆分把I/O压力分摊到多块磁盘、多台机器当单台设备的I/O能力已经到达物理极限再怎么调优都只是杯水车薪接下来就轮到架构层面的工作了。思路无非两种拆盘和拆机器。拆盘就是把不同业务的I/O分散到不同的物理设备上。比如把数据库的数据目录、日志目录、系统目录放在不同的磁盘上把WAL日志和热数据分开存储避免一份盘上的I/O峰值影响其他模块。拆机器则是按业务维度横向扩展比如把读多写少的服务拆出去做读写分离用多台机器分担读压力或者在中间加一层缓存服务来挡住读I/O。从成本角度看拆盘是最便宜高效的手段如果你手头有闲置的磁盘先做这一步。拆机器需要考虑的维度更多还涉及到数据一致性、负载均衡、故障转移等问题适合在拆盘仍不能满足需求时推进。6.4 如果已经“救不回来”了提前建立I/O性能基线和容量预警才有退路真实运维场景中经常有“问题已经发生再开始排查”的被动情况。几个主动运维的建议值得记住一是平时定期记录iostat的常规读数形成性能基线将来出现问题时才能知道偏离了正常范围多少二是监控告警不能只设置%util90%这一条还要配合await上升幅度、队列长度avgqu-sz变化来综合判断三是重要业务尽量在条件允许时提前做高I/O压力测试观察它在I/O密集时的表现。我之前在给一台备用服务器做例行巡检时发现它的%util有段时间一直异常偏高但因为不是线上业务所以没人care。后来这个“备用机”真的被顶上生产时第一波流量就把磁盘打满了。这个教训让我很感慨I/O性能基线这个东西平时花十分钟记录关键时刻能顶一轮大排查。7. 排查时候的思维框架比技巧本身更值得沉淀7.1 按顺序排查少走弯路设备层→系统层→应用层→架构层I/O问题排查最怕一上来就怀疑某一方面然后根据不完整的信息乱试。我踩过好多次“反过来排查”的坑后逐渐形成了一个固定的排查顺序分享出来仅供参考先确认硬件层有没有问题包括磁盘健康状态、RAID状态、接口速率这一步可以用dmesg、smartctl、RAID卡管理工具快速检查再确认系统层的配置是否合理包括I/O调度器、挂载参数、脏页比例接着才看应用层的I/O模式找出哪个进程、哪类操作在制造压力最后才是架构层面的评估判断是否需要扩容或者拆分。这个顺序符合“从底层到顶层”的排查逻辑每层都有相对清晰的判别标准不容易漏掉关键信息也不容易在一个方向上钻牛角尖。7.2 不同业务的I/O陡增特征不同要结合高峰时段一起分析同样是一台机器I/O %util飙升出现在双十一大促期间和出现在凌晨三点含义完全不同。前者可能是预期内的业务峰值后者则多半有异常因素。我习惯在做I/O分析时把时间因素一起拉进来思考%util高的时间段是否与业务高峰一致是否与定时任务窗口一致是否与备份作业或日志切割窗口一致如果各项都对齐了那很可能是负载到了一定量级之后超出磁盘能力如果时间上对不上就要优先怀疑某个进程异常失控。有个典型的例子一台服务器白天%util正常每晚10点开始飙高到90%持续半小时后回落。排查后发现是某个定时脚本在这个时间点做全量文件扫描和索引重建但脚本的执行频率和业务访问高峰撞在一起了。解决方式很简单把定时任务错峰执行%util就下来了。7.3 长期高%util环境下的“降载”思路削峰填谷给系统留出喘息机会在I/O资源长期吃紧的环境里除了被动扩容之外还可以主动做一些“削峰填谷”的操作。思路和城市交通错峰是一个道理把密集的I/O请求尽量打散到不同的时间段执行。具体做法包括重I/O的任务错峰执行比如备份、批量导入、索引重建不要挤在同一个时间窗口在代码层面控制并发度比如限制同时运行的异步任务数量避免一瞬间启动大量线程产生I/O雪崩利用消息队列将突发写入削峰让系统以可控的速率慢慢消费积压的数据。我见过很多系统在高峰期I/O很紧张但非高峰时磁盘却很空闲这种情况下优先考虑削峰要比直接买新硬件更划算。说到底I/O问题很多是“模式问题”不完全是“容量问题”。8. 最后再分享一把排查利器和一个压箱底的冷门技巧8.1 一个非常实用的快捷排查组合命令每次排查I/O问题我总会在最开始执行这样一条命令它能把当前I/O状况和进程占用情况一次性展现出来iostat -x 1 3 pidstat -d 1 3 top -bn1 | head -30iostat -x 1 3先看设备层状态pidstat -d 1 3再看进程层统计最后top看一眼整体负载和D状态进程数量。如果这几条命令的输出里已经能明显看出问题那就不用启动一堆重型监控工具了快速定位、快速止血是最重要的。8.2 使用磁盘延迟直方图观察长时间趋势比看平均值更有效很多人在看iostat时只关注平均值其实那只是“整体感受”很难反映出问题的分布特征。后来我开始关注biostat某些发行版自带需要额外安装或者内核提供的延迟统计信息用直方图的形式观察I/O延迟的分布情况。通过直方图能够看到有多少I/O落在1ms内、多少落在10ms内、多少卡在100ms以上这比单纯看平均延迟有意义得多。因为一个系统里可能90%的I/O非常快只有10%的I/O慢到拖垮整体平均值可能看起来还行但实际用户体验已经在崩溃边缘了。这种“长尾延迟”问题看平均值看不出来看直方图一目了然。我见过一台数据库机器平均await只有20ms看起来非常正常但用直方图一看有约2%的I/O等待超过了500ms。正是这2%的长尾产生了大量的慢查询和锁等待业务端实际感受是“系统经常卡顿”。这个问题后来通过对热点表分区、改善索引命中率解决掉了如果只看平均值可能排查方向完全被带偏。8.3 为什么这篇文章没直接给你一个“标准答案”经常有人问我磁盘I/O %util特别高到底该怎么解决说句实话真没有一个适合所有场景的统一答案。不同硬件、不同应用、不同业务模式解决路径可能完全不同。我能给出的是完整的分析框架和实操思路而不是一个“万能脚本”。你真正要做的是花一点时间搞清楚自己系统的I/O特征是读多还是写多、是随机还是顺序、是高并发还是定时突发然后把上面的方法按顺序套一套。思路对了工具只是放大器思路错了再强大的监控也只能让你看到问题依然存在。我自己的习惯是把每次I/O问题排查的过程记录成一份文档包括现象、指标数据、诊断过程、最终方案和效果验证。遇到类似问题时直接翻历史记录能省掉大量重复排查时间。希望这个习惯对你也有启发也欢迎在评论区交流你遇到过的I/O疑难杂症。
返回列表