首页 / 资讯中心 / 文章详情

Linux IO性能排查实战:从调度器到Page Cache的优化指南

Linux IO性能排查实战:从调度器到Page Cache的优化指南 ★ FEATURED ARTICLE
1. 先从全局看IO一条读写命令在内核里到底经历了什么上一篇我们聊了IO的基本概念这一篇直接进入实战。很多人排查IO问题的时候习惯性先跑一个iostat看到%util高就开始怀疑磁盘老化其实这个思路容易走偏。IO问题的高发区往往在应用层和文件系统层磁盘本身反倒是最后才需要怀疑的对象。所以下手之前先把IO路径在脑子里过一遍你就知道该在哪里下刀了。1.1 一次读写从应用到磁盘要过几道门一条简单的read系统调用从进程发起一直到数据真正落到磁盘或者从磁盘读到内存至少要经过这么几层应用层进程调用read/write本身可能带缓冲区也可能直接走裸IO。系统调用层内核根据文件描述符找到对应的文件这里涉及到VFS虚拟文件系统的通用逻辑。具体文件系统比如ext4、xfs、btrfs负责把文件逻辑地址映射到磁盘块并且管理页缓存。Page Cache内核会把读到的数据缓存在内存里写数据也先写进脏页再择机回写。块层这里就是IO调度器的地盘请求排队、合并、排序都发生在这。设备驱动和磁盘硬件最终执行真正的数据传输。每一层都可能成为瓶颈而且每一层表现出来的症状高度相似——进程卡住、IO耗时变长、读写速度上不去。不理解这条链路你手里一堆工具也不知道先用哪个。举个很常见的例子你在iostat里看到util一直是95%以上以为是磁盘扛不住了但仔细排查后发现是因为应用每写一行日志就强制fsync一次导致IO请求被拆成大量小包磁盘大部分时间都耗在寻道和刷盘上。这种情况下问题出在应用层的写模式而不在磁盘本身的健康度。1.2 我常用的五件IO监控工具监控工具很多但我日常最常用的就五个分别从不同视角看IO组合起来才能构成完整拼图。工具观察维度核心指标最佳使用时机iostat块设备层%util、await、r/s、w/s、rkB/s、wkB/s先摸全局判断磁盘是否饱和pidstat进程层单进程读写速率、CPU占比锁定“哪个进程在刷盘”strace系统调用层read/write/fsync耗时揪出应用层低效操作sar历史采集任意指标按时间序列回放事后回溯故障时间段blktrace内核块层请求从进入到完成的完整生命周期深挖块层排队和调度问题这五个工具的使用顺序也很重要。我习惯先从iostat这种“上帝视角”看磁盘整体状态再用pidstat缩小范围到具体进程最后用strace定位到系统调用级别。如果iostat显示磁盘根本没压力那问题大概率出在文件系统锁、页缓存回写、或者进程本身的状态上根本没到磁盘那一步。所以别上来就strace挂在一个进程上看全局信息都没有等于盲人摸象。2. IO调度器内核替你排队但排队的策略得你定很多工程师知道有IO调度器这回事但很少有人真正理解它做了什么。简单打个比方你开了一家餐厅客人点单的纸条不断递进来后厨只有一个灶台。你得决定是按先来后到做还是优先做快熟的菜还是保证每桌都均匀上菜不饿着谁。Linux的IO调度器做的事情就是这个。2.1 调度器到底在做什么三种策略怎么选IO调度器的核心职责有两个合并和排序。相邻扇区的请求合并成一个大请求减少磁盘寻道次数对不同进程的请求重新排序兼顾公平和延迟。传统机械硬盘时代一次寻道要花费几毫秒调度器通过电梯算法能大幅提升吞吐量。到了SSD和NVMe时代硬件本身已经支持深度队列并行的随机读写内核调度器反而成了多余的一层。所以在多队列blk-mq架构下可选策略主要是这么几种none不做任何调度请求直接往下扔。适合NVMe SSD、以及已经被硬件/宿主机做了调度的虚拟磁盘。mq-deadline默认策略。保证每个请求在deadline时间内得到处理兼顾读写公平对绝大多数通用场景都够用。bfq基于权重分配带宽能有效防止某个大块读把交互式应用的IO饿死。桌面环境、混合负载场景下体验更好但CPU开销相对大。kyber以延迟目标为导向动态调整队列深度适用于低延迟敏感的服务比如分布式存储节点。选择策略的底层逻辑就一句话机械盘需要排序固态盘需要去排序延迟敏感场景需要控队列深度。如果你把默认的mq-deadline改成bfq跑在NVMe上虽然不至于出大问题但明显是浪费CPU资源——你让内核干了一件硬件本来就能干得更好的活。2.2 改调度器之前先看一眼你的磁盘类型动手之前先确认磁盘属于哪一类。这是很容易被忽略的一步很多人在虚拟机里改了调度器发现没效果就开始怀疑自己操作错了。其实虚拟化环境下你的“磁盘”大概率已经由宿主机做了IO排队和调度虚拟机里的调度器形同虚设。查看当前磁盘支持的调度器cat /sys/block/sda/queue/scheduler输出会显示类似这样的内容[none] mq-deadline bfq kyber方括号里是当前生效的调度器。如果要临时切换echo bfq /sys/block/sda/queue/scheduler这种方式重启即失效。想做持久化常见做法是写一个udev规则在设备出现时自动设置调度器。以systemd系统为例可以在/etc/udev/rules.d/60-iosched.rules里写ACTIONadd|change, KERNELsd[a-z], SUBSYSTEMblock, ATTR{queue/scheduler}bfq写完重启或者udevadm trigger即可生效。还有一种方式是改内核启动参数在grub配置里给kernel那行追加elevatorbfq但这个方式只对传统单队列调度器有效对blk-mq多队列架构并不适用所以我个人更推荐udev规则兼顾灵活和稳定。2.3 改完不一定生效容易被忽略的环节一个常见坑你以为改了调度器但实际上没生效。查看命令输出里方括号永远只在none上那你大概率踩了下面几个雷之一。磁盘是virtio驱动管理的宿主机已经接管了IO排队Guest里不允许修改。磁盘用了设备映射比如LVM、dm-crypt你改的是/dev/mapper下的虚拟设备而真正干活的是底层的/dev/sd*物理盘。NVMe设备默认就没有传统调度器只有none可选这是正常的不是你配置错了。另外提醒一句磁盘调度器不是性能调优的银弹。很多线上故障排查到最后发现调度器只是“背锅侠”。真正的问题可能是应用程序过度刷盘、文件系统碎片化、或者干脆就是磁盘容量不足导致回写频繁。调度器只决定IO请求怎么排队它不能凭空提升磁盘本身的吞吐上限。3. IO瓶颈定位三板斧从磁盘到进程再到系统调用掌握了工具和原理接下来就是实战环节。我把整个排查过程总结成“三板斧”从宽到窄逐步收敛每一步都能过滤掉大量无关信息。这套流程适用于绝大多数IO性能问题的初诊。3.1 第一斧iostat 先把磁盘状态看明白第一步永远是用iostat看块设备层的整体状态。我习惯用扩展模式iostat -x 1 3每秒输出一次连续三次。重点关注这些指标%util设备繁忙百分比。注意这个指标在机械盘上意义较大接近100%说明磁盘几乎一直有事在做。但在NVMe这类支持多队列的设备上%util的参考价值会打折扣——它反映的是采样周期内设备是否有IO在跑不代表设备已经到物理极限。awaitIO请求从进入队列到处理完成平均耗时。包括排队时间和服务时间。这个值高意味着请求等得久但不等同于磁盘慢。svctm这个指标已经不建议使用了它代表的是平均服务时间但在现代复杂硬件下计算方式已经没有实际意义很多版本里已经移除或者不再更新看了容易误导。avgqu-sz平均队列长度。机械盘上这个值持续大于2-3说明排队已经比较严重。r/s 和 w/s每秒读/写请求次数。配合下一行的rkB/s、wkB/s可以算出平均每次IO请求的数据量大小。比如你看到读请求每秒1000多次但每秒读吞吐只有2MB那平均每次请求才2KB左右。这种模式下磁盘绝大多数吞吐都浪费在寻道上应用层应该尽量合并小IO而不是换一块更快的盘。3.2 第二斧pidstat 找出哪个进程在刷盘磁盘整体状态看明白了下一步就是锁定进程。pidstat的-d参数可以打印每个进程的IO统计pidstat -d 1输出里最关键的是kB_rd/s和kB_wr/s两列。按写入速率排序基本上你能一眼看出“真凶”。比如某进程持续每秒写几十MB而其他进程加起来还不到1MB那几乎可以断定它就是吃IO的主角。如果系统里进程特别多可以先看一眼CPU列表里哪个进程处于D状态不可中断睡眠。D状态的进程通常是在等IO返回用ps -eo pid,stat,wchan:30,cmd | grep ^ *[0-9]* D能直接抓出来。这个状态下进程杀不掉只能等IO超时往往对应着磁盘故障或者NFS挂载失联。3.3 第三斧strace 一探系统调用到底有多慢走到这一步已经知道是哪个进程出了问题。接下来要回答的问题是它在IO上做了什么导致拖延。strace这时候就该登场了strace -p PID -f -tt -T -e traceread,write,fsync,fdatasync,open,close-f跟踪子进程-tt显示微秒级时间戳-T打印每次系统调用的耗时。重点观察两个现象write返回很慢单次write耗时几百毫秒甚至秒级往往是Page Cache写回路径阻塞了也就是俗称的刷脏页卡顿。fsync频繁出现如果应用流程里大量出现fsync并且每次都要几十毫秒才能返回说明应用在强制刷盘。这种情况下fdatasync其实比fsync更轻量因为fsync同时刷数据和元数据而很多文件的元数据变更比如修改时间戳根本不需要立刻落盘。还有一种隐蔽的情况open调用非常频繁每次open都经过路径解析和权限检查如果配合高频的close可能文件描述符泄漏。这种问题从业务层面表现为“文件操作越来越慢”底层原因是文件表越来越大。4. 四个真实的IO故障现场与排查清单理论和工具都齐了下面用四个我实际遇到过的典型案例把排查过程串起来。每个案例都代表一类常见的IO假象展示它们如何一步步从“看似磁盘问题”演变成“应用或系统层问题”。4.1 磁盘利用率是0%服务却卡成PPT有一个场景某服务的响应延迟突然飙升每次请求动辄好几秒。工程师第一反应就是看磁盘iostat跑出来却显示%util连1%都不到读写在量级上完全正常。这一下就让所有人懵了——磁盘没事服务为什么卡经验告诉我们这种“磁盘不忙但应用卡”的诡异情况十有八九是文件系统层的锁在闹脾气。某开发者在一次压测过程中写入热点数据ext4的某个inode锁竞争严重多个线程都在等这一个锁的释放表现在业务上就是延迟爆炸而磁盘本身根本没跑满。这里可以用perf去抓内核函数的调用栈perf top里如果能频繁看到_raw_spin_lock或者文件系统相关的函数名基本就能锁定问题方向。后来临时用sync配合调整写放大策略才算把服务从锁竞争里救出来。这类问题给到的教训很直接iostat显示磁盘不忙不等于IO链路没阻塞。文件系统锁、内存回收、脏页回写都可能卡住进程你以为的“磁盘问题”其实是“IO路径上的软件问题”。4.2 大文件写入瞬间卡死是脏页回写在捣乱另一个高频场景向磁盘写入大文件时写入速度刚开始很高随后突然跌到几乎为零过一阵子又恢复。用iostat -x 1观察会发现%util不高但await变得很大同时vmstat里的bo块设备写出列有大量数据瞬间涌出。这是典型的Page Cache脏页强制回写。内核默认配置下当脏页占比超过vm.dirty_ratio默认20%时写入进程会被强制阻塞直到脏页刷到安全水位以下。对于大文件写入这种一次性吐出一大堆数据的场景很容易触发这个阈值。针对这种场景适当调大后台回写阈值很有用。常用的两个参数sysctl -w vm.dirty_background_ratio10 sysctl -w vm.dirty_ratio30dirty_background_ratio后台内核线程开始回写脏页的阈值。调大一点能延迟后台回写的启动时机让写入进程先顺利进行。dirty_ratio强制同步回写的硬阈值。设置得比background高给应用留更多缓存空间。但这两个参数不能无限调大否则一旦磁盘写入能力跟不上大量脏页积累在内存里系统会瞬间进入同步回写状态反而更严重。我个人的习惯是在内存充足且写入量大的服务器上background调到10-15ratio调到30左右同时用dirty_expire_centisecs默认3000即30秒兜底确保老脏页不会被无限期囤积。4.3 明明没写数据IO却一直居高不下有一个排查了很久的问题iostat显示写IO很高每秒几百个写请求但通过pidstat居然找不到任何进程在大量写数据。这就非常诡异了所有进程加起来写入量都不到1MB/s可磁盘就是忙个不停。后来发现真相在日志上——页缓存回写。文件被写入后数据先暂存在内存里等满足一定条件才真正落盘。系统如果持续产生小量写入并且不断触发后台回写在iostat上就会表现为“持续不断的低速写IO”而任何进程的实时写入量看起来都不高。这类问题有个很实用的排查技巧观察atop或sar -B里pgin/s pgout/s把内存和磁盘的关联性展示出来。另外一个思路是查/proc/meminfo里Dirty字段看到Dirty数值从几百MB降到很小中间伴随着写IO就说明回写确实在进行。对于这种模式如果可以牺牲崩溃时的数据一致性可以在挂载文件系统时用datawriteback针对ext4替代默认的dataordered能显著减少提交时的同步开销。有些场景下这个改动能把写放大降低一个量级。4.4 文件句柄耗尽IO操作突然全部失败还有一次刷新认知的故障某服务gateway突然集体报错“Too many open files”写入文件、打开新连接通通失败。因为日志级别很高监控系统看起来就像IO故障——大量连接建立失败、写日志失败。排查命令其实特别简单cat /proc/sys/fs/file-nr第一列是当前已分配的文件句柄数第二列是空闲句柄数第三列是系统上限。当第一列接近第三列时系统级句柄基本耗尽。再用lsof -p PID | wc -l立刻看到是哪个进程的句柄失控。这个问题在服务处理大量连接或者频繁打开文件并且异常分支没关闭文件描述符时尤其常见。解决方式分为两步先修进程的文件描述符泄漏临时提高ulimit -n和fs.file-max但最重要的是靠线上一轮一轮压测排查那个让fd泄漏的代码路径。这类问题的耐人寻味之处在于你以为的IO性能问题根因是代码问题。5. 写在最后调IO参数之前先把问题量化写了这么多我最想强调的一点绝大多数IO性能问题都不是靠调参数解决的而是先量化、再定位、最后才动手。三步顺序一旦颠倒很容易被表面现象带偏。比如一上来就把dirty_ratio调大最后发现卡顿原因是文件系统锁调了等于白调还额外增加了数据丢失的风险。我个人在排查IO问题时的习惯是现在长期运行sar采集历史数据。定时任务采集的sar数据虽然看起来粗糙但在事后故障分析时价值极高。你可以在故障发生的那个时间段用sar -d -f /var/log/sa/saXX回放设备利用率用sar -B回放内存里的页交换情况再结合应用日志就能快速锁定问题窗口而不是对着正在变化的现场瞎猜。最后再分享一个很少被人提起但非常有用的小技巧给/etc/sysctl.conf里的调优参数写注释。这个习惯帮我避免了很多次“这个参数到底是谁改的”的尴尬。比如# 提高后台脏页回写触发点减少大文件写入的瞬时同步卡顿某生产环境验证过 vm.dirty_background_ratio 12一行注释既不会影响参数生效又能在三个月后重启服务器时让你想起当时的决策背景。IO问题永远无法被彻底消灭因为访问模式在变、存储硬件在变、业务流量也在变。能不能快速定位问题往往不取决于你背了多少参数手册而是取决于你是不是真正理解数据从应用走到磁盘的那条路。希望这篇能把你的思路拽回正确的方向上。
阅读完成 · 觉得有帮助?
咨询建站