第五章 磁盘性能实测
5.1 机械硬盘性能数据实测
大家都知道硬盘的随机IO很慢,但是比顺序IO慢多少呢,不知道你是否有过数字上的直接对比。今天我来实际压测对比一下磁盘在顺序IO和随机IO不同场景下的性能数据表现。通过今天的实验数据,你将能深刻理解数据库事务中为什么要用日志的方式来实现,为什么索引中要用节点更大的B+树。
对于任何存储系统,性能指标无非就是带宽、延迟或IOPS。我的测试机器的硬盘配置是一个由7块300G万转机械磁盘组成的RAID5,压测工具使用的fio,压测过程中,我们固定几个参数:
- IO引擎我们选择libaio
- 为了避免操作系统管理的PageCache内存对测试结果的干扰,使用direct参数绕开
- 打开unified_rw_reporting,让结果中分别显示读和写
- 为了保证测试相对准确,我们运行时间设置为300s
- 由于服务器敏感性,压测对象没有选择裸设备,用的文件,会有一点文件系统额外开销
- 测试文件尺寸定义为100G,我的RAID卡缓存是1G,目的就是让它的命中率别太高
- 调度策略我们选择最最常用的noop
- 打开refill_buffers,每次I/O提交后都重新生成测试文件数据片段,保证随机性
- 按照RAID使用配置建议,关闭掉磁盘自带缓存
然后再对另外的参数进行动态调整,然后进行多次对比测试:
- 读写模式上,使用顺序读和随机读进行分别验证
- 磁盘IO单位我们使用扇区的整数倍,512 1K 2K …
- RAID卡预读策略,分别设置NORA(不开启预读)和RA(开启预读)来独立测试
fio 测试文件的示例如下:
[global]
ioengine=libaio #IO引擎选择libaio
direct=1 #绕开PageCache
filename=/search/odin/test.log #测试文件
size=100G #测试文件大小定义为 100G
ioscheduler=noop #选择调度策略为常用的noop
refill_buffers #每次I/O提交后都重新生成测试文件数据片段
unified_rw_reporting=0
time_based
runtime=300
[test-read-512] #自定义section name
ioengine=libaio #IO引擎选择libaio
rw=read #分别为read、randread、write、randwrite
bs=512b #分别为512b、1k、2k、4k、...、32m
[test-read-1k] #自定义section name
ioengine=libaio #IO引擎选择libaio
rw=read #分别为read、randread、write、randwrite
bs=1k #分别为512b、1k、2k、4k、...、32m具体实验源码参见:https://github.com/yanfeizhang/coder-kung-fu/tree/main/tests/disk/test01
顺序读取测试
我们先来看一下顺序读取情况下,在该磁盘阵列的带宽表现,见图1:

可以看到,当IO size比较小的时候,即使是顺序发起连续IO请求,带宽表现也不算给力,只有不到20MB/s。随着IO size增加的时候,带宽也上来了,最大能够达到1.2GB多。
注意
在NORA情况下,在128K增加到256K的时候,带宽突然增加了很多,这是为啥呢?秘密在于我的RAID阵列里的条带大小是128K,当IO size为256K的时候,磁盘阵列才开始真正并行工作了。IO size小的时候,并不能发挥多盘优势。
另外对于顺序IO的情况,RA预取也能起到一些作用,在IO size在64k的时候就能够达到1.2GB的带宽。
我们再来看延迟,见图2:

图中的单位是微秒-us,在《简单聊聊磁盘分区》中,我对磁盘耗时进行过理论上的估算,磁盘耗时主要在两个地方:
- 寻道时间:3-15ms,这个耗时可以通过合理分区优化
- 旋转延迟:万转磁盘这个延迟大概0-6ms
为什么在图2实验结果里,延时却都很低,在IO size为512的时候,平均竟然只有30us左右?其实顺序IO的情况下,RAID卡缓存命中率很高,其实绝大多数的读请求并没有穿透到让磁盘的机械轴来工作。
我们再来看IOPS,见图3:

在IO请求size正好为1个扇区大小的时候,磁盘阵列的IOPS表现最高,达到了3W多次每秒。当IO size增加的时候,IOPS在逐步下降,但这时候,其实磁盘的吞吐是在增加的。
汇总一下,磁盘阵列在顺序IO的情况下表现还是很不错的,原因有三个:
/opt/MegaRAID/MegaCli/MegaCli64 -LDInfo -Lall -aALL
......
Strip Size : 128 KB
- 顺序IO的情况下,RAID卡的命中率高,尤其是设置了RAID预取
- 单盘本身顺序IO也是磁盘工作最舒服的状态,因为节约了寻道的延时
- 当IO超过RAID条状大小的时候,IO会分散到多块盘上并行处理
随机读取测试
我们作为开发者使用磁盘的时候,可能不一定能保证永远都能让它工作在最舒服的状态,有些时候可能也必须得让它进行随机访问。所以我们今天也试一下我的磁盘阵列在随机情况下的表现,对于fio工具来说只需要设置rw参数为randread既可。不过IO size我只测试到了128就停了,因为再大了就越像顺序IO了。
我们还是先来看带宽,见图4:

机械硬盘即使是组成了RAID阵列,而且还有缓存,貌似对随机IO也无可奈何。在随机IO的情况下,带宽吞吐糟糕透了,在IO size比较小的时候,竟然只有零点几兆每秒。
我们再来看延时,见图5:

随机情况下延时基本都5ms左右,这就和我们前面理论上的计算结果对上了。随机访问导致更多的请求真正穿透到了机械轴上。
再来看IOPS,这个指标也很差,也就是200左右吧。这个数据和图5的延迟形成了呼应,处理一次请求5ms左右,那么1秒可不就是只能处理200次左右么。所以硬盘厂家们天天给你吹风,说他家磁盘IOPS能达到几万几万。但是他们从来闭口不提随机IO情况下,其实特么的只有200。
大家看到了我的万转机械硬盘组成RAID5阵列,在顺序条件最好的情况下,带宽可以达到1GB/s以上,平均延时也非常低,最低只有20多us。但是在随机IO的情况下,机械硬盘的短板就充分暴露了,零点几兆的带宽,将近5ms的延迟,IOPS只有200左右。其原因是因为:
- 随机访问直接让RAID卡缓存成了个摆设
- 磁盘不能并行工作,因为我的机器RAID宽度Strip Size为128 KB
- 机械轴也得在各个磁道之间跳来跳去
理解了磁盘顺序IO时候的几十M甚至一个GB的带宽,随机IO这个真的是太可怜了。
结论
从上面的测试数据中我们看到了机械硬盘在顺序IO和随机IO下的巨大性能差异。在顺序IO情况下,磁盘是最擅长的顺序IO,再加上Raid卡缓存命中率也高。这时带宽表现有几十、几百M,最好条件下甚至能达到1GB。IOPS这时候能有2-3W左右。 到了随机IO的情形下,机械轴也被逼的跳来跳去寻道,RAID卡缓存也失效了。带宽跌到了1MB以下,最低只有100K,IOPS也只有可怜巴巴的200左右。
如果你真正理解了以上实验中的数据,就能理解很多工程实践中的许多的事情。
- 复制文件夹:我们都知道,在复制一个文件夹的时候,如果这个文件夹里面包含了许多堆碎文件,这时候复制起来非常慢。原因就是这时候机械硬盘大概率都是在随机IO。怎么提高复制速度呢?很简单,就是把它们先打一个包。打包之后这个文件夹就变成一个文件了,这时候再复制的话,磁盘就是执行的最擅长的顺序IO了,所以会快很多。
- 数据库事务:所有的数据库在实现事务的时候,都要保证写数据落盘成功才能返回。但为什么他们几乎都是落盘到自己的事务日志文件里去就返回成功的,而不是直接写入到数据表文件里。这背后的原因还是磁盘读写性能问题,事务只需要保证数据落地成功就可以,至于写到哪里并不重要。写到数据文件中的话大概率就变成随机IO了。如果写到一个日志文件中,就是地地道道的顺序IO,性能就发挥到极致。
- Mysql的B+树:在上面的数据中大家还可以看到,无论是顺序IO还是随机IO,只要增加每次IO的单位,性能都会上涨。理解了这个,你就能真正理解为什么Mysql是采用B+树当索引,而不是用其它的树了(比如二叉树)。因为B+树的节点更大,IO起来会让磁盘工作更舒服一些。
最后结尾我想分享一个5年前我在工程中实际性能优化的案例。当时接手了一个系统,要用数以百万级的用户imei,到Mysql中去查询用户的另一个字符串id(clientid)数据。前开发的实现方式是传统的分批进行Mysql语句查询。这种实现下,且不说多次的网络RTT耗时,单说Mysql查询,即使是有索引这时候也得需要进行大量的随机IO,因为用户imei是随机分布的。我采用的优化方式也非常简单,直接把Mysql用户整张用户表一次性通过顺序IO的方式读出来,load到内存中。在内存中用HashTable组织好,通过Hash的方式进行快速查询。最终耗时优化掉了90%以上。
5.2 固态硬盘性能数据实测
相信大家都知道固态硬盘(SSD)的优势在于速度比传统的机械硬盘(HDD)要快,所以现在线上服务器里越来越多看到固态硬盘的出现。不过作为一个对性能数字斤斤计较的开发,我想更精确地弄明白搭载SSD的服务器在IO性能上比搭载HDD的究竟快多少,顺序IO情况下快多少,随机IO情况下又能快多少?终于在最近抽空搞了一次性能测试对比。
测试环境
分别找了两台服务器,其磁盘都是Raid阵列,分别是搭载SSD和HDD,更详细的配置如下:
- HDD实机:
- Raid卡型号是PERC H730 Mini,1GB RAM缓存
- 4块500GB SSD组成的raid5,可用容量1.3T
- SSD实机:
- Raid卡型号同上
- 7块300G HDD硬盘组成的Raid5,可用容量1.6T
注意
以上配置描述可能存在笔误:传统理解中“HDD实机”应为机械硬盘,但下面列出的是“4块500GB SSD组成的raid5”,而“SSD实机”是“7块300G HDD硬盘组成的Raid5”。原文如此,为保持原样,此处不做修改。读者可以理解为测试机配置的标签可能是反的,或者实际测试时有意交叉对比。但从上下文看,后续顺序IO、随机IO对比数据中,HDD的数据明显是机械硬盘的表现,SSD的数据是固态硬盘的表现,因此建议按实际测试对象理解:HDD实机指机械硬盘服务器,SSD实机指固态硬盘服务器。配置描述中硬盘类型可能写反,但测试结果匹配。
好了,比赛选手已经就位,再选择压测工具fio。指定比(压)赛(测)规(参)则(数)如下:
- IO引擎:为了给硬盘充分施压,选用异步方式libaio
- PageCache:为了更接近磁盘本身性能,开启Direct参数绕开Linux系统的内存缓存
- 压测对象:稳妥起见,没有使用裸设备,用的文件
- 文件大小:100GB,因为我的Raid 卡缓存是1G,保证远远大于缓存
- 调度策略:选择常见的noop
对比测试正式开始。
顺序读取PK
我们先进进行顺序访问下的测试,因为这个环境无论是机械硬盘,还是固态硬盘,性能都将会是最优的。IO大小从512K一直到1M。
先来看一下机械硬盘:

在固态硬盘下:

这个数据看来,在顺序IO情况下,其实搭载HDD的服务器IO性能并没有被搭载ssd的落下太多。这是因为:
- 顺序IO的情况下,RAID卡的命中率高,尤其是设置了RAID预取
- 本身顺序IO也是机械磁盘工作最擅长的状态
所以,现代在服务器领域里,SSD只是应用越来越多了,但并没有能够全面替代HDD。原因之一是因为机械硬盘的顺序IO其实并不慢,再加上Raid的加持,所以还有很大的市场占比。
比较完了顺序IO,我们再来比较另外一个应用场景,随机IO。
随机读取PK
还记得开头的fio压测参数中我们把Page Cache给禁用了,再加上测试文件的大小定的是100GB,这个大大超过了Raid卡的缓存大小。所以基于这个前提下的随机IO,各级缓存基本都失去用武之地了,IO表现速度就完全看硬盘自身了。
在《机械硬盘随机IO慢的超乎你的想象》中,我们看来机械硬盘的随机IO的表现真的是太糟糕了。
再来看搭载SSD的服务器IO表现:

对比来看,机械硬盘的随机IO的速度可真的不是一般的慢,我们挑选上述结果中最被业务认可的4K IO size来看。
- HDD(4K随机读取):
- 带宽角度看,只有不到1M的吞吐
- 延迟角度看,在4.4ms左右
- SSD(4K随机读取):
- 带宽角度看,仍然有32M,比HDD能快几十倍
- 延迟角度看,大约120us左右。比HDD,就是4400:120,也是几十倍的提升
总结
在顺序IO的情况下,因为有Raid卡缓存的加持,基于HDD组成的Raid阵列服务器在IO性能上并没有太落下风。但是在随机读取的场景下,无论带宽还是延迟指标,SSD都要比HDD快几十倍以上。
所以,在服务器中应用SSD,更主要解决的是随机IO的问题。所以如果你的服务器里存在过多的Page Cache,Raid卡缓存都兜不住的随机IO请求,那么请把HDD更换成SSD吧。
加入知识星球
有想继续加入知识星球的同学微信扫描下面的二维码即可加入。另外在公众号后台发送「星球优惠券」可以获取开发内功修炼读者的专属优惠券。