:磁盘)
本文是《性能之巅》Systems Performance第 2 版第 9 章的导读。本书是系统性能领域的经典本系列逐章导读把书的核心概念讲清楚。仓库上一章讲了图书馆——文件系统把数据组织成可以检索的书籍。现在该去仓库了。磁盘就是城市的仓库——它长期存放货物数据需要的时候取出来。图书馆负责编目仓库负责存储。但仓库也是最容易被误诊的地方仓库忙不一定有问题仓库闲也可能藏着大麻烦。上一章讲的是图书馆怎么组织书籍这一章讲**“书最终存在哪里、怎么取出来”**。关键提醒这一章和上一章容易搞混。一定要记住图书馆延迟是读者等的时间“从查书到拿到书”仓库延迟是仓库自己花的时间“实际取货的时间”。读者只感知从查书到拿到书不感知仓库内部怎么操作的。这一章我们分两部分讲先搞清楚仓库是怎么运作的理论再看怎么给仓库做检查实操。一、仓库是怎么运作的仓库的基本结构想象你是一个仓库管理员。有人来取货你收到订单去货架上找找到后交给对方。最简单的仓库只有一个取货窗口——所有订单排队你一个一个处理。队伍越长等得越久。但真实的仓库没那么简单。它有前置仓磁盘缓存常用的货直接从前置仓拿——快。不常用的才去主仓取——慢。调度中心磁盘控制器决定哪个订单去哪个仓库取货、什么时候取。货架和货位磁盘介质实际的存储位置。那一次取货到底花了多少时间从下单到收货——时间花在哪了有人来取货你花的时间可以分成两段发起 I/O排队等待磁盘服务完成排队等待订单在队列里等——轮到你之前什么都不做磁盘服务你实际去货架找货、取货的时间加起来就是总时间——也就是应用感知的延迟。从内核的视角看有块 I/O 等待时间从 I/O 创建到离开内核队列和块 I/O 服务时间从设备接收到完成中断。从磁盘的视角看有磁盘等待时间在磁盘内部队列等和磁盘服务时间磁盘实际干活。你可能会想这些等待和服务分得这么细有什么用用处很大。如果延迟主要花在排队等待上说明仓库太忙了——需要加仓库、或者减少订单。如果延迟主要花在磁盘服务上说明仓库本身太慢——可能是机械硬盘老了或者 SSD 在垃圾回收。那仓库到底有多快时间尺度从微秒到分钟仓库的取货速度差异大到令人发指事件延迟磁盘缓存命中 100 μs闪存读~100–1000 μs机械磁盘顺序读~1 ms机械磁盘随机读7200 rpm~8 ms机械磁盘随机读有排队 10 ms最坏的虚拟磁盘 I/O 1000 ms你看到了什么磁盘有两种延迟——缓存命中微秒级和未命中毫秒级。这两个模式差了几百倍。如果你把它们平均一下——平均下来完全没意义。就像一家餐厅有的客人来了就上菜缓存命中有的客人等了半小时缓存未命中。你算平均等待时间——15 分钟。但这个数字既不反映来了就上菜的体验也不反映等了半小时的痛苦。要看分布不能看平均。那为什么有的订单快、有的订单慢呢随机 vs 顺序 I/O取货的姿势顺序 I/O是地址连续的——像沿着一排货架依次取货。你从第 1 号货架走到第 2 号、第 3 号……一路走下去不用回头。随机 I/O是地址跳跃的——像满仓库跑着找不同的货。你从第 1 号货架跑到第 500 号、再跑到第 3 号……每次都要重新找位置。对机械仓库来说随机 I/O 比顺序 I/O慢几十倍——因为仓库管理员要走过去寻道。想象你在一个巨大的仓库里每次取货都要走到不同的货架走路的时间远远超过取货本身。对 SSD 仓库来说随机和顺序差不多——因为没有机械部件不需要走过去。所以同样的负载换到 SSD 上可能快几十倍。那仓库忙不忙怎么衡量利用率不等于饱和度利用率是仓库在某个时间段内忙的比例。比如 60% 利用率——仓库有 60% 的时间在取货40% 的时间闲着。饱和度是排队长度——有多少订单在等。你可能会想利用率 80% 是不是就快满了不一定。利用率 80% 可能没事——只要没人排队。但如果排队长度大于 0——一定有人在等。更关键的是利用率 60% 但排队长度大于 0——说明排队已经开始了。就像高速公路车流量 60% 时你可能会想还有 40% 的空余应该不堵。但如果这时候已经开始堵车了——说明这条路的设计有问题或者有瓶颈。任何排队都是问题。用iostat的aqu-sz列看饱和度——大于 1 就是有问题。那仓库本身有哪些类型磁盘类型机械 vs 固态机械磁盘HDD靠盘片旋转和磁头移动来读写。就像去仓库找东西——先走到货架寻道找到位置旋转拿东西传输。每一步都要时间。盘片转速越快5400/7200/10000/15000 rpm找得越快。固态硬盘SSD靠闪存芯片存储。就像图书馆的电子检索系统——没有物理移动直接定位。大部分时间很快但**“系统维护”垃圾回收时会变慢**。SSD 有几个坑寿命有限——每个块只能写有限次SLC 约 5 万次MLC 约 5 千次TLC 约 3 千次延迟离群点——垃圾回收时可能突然很慢写放大——小写入会放大成大写入因为闪存要按块擦除那数据怎么从仓库送到图书馆接口和存储类型磁盘接口决定了通道的宽度和速度SCSI老式并行接口——多个设备共享一条总线会互相争抢SAS串行、点对点——每个设备独立通道快SATA串行、消费级——便宜但不如 SAS 可靠FC光纤通道专为企业存储设计——远距离、高可靠NVMePCIe 直连——最快延迟低至 10–20 微秒存储类型有JBOD一排独立仓库——每个磁盘独立管理RAID多个仓库共享一个调度中心——提供冗余和性能存储阵列大型物流园区——带大缓存、高级管理网络存储远程仓库——通过网络访问延迟更高RAID 各级别级别特点仓库类比RAID 0条带——快没冗余多个仓库没有备份RAID 1镜像——冗余写慢两个仓库内容一样RAID 5条带 校验多个仓库有备用RAID 6双校验——更安全多个仓库双备用那谁来决定谁先取货I/O 调度器谁先取货仓库里有很多订单谁先取I/O 调度器就是那个决定顺序的人。Linux 有几种调度器Noop不排序——谁先来谁先取。适合 SSD不需要排序Deadline保证延迟——急单优先。适合数据库CFQ公平分配——每人轮流。适合桌面BFQ带宽公平——按带宽分配。适合交互式应用Kyber简单、低延迟。适合快速设备mq-deadline多队列版本。适合现代多核系统就像仓库调度员——有的调度员按先来后到有的按急单优先有的按公平轮流。选哪个取决于你的业务。二、怎么给仓库做检查理论讲完了现在看实操。仓库出问题时你怎么查iostat仓库的仪表盘$ iostat-sxz1avg-cpu: %user %nice %system %iowait %steal %idle15.820.0010.7131.631.5340.31Device tps kB/s rqm/s await aqu-sz areq-sz %util nvme0n11642.009064.00664.000.440.005.52100.00关键列tps每秒操作数IOPSkB/s吞吐量await平均 I/O 响应时间——最重要aqu-sz平均队列长度——饱和度指标%util利用率注意%util对虚拟磁盘RAID、云盘有误导性——它可能显示 100%但底层还有空闲磁盘。await和aqu-sz更可靠。类比iostat 像仓库仪表盘的总览屏——吞吐量、响应时间、排队长度一屏全看。sar看历史sar前面章节介绍过关键是能看历史数据。磁盘相关用-d选项$ sar-d1DEV tps rkB/s wkB/s areq-sz aqu-sz await %util dev259-01509.0011100.0012776.0015.820.020.6094.00列名和iostat一样。-d后加-p可以显示更友好的设备名。注意老版本sar有svctm列服务时间新版本已经移除——因为它在现代磁盘支持并行 I/O上计算不准确。类比sar 像仓库的历史运营报告——能看上周三仓库多忙。PSI仓库压力# cat /proc/pressure/iosomeavg1063.11avg6032.18avg3008.62total667212021fullavg1060.76avg6031.13avg3008.35total622722632some至少有一个任务因为 I/O 停了full所有非空闲任务都停了avg10、avg60、avg300分别是 10 秒、60 秒、300 秒的滑动平均——比较三个数字就能看出压力在上升还是下降。比%util更直接——因为它测的是任务真的因为 I/O 停下的时间而不是磁盘在忙的时间。类比PSI 像广播多少人因为仓库取货慢停下了。pidstat分进程看 I/O$ pidstat-d1UIDPID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command0270532468.000.000.005tar027060.008192.000.000gzip关键列kB_rd/s读速率kB_wr/s写速率kB_ccwr/s取消的写写被覆盖或删除iodelay被磁盘 I/O 阻塞的时间时钟滴答——最关键的列iodelay是应用真正卡在磁盘上的时间。上面这个例子中tar有 5 个滴答的延迟读文件gzip没有延迟写在缓存里还没刷盘。注意只有 root 才能看非自己拥有的进程的磁盘统计。类比pidstat 像看每个企业被仓库卡了多久——这才是应用真正感受到的痛苦。perf追踪块设备事件# 追踪所有块 I/O 发起事件带栈10 秒perf record-eblock:block_rq_issue-a-gsleep10perf script--header输出会显示每个 I/O 的发起时间、进程、磁盘、大小、扇区以及内核栈——谁发起的。也可以只看大 I/Operf record-eblock:block_rq_complete--filternr_sector 200注意block_rq_issue在 I/O 发给设备时触发——如果 I/O 是通过内核工作线程发出的进程名可能不对。这时改用block:block_rq_insert插入队列时触发但可能漏掉没排队的 I/O。类比perf 像给仓库装监控——每个订单从哪来、发给谁、多大。biolatencyI/O 延迟分布# biolatency 10 1usecs:count distribution128-255:1065|*****************256-511:2462|****************************************512-1023:1949|*******************************1024-2047:373|******2048-4095:1815|*****************************4096-8191:591|*********8192-16383:397|******16384-32767:50|双峰分布——128-1023μs 是一个模式缓存命中2048-4095μs 是另一个模式实际磁盘。平均下来完全没意义。常用选项-m毫秒输出-Q包含 OS 排队时间-F按 I/O 标志拆分-D按设备拆分注意这是 BCC 工具。-F对排查问题特别有用——区分读、写、同步写、预读。类比biolatency 像统计仓库取货时间的分布——发现有两种模式。biosnoop每次 I/O 的细节# biosnoopTIME(s)COMM PID DISK T SECTOR BYTES LAT(ms)0.009165000jbd2/nvme0n1p1174nvme0n1 W211627281920.430.011836000mysqld1948nvme0n1 W1043467240960.450.016809000mysqld1948nvme0n1 W102277122621441.820.017184000mysqld1948nvme0n1 W102282242621442.19关键列完成时间、进程名、PID、磁盘、类型R/W、扇区、大小、延迟注意默认输出可能很多——生产环境常用-Q看排队时间或重定向到文件后分析。找离群点把输出排序看延迟最高的几个# sort -n -k 8,8 out.biosnoop01.txt | tail -5类比biosnoop 像流水账——每个订单的完整记录可以找异常订单。iotop / biotop实时看谁最忙# iotop -bod5TID PRIOUSERDISK READ DISK WRITE SWAPIN IO COMMAND22400be/4 root4.78K/s0.00B/s0.00%13.76%[flush-252:0]279be/3 root0.00B/s1657.27K/s0.00%9.25%[jbd2/vda2-8]# biotopPID COMM D MAJ MIN DISK I/O Kbytes AVGms14501cksumR2021xvda1361288323.396961ddR2021xvda11628130240.59326jbd2/xvda1-8 W2021xvda131683.00关键列iotopSWAPIN等 swap 的比例、IO等 I/O 的比例biotopI/O次数、Kbytes吞吐、AVGms平均延迟注意iotop有时会低估写入biotop基于 BPF更准确。两者都默认每秒刷新。类比这两个工具像实时看谁在仓库最忙——一眼看到最活跃的企业。biostacksI/O 的代码路径# biostacks.btusecs[blk_account_io_start1 blk_mq_make_request1069... ext4_file_read_iter... __vfs_read... sys_read...]:[4K, 8K)37||用途看 I/O 是从哪个代码路径发起的——发现意想不到的 I/O。比如你可能发现磁盘 I/O 不是来自应用读文件而是来自内核后台扫描kswapd、jbd2或者来自你以为不碰磁盘的操作比如stat触发的元数据 I/O。类比biostacks 像给每个订单贴来源标签——发现仓库 I/O 到底从哪来。blktrace深入块设备层blktrace是内核级的块设备追踪框架比biosnoop更底层、更详细。# btrace /dev/sdb8,16310.42960414520442A R1847738798-(8,17)1847738168,16320.42960456920442Q R1847738798[cksum]8,16330.42960601420442G R1847738798[cksum]...8,16110.4402271440C R1847738798[0]每个 I/O 会显示多个事件A重映射、Q入队、G取请求、I插入、D发给设备、C完成。能看到每个阶段的耗时。# btt -i out.binQ2C0.0001980560.0009647840.00957821312308D2C0.0001956850.0008858330.00608353812308关键指标Q2C从入队到完成的总时间D2C从发给设备到完成——这才是真正的磁盘延迟I2D从插入队列到发给设备——队列等待时间注意blktrace开销大只适合短时间使用。日常用biolatency和biosnoop就够了。类比blktrace 像给仓库全流程装摄像头——从下单到收货每个环节都拍下来。bpftrace自定义追踪bpftrace让你自己写工具。几个磁盘相关的常见场景I/O 大小分布bpftrace-et:block:block_rq_issue { bytes hist(args-bytes); }按 I/O 类型统计bpftrace-et:block:block_rq_issue { [args-rwbs] count(); }磁盘错误bpftrace-et:block:block_rq_complete /args-error/ { printf(dev %d type %s error %d\n, args-dev, args-rwbs, args-error); }I/O 延迟直方图原理演示bpftrace-e t:block:block_rq_issue { start[args-dev, args-sector] nsecs; } t:block:block_rq_complete /start[args-dev, args-sector]/ { usecs hist((nsecs - start[args-dev, args-sector]) / 1000); delete(start[args-dev, args-sector]); }注意I/O 可能很多——先看biolatency和biosnoop是否够用。用bpftrace时加过滤条件或先用iostat看 I/O 频率。类比bpftrace 像自己组装检测设备——想要什么指标自己搭一个。smartctl看磁盘健康# smartctl --all -d megaraid,0 /dev/sdbSMART Health Status: OK Current Drive Temperature:23C Elementsingrown defect list:0Error counter log: Errors Corrected by Total Correction Gigabytes Total ECC rereads/ errors algorithm processed uncorrected fast|delayed rewrites corrected invocations[10^9 bytes]errors read:741619700741619774161971886.4940write:000001349.9990关键信息SMART Health Status总体健康Current Drive Temperature温度Elements in grown defect list坏道数量Error counter logECC 纠正的错误——增长快说明磁盘快坏了用途预测磁盘故障——在磁盘彻底坏掉之前换掉它。注意云实例通常看不到 SMART 数据虚拟磁盘。-d megaraid,0是 RAID 卡后面的磁盘——需要指定控制器。类比smartctl 像给仓库做体检——温度、坏货架、错误率。MegaCliRAID 控制器事件# MegaCli -AdpEventLog -GetLatest 50 -f lsi.log -aALL# more lsi.logseqNum: 0x0000282f Time: Sat Jun1605:55:052012Code: 0x00000023 Class:0Event Description: Patrol Read complete用途看 RAID 控制器的事件日志——发现后台任务如 Patrol Read、重建影响性能。注意MegaCli是 LSI/Broadcom 的专有工具只支持特定 RAID 卡。其他控制器有自己的工具hpacucli、arcconf等。类比MegaCli 像看仓库调度中心的日志——发现昨天晚上有例行盘点所以慢了。SCSI 日志# 开启 SCSI 日志sysctl-wdev.scsi.logging_level03333333333# 或者用 sg3-utilsscsi_logging_level-s--all3# dmesg 看输出dmesg[542136.259412]sd0:0:0:0: tag#0 Send: scmd 0x0000000001fb89dc[542136.259422]sd0:0:0:0: tag#0 CDB: Test Unit Ready 00 00 00 00 00 00[542136.261103]sd0:0:0:0: tag#0 Done: SUCCESS Result: hostbyteDID_OK driverbyteDRIVER_OK用途调试磁盘错误和超时——能看到每个 SCSI 命令的发送、完成、结果。注意输出量可能很大——可能淹没系统日志。只在排查问题时临时开启排查完就关掉。类比SCSI 日志像仓库的底层通信记录——每个货架操作都记下来。实验工具# 顺序读测试ddif/dev/sda1of/dev/nullbs1024kcount1k# 用 O_DIRECT 绕过缓存更准确地测磁盘ddif/dev/zeroofout1bs1024kcount1000oflagdirect# 轻量级 I/O 延迟测试ioping /dev/nvme0n1# 灵活定制 I/O 负载fio--runtime60--time_based--namerandread\--numjobs1--rwrandread--bs8k--size5g# 标准磁盘读测试hdparm-Tt/dev/sdb注意dd默认会走缓存——测试磁盘要加oflagdirect。fio是最灵活的磁盘测试工具支持自定义负载、并发、访问模式。类比这些像给仓库做压力测试——看它能承受多少订单。可视化延迟散点图每个 I/O 一个点——x 轴完成时间y 轴延迟。延迟(ms) ^ | * | * | * | * | * | * | * * * | * * * * ------------------------------------ 时间找离群点——那些特别慢的 I/O。延迟热力图x 轴时间y 轴延迟颜色 频率。延迟 ^ | . . . . . . . . | . . . . . . . . | ▒▒ ▓▓ ▒▒ ▓▓ ▒▒ ▓▓ ▒▒ ▓▓ | ██ ██ ██ ██ ██ ██ ██ ██ ------------------------- 时间看分布模式——双峰离群点周期性偏移热力图x 轴时间y 轴磁盘偏移颜色 频率。偏移 ^ | ▓▓ ▓▓ ▓▓ ▓▓ | ▒▒ ▒▒ ▒▒ ▒▒ | ██ ██ ██ ██ ------------------------- 时间看访问模式——顺序斜线还是随机散点。三、总结不要看忙不忙要看排不排队——利用率 60% 就可能开始排队看aqu-sz比看%util重要不要看平均多快要看延迟分布——磁盘延迟是双峰的平均值把两个模式搅成了一锅粥随机 I/O 对机械磁盘是灾难——尽量把随机变成顺序——比如用 COW 文件系统。SSD 对随机 I/O 友好得多iowait 会骗人——CPU 变忙时 iowait 会降但磁盘问题还在看pidstat -d的iodelay才靠谱磁盘是共享队列——CPU 和内存是私有的磁盘是所有人排同一条队VIP 也会被普通人堵住。下一章讲网络——也就是快递网络。仓库讲完了接下来看城市的通信系统。快递网络负责把数据从一个地方送到另一个地方它的速度和可靠性直接决定城市能不能和外界高效协作。