
你有没有遇到过这种情况程序跑起来CPU使用率不高、内存也很充裕但整个系统就像被什么东西卡住了一样点一下窗口要等好几秒才反应。我这些年排查类似的性能问题十次里有七次最后都指向同一个地方——磁盘I/O。磁盘I/O这个东西说简单也简单就是读写数据说深也深从物理结构到操作系统调度哪一层都可能成为瓶颈。我最早做后端性能优化的时候也曾经天真地以为瓶颈在SATA接口带宽上后来才发现一块7200转的机械硬盘随机读写连接口带宽的十分之一都用不满。真正决定磁盘I/O性能的是那块每分钟转几千圈的盘片和那个悬浮在盘片上方几纳米处的磁头。这篇文章是《深入理解磁盘I/O》系列的第一篇先讲最底层的东西磁盘的物理结构和运行原理。为什么要从物理结构讲起因为上层所有的I/O行为——顺序读快、随机读慢、IOPS上不去、RAID怎么选盘——本质上都是被物理结构锁死的。搞懂了这一层后面再看操作系统怎么排队、怎么调度、怎么缓存你才会有那种“原来如此”的通透感。这篇内容适合正在做后端开发、数据库运维、存储系统设计或者单纯想搞清楚自己电脑为什么越用越卡的读者。1. 先纠正一个普遍误解瓶颈从来不在接口带宽1.1 接口速率与肉眼可见现实之间的巨大落差很多人看磁盘性能第一眼永远盯着接口参数。SATA 3.0理论速率6GbpsSAS 12Gbps听着挺唬人。但你要真去测一块机械硬盘的顺序读写能跑到200MB/s已经算优秀选手了多数7200转盘也就150MB/s上下。这中间的差距大到什么程度呢SATA 3.0去掉编码开销之后实际可用带宽大约600MB/s机械硬盘顶天用掉三分之一。也就是说接口不仅不是瓶颈反而是严重过剩的资源。我见过有人为了提升磁盘性能把SATA 2.0的机器换成SATA 3.0的结果测试数据几乎没变化——因为磁盘本身根本跑不满旧接口。真正的瓶颈藏在另一个地方从盘片上读取一个扇区需要等待盘片旋转到磁头下方写入一个扇区需要磁头移动到正确的磁道。这些动作的单位是毫秒。而CPU缓存的访问延迟是纳秒级内存是几十纳秒即便是SSD也只有几十微秒。毫秒跟纳秒之间隔着六个数量级这才是磁盘慢的根源。1.2 为什么慢这个属性无法用软件优化抹平这里有一个很残酷的事实机械硬盘从1956年IBM发明第一块硬盘到现在核心机械结构几乎没有本质变化——都是旋转盘片加移动磁头。工程师们在过去六十年里把电机精度、磁头灵敏度、记录密度做到了极致但物理法则摆在那儿一个物体要在盘片上方移动并定位这个动作本身就要花掉几毫秒。操作系统的各种优化——缓存、预读、I/O调度算法——本质上都是想办法“减少机械动作的次数”或者“把随机动作变成顺序动作”。但它们永远无法消除机械动作本身。这就是为什么同样的I/O优化手段放在机械硬盘上效果立竿见影放在SSD上却可能微乎其微——因为SSD没有机械运动随机访问和顺序访问几乎没差别。1.3 这篇文章覆盖的内容边界我计划用三到四篇的篇幅把磁盘I/O整个链路讲完这篇是第一篇聚焦两个核心问题硬盘内部长什么样以及一次磁盘I/O请求在物理层面是怎么被执行的。搞清楚这两个问题你就掌握了分析一切磁盘性能问题的“底层坐标系”。后续文章会陆续覆盖操作系统I/O栈、I/O调度算法、page cache、直写与回写这些主题到时候你会发现上层那些看似复杂的机制全都是为适配这一层的物理特性而设计的。2. 一块硬盘的内部解剖盘片、磁头与电机2.1 数据到底存哪儿盘片与磁性介质拆开一块机械硬盘里面通常叠着1到4张圆形的盘片材质是铝合金或者玻璃基板。盘片表面镀了一层厚度只有几纳米的磁性材料数据就是以磁化方向的形式记录在这层材料上的——一个微小的磁性区域极化为某个方向代表二进制里的0或1。关键点在于这些磁性区域不是随便铺开的而是沿着盘片表面形成一圈一圈的同心圆轨道这些轨道叫作磁道。每一条磁道又被均分成若干段每一段叫一个扇区。磁盘最小的读写单位就是扇区你不可能只读半个扇区也不可能一次读写小于一个扇区的数据这是硬件层面的硬限制。关于扇区尺寸历史上有过一次重要变迁。传统硬盘物理扇区是512字节后来容量做大了512字节的扇区带来的格式开销太大行业逐步过渡到4KB物理扇区。但操作系统层面的逻辑扇区仍然暴露512字节方便兼容老软件。这个差距引发了一个非常经典的问题——4K对齐我在后面专门用一节来展开。2.2 机械手臂的末端磁头如何工作读取和写入数据靠的是磁头它安装在一条可以摆动的机械臂末端。机械臂由音圈电机驱动能在几十毫秒内把磁头从盘片最外圈摆到最内圈。注意“悬浮”两个字——磁头不是贴着盘面的而是依靠盘片高速旋转时带动气流在磁头与盘面之间形成一层几纳米到十几纳米的气垫让磁头“飞”在盘片上方。读写是两个独立的过程。写入时磁头通过线圈产生强磁场把盘片表面微小区域的磁化方向改变从而记录数据读取时磁头感应盘片表面磁场的变化把它转成电信号。现代硬盘的磁头其实是一个“读写复合头”写元件和读元件集成在同一个滑块上体积比头发丝还小。这块有一个非常实际的教训机械硬盘在通电运行时被搬动或撞击磁头可能会直接砸到盘面瞬间刮掉磁性涂层产生物理坏道。飞机托运电脑、开机状态摔硬盘都属于“作死”操作。相比之下SSD没有磁头盘片这种精密结构抗物理冲击能力天然强得多。2.3 主轴电机与转速为什么都在说7200转盘片被固定在主轴电机上电机带着盘片以恒定角速度旋转。转速是硬盘最重要的物理参数之一因为它直接决定了“等数据转过来”的时间。消费级硬盘通常5400转或7200转企业级高性能硬盘有10000转甚至15000转的。转速越高单位时间内扇区扫过磁头的次数越多你等一个随机扇区转到磁头下面的平均时间就越短。但转速提升的代价也很直接轴承磨损加剧、噪音增大、功耗和发热上升、抗震性变差。所以你会看到笔记本用的5400转盘省电安静服务器用的15000转盘性能好但价格昂贵且娇气。还有一个容易忽略的点恒定角速度意味着盘片外圈的线速度高于内圈所以外圈磁道的数据吞吐率更高。磁盘做顺序读写时从外圈往内圈写速度会逐渐下降头尾差距甚至能到50%。这也是为什么有些测试软件测出的顺序读写速率是一个区间而不是一个固定值。2.4 物理结构给I/O行为套上的锁总结一下上面这些物理组件你会发现它们共同决定了一个核心事实磁盘的每一次I/O都要先完成两个物理定位动作——把磁头摆动到目标磁道然后等盘片旋转到目标扇区。这两个动作都是纯机械运动时间以毫秒计。更关键的是不管你要读的数据是1个字节还是1个扇区这些等待都一样存在。“移动”的成本是固定的“传输”的数据量反而是可以变化的。这意味着什么如果我一次多读一些数据寻道和旋转的成本可以被摊薄到每字节上但如果每次都只读一小块那每一小块都要支付一次完整的机械运动成本。理解了这个摊薄逻辑你就能明白顺序I/O为什么远快于随机I/O。3. 一次磁盘I/O请求的完整旅程三大时间开销3.1 寻道时间磁头移动才是最大开销磁盘I/O的总时间通常由三部分组成寻道时间、旋转延迟、传输时间。三者中最不可控的是寻道时间。寻道时间指的是磁头从当前所在磁道移动到目标磁道所花费的时间。这个时间取决于两个因素一是磁头臂移动的距离二是音圈电机的响应速度。距离越远时间越长单磁道相邻移动可能只需要一毫秒出头而跨越整个盘面需要十几毫秒。硬盘厂商和数据手册里给出的“平均寻道时间”指的是所有可能寻道距离的时间加权平均值一般在4毫秒到9毫秒之间。这里我想强调一个很反直觉的事实寻道时间和读写的数据量没有一毛钱关系。不管是读4KB还是读4MB只要目标磁道相同寻道时间完全一样。这就是随机小I/O性价比极低的原因——你付了最贵的机械动作成本却只搬了指甲盖大小的一点数据。3.2 旋转延迟用转速算出来的等待时间磁头到达目标磁道之后还不能马上读写因为目标扇区此刻大概率不在这颗磁头的正下方。你得等盘片继续旋转直到目标扇区跟着盘面转到磁头下面。这段等待时间叫作旋转延迟。平均旋转延迟的计算很简单盘片转一圈需要的时间除以2。为什么除以2因为目标扇区相对磁头的初始位置是随机的平均来看相当于等半圈。7200转的硬盘转一圈的时间是60秒除以7200约8.33毫秒平均旋转延迟就是4.17毫秒。15000转的盘一圈只需要4毫秒平均旋转延迟约2毫秒。这个公式是所有IOPS估算的基础我建议你把它刻在脑子里。未来遇到“为什么这个阵列IOPS上不去”的疑问先算算转速给出的物理上限基本就能排除掉一大批“假问题”。3.3 传输时间顺序I/O为什么占便宜磁头定位完成、扇区到达之后真正的数据传输才开始。传输时间等于数据量除以磁盘的内部传输速率。内部传输速率是指磁头从盘片读取或写入数据的原始速度一般在120MB/s到200MB/s之间取决于磁道位置外圈更快。假设你在7200转的盘上做一个4KB随机读寻道时间按9毫秒算旋转延迟4.17毫秒传输时间只有4KB除150MB/s约0.027毫秒——几乎可以忽略。但如果做一次1MB的顺序读寻道一次9毫秒旋转延迟一次4.17毫秒传输时间却是1MB除150MB/s约6.7毫秒。传输时间占据了总耗时的一半以上寻道和旋转的成本被高度摊薄。这就是顺序I/O的本质优势同样的机械动作成本搬运了多得多的数据。为了利用这个特性操作系统和存储工程师想尽了各种办法——预读、日志结构化写入、I/O调度器的合并与排序——所有手段的核心思路都指向同一个方向在物理上制造尽量长的顺序读写序列。3.4 用一张表和一段脚本算出磁盘的真实IOPS上限IOPSInput/Output Operations Per Second衡量的是磁盘每秒能完成的I/O请求数。单块硬盘的IOPS上限可以由上一个公式直接推导IOPS约等于1000毫秒除以单次I/O总耗时。下面是几种典型硬盘的估算硬盘类型平均寻道时间平均旋转延迟4KB随机读单次耗时理论IOPS上限5400转消费盘约12ms5.56ms约17.6ms约577200转消费盘约9ms4.17ms约13.2ms约7610000转企业盘约4ms3ms约7ms约14315000转企业盘约3ms2ms约5ms约200你可以写个小脚本根据转速和寻道时间快速估算def estimate_iops(rpm, seek_ms, transfer_ms0.03): # 平均旋转延迟 60s / rpm * 1000ms / 2 rotate_ms 60 / rpm * 1000 / 2 one_io_ms seek_ms rotate_ms transfer_ms return round(1000 / one_io_ms, 1) # 示例7200转平均寻道9ms的盘4KB随机读 iops estimate_iops(7200, 9) print(f7200转盘随机读IOPS ≈ {iops})这个表给我们的启示非常直接单块机械硬盘的随机I/O能力天花板就在200 IOPS左右。而一块普通SSD随随便便几千上万IOPS高端NVMe SSD甚至能到几十万。所以当你设计一个数据库系统估算需要多少块机械盘才能扛住业务负载时乘以单盘几十到两百的IOPS几乎就能得出正确答案。4. 顺序I/O与随机I/O性能差距背后的物理逻辑4.1 靠缓存和预读究竟能掩盖多少性能缺陷前面说了随机I/O是磁盘的死穴但你可能有一个疑问为什么我电脑上的机械硬盘好像也没那么慢这就要提到两类关键优化一类在操作系统层面一类在硬盘内部的固件层面。顺序读时操作系统发现你在连续读取文件会主动向磁盘发出预读指令把当前文件后面还未被请求的数据块一股脑读入内存缓存。这样你下一次读取时数据已经在内存里等着了根本不用再次访问磁盘。顺序读性能高一部分是硬件的功劳另一部分是预读机制的功劳。写路径也有类似的优化。操作系统和磁盘都有自己的写缓存小块写请求会先被缓存起来操作系统觉得缓存数据积累得差不多了再一次性发一个较大的连续写请求给磁盘这样就把多次随机写变成了一次顺序写。但要注意这些优化只对具备明显顺序特征的工作负载有效。如果你的应用是真正的随机访问——比如数据库按主键访问离散的数据页——预读大概率读不到有用的数据写缓存也凑不齐连续的写块优化效果微乎其微机械盘还是原形毕露。4.2 典型负载对比数据库与视频流的I/O画像用两类典型业务来直观感受一下数据库OLTP业务比如订单系统、用户中心数据量一大基本上都是8KB到16KB的随机读写。用户的每次请求都要按索引去数据文件中找对应的数据页这个访问模式天然是随机的。一台7200转机械盘撑死能提供70到80的随机IOPS你可能遇到几十个并发请求磁盘就满负荷罢工了平均响应时间直线上升。这也是为什么现在稍有规模的应用数据库必须上SSD。反过来看视频监控存储、日志归档、大数据分析这类场景。视频监控是一路摄像头持续写入固定大小的录像文件日志系统是连续追加写入数据备份则是一次性大块顺序读。这些负载都是顺序I/O机械硬盘能发挥出全部顺序带宽一块7200转盘跑满150MB/s不成问题。用机械盘做这些事成本比SSD低得多容量又大性价比非常可观。4.3 RAID场景下的IOPS预算与磁盘数量估算如果你必须用机械盘承载一个对随机I/O有要求的业务那就得按IOPS预算来数盘。举个例子你的业务要求800 IOPS单块7200转盘能提供约76 IOPS哪怕是RAID 10读可以两块盘并行写需要两块盘同时动作你至少需要11到12块盘才能满足读需求写需求还要再往上加。算完之后你会发现用12块机械盘去换800 IOPS成本几乎和直接用SSD持平但延迟、噪音、功耗和故障率却全都不如后者。这种计算我建议所有做存储选型的人都提前做一遍。很多人采购服务器时只看了容量买了大容量机械盘上线后一压测才发现IOPS完全不够用再换盘的成本就高了。先算物理上限再做选型决策顺序不要反。5. 从CHS到LBA操作系统如何与磁盘对话5.1 CHS寻址时代把磁盘当成三维空间早期硬盘容量小操作系统直接用物理位置来定位数据使用的就是CHS三元组CCylinder柱面、HHead磁头、SSector扇区。柱面的概念是这么来的硬盘有多个盘片每个盘片对应两个磁头上下各一个所有盘片上半径相同的磁道在垂直方向上组成一个圆柱面。如果要读写某个扇区必须指定它在哪个柱面上、用哪个磁头、以及在该磁道上的第几个扇区。这种寻址方式看起来很直观但实际用起来很麻烦。磁盘的物理参数一变操作系统就得跟着适配而且早期的CHS参数还有BIOS与操作系统之间约定不统一的问题导致同一个硬盘在不同系统里容量不一样这在今天听着像天方夜谭当时确实真实发生过。5.2 LBA线性寻址把一切简化为一个数字后来的解决方案是LBA全称Logical Block Addressing逻辑块寻址。简单说就是让硬盘把所有可访问的扇区排成一条直线从0开始依次编号。操作系统和驱动只需要告诉硬盘“我要读第123456号逻辑块”磁盘内部自行把这个逻辑块号翻译成真实的磁道和扇区位置。LBA的引入对上层是一个巨大的简化从此操作系统不需要关心盘片有几张、磁头有几个、内圈外圈有什么区别它面对的就是一个扁平的线性存储空间。这个抽象非常成功以至于今天几乎所有存储接口——SCSI、SATA、NVMe——都采用类似的线性块寻址模型。5.3 一个常见的认知误区LBA与物理位置的对应关系我在实际工作中发现不少人对LBA有一个默认假设逻辑上相邻的块物理上也一定相邻。这个假设在早年基本成立但现代硬盘已经完全不是这样了。原因有二。第一硬盘出厂时可能就存在坏块固件会把坏块从地址映射表中移除用备用区域顶上。第二现代硬盘内部其实维护着一张LBA到物理扇区的映射表这张表在硬盘使用过程中会动态变化。一旦某个物理扇区出现不稳定固件会自动把数据重映射到备用区。更典型的例子是SMR硬盘它的逻辑块和物理磁道之间采用了更复杂的区段映射关系。所以说操作系统眼中的“相邻块”在物理盘片上可能隔了好几条磁道。这个误区会导致什么实际问题最典型的是你以为自己在做顺序写实际上在物理层是东一榔头西一棒子性能表现远低于预期——这种情况通常不是硬盘坏了而是硬盘特性与工作负载不匹配。6. 现代磁盘固件层的小动作NCQ、缓存与4K对齐6.1 NCQ让硬盘自己决定先执行哪个请求机械硬盘的随机I/O性能差但如果同时有多个I/O请求在排队能不能让硬盘自己聪明地安排执行顺序尽量减少磁头的来回跑动当然能这就是NCQNative Command Queuing原生指令队列在做的事。启用NCQ后操作系统可以一次性向硬盘提交最多32条I/O指令硬盘内部的调度器会分析这些指令的LBA地址按照“尽可能减少寻道距离”的原则重新排序执行。比如请求A在磁道100请求B在磁道110请求C在磁道5如果按顺序执行磁头要来回跑好几趟经过NCQ排序之后可能会先做磁道100和110的请求最后再回到磁道5。NCQ对多线程随机负载的提升是实打实的通常能带来10%到30%的IOPS改善具体取决于工作负载的并发度和地址分布。但在单线程、单请求的负载下NCQ基本没有用武之地。这也是为什么很多人说“SSD没必要纠结NCQ”的原因——SSD没有寻道动作重排序带来的收益微乎其微甚至可能因为增加固件处理开销而适得其反。6.2 磁盘自带的缓存读预取与写回的秘密现代机械硬盘板上都有一颗DRAM或者SLC闪存作为缓存容量从64MB到256MB不等。这个缓存干两件事读预取和写回。读预取相对简单——硬盘发现你是顺序读会自动把当前位置后面的数据提前读入缓存。下次请求到来时如果命中了缓存就不需要再等待机械运动响应延迟可以降到接近内存访问的水平。写回就激进得多操作系统发出写请求硬盘先把数据放入缓存立刻向上层返回“写完了”然后趁盘片转到合适位置时再把缓存里的数据真正落盘。这种设计大幅提升了用户体验但也埋了一颗雷如果写入的数据还在缓存里没有真正落盘此时断电数据就丢了。消费级硬盘对这类问题的保护相对有限企业级硬盘通常会有掉电保护电路利用超级电容给硬盘提供最后几毫秒的供电把缓存里残留的数据写入专用的非易失存储区。这也是企业级硬盘和消费级硬盘之间的一个重要区别。6.3 分区对齐与4K物理扇区老问题为何今天还在前面提到过1个细节现在物理扇区已经变成4KB但逻辑扇区仍然暴露512字节。这中间存在一个非常经典的性能陷阱4K对齐。在老式的512字节扇区时代分区起始位置可以落在任意扇区操作系统文件系统的逻辑块与物理扇区天然能对上。但到了4K物理扇区时代如果你的分区起始位置不是4KB的整数倍比如有些老工具默认让分区从第63扇区开始63乘以512字节等于31.5KB不是4KB的倍数那一个逻辑块就可能跨越两个物理扇区的边界。后果是什么每次写一个逻辑块磁盘不得不读写两个物理扇区甚至触发读改写流程——先读出整个4K物理扇区的旧数据修改其中一部分再整个写回去。这种写放大效应会严重拖垮随机写性能我见过某些测试中没对齐的分区随机写性能比对齐后的差了3倍。现在的操作系统安装程序默认都会对齐这个坑主要出现在两类场景一是用老版分区工具手动分区二是直接克隆或恢复旧系统镜像。如果你在排查性能问题且怀疑分区有问题用一个简单的命令检查Linux下用fdisk -l查看分区的起始扇区判断它除以8是不是整数因为8个512字节扇区等于4KB。不是整数就需要重新分区或使用对齐工具修复。6.4 一个物理结构决定I/O行为的极端案例SMR硬盘说到物理结构决定I/O行为没有比SMR硬盘更极端的例子了。传统硬盘使用PMR垂直磁记录磁道与磁道之间留有一定的物理间隙防止写入时影响相邻磁道。SMR改了一种思路让磁道像屋顶的瓦片一样叠加重叠排列理论上可以在同样的盘面面积下塞进更多磁道显著提高存储密度。听起来是白捡的容量但代价是灾难级的随机写性能。原因在于磁头的写入宽度大于单个磁道的宽度。你往重叠区里的某条磁道写入数据必然会破坏旁边那条磁道的数据。所以硬盘固件遇到这种情况必须先把这个磁道所在的整个区段读出来在缓存里合并修改再把整段数据写回去。一个针对单个磁道的小写请求实际执行时变成了读一大片、改一小点、写一大片。这个操作模式比普通的写放大严重得多导致SMR硬盘一旦进入随机写场景性能可以用“暴跌”来形容。我提这个例子是想说明一个非常重要的选型理念硬盘的每一项技术特性都是有代价的SMR用容量换走了随机写性能而它的物理结构决定了这种退化不是靠固件优化能挽回的。买了所谓的“大容量NAS盘”回家做了RAID跑起数据库之后发现写入速度惊人地慢大概率就是买到了SMR盘。所以在采购之前一定要搞清楚自己买的是PMR还是SMR以及你的业务主要是顺序写还是随机写。写在最后的一点实操体会系列的第一篇到这里差不多收尾了。我自己这些年排查I/O问题最深的一个感受是不要被操作系统层面的指标迷惑先回到物理层去算一笔账。磁盘的转速、平均寻道时间、扇区大小这些看似老掉牙的参数其实才是判断一切磁盘性能问题的第一性原理。比如你发现应用偶尔卡顿第一反应不应该是一边翻代码一边猜“是不是锁竞争”而是先去查一下这块盘在当前负载下IOPS是不是已经贴着物理上限跑了。如果确实触顶那问题不在于你的代码写得不好而是硬件选型没跟上需求。反过来如果IOPS远未触顶但性能还是很差那才需要去更上层查I/O调度、日志刷盘策略、文件系统配置这些问题。下一篇我会顺着I/O路径往上走聊操作系统怎么管理这些块设备的I/O请求——就讲讲I/O调度算法的那些事。在那之前建议你花几分钟看一下自己服务器上磁盘的型号和转速算一算理论IOPS上限。有了这个数字垫底后面所有关于性能的讨论才有基准。