磁盘原理
磁盘是计算机组成一个重要部分,也是最主要的存储部件。
我们知道内存也可以用于存储数据,而且读写速度非常快,通常比磁盘要快几个数量级,但是内存资源是珍贵而且有限的,通常我们的服务器内存也就是 16G,32G。
而需要存储的数据动不动就是几百 G 以上,很明显不能把数据都存储到内存,而且内存的数据是暂时的,当计算机重启就会丢失,不适合存储需要持久化的数据。
所以基于磁盘的文件存储系统是非常重要和常用的,我们平时使用的数据库、mq 消息存储、程序日志等都是存储在磁盘上。
计算机工作过程中对磁盘的读写是非常频繁的,这也是我们常说的磁盘 IO,而读写主要有随机读写和顺序读写两种方式,这两者有很大的性能差异。接下来我们先看下磁盘的工作原理。
组成部件
如图是磁盘的物理结构,主要部件有磁盘盘片,传动手臂,读写磁头和主轴马达。
磁盘盘片是数据实际存储的位置,读写就是通过主轴马达让磁盘盘片转动,然后转动传动手臂上的读写磁头在磁盘盘片上进行读写操作。
磁道和扇区
上图显示的是一个盘片,盘片中一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。
磁盘的读写主要有三个步骤:
- 1.寻道
指将读写磁头移动到正确的磁道上所需要的时间,目前磁盘的平均寻道时间约为 3-15ms。 - 2.旋转延迟
指盘片旋转将请求数据所在的扇区移动到读写磁头下方所需要的时间。旋转延迟主要取决于磁盘转速,如 7200rpm 的磁盘平均旋转延迟大约为 4.17ms,转速为 15000rpm 的磁盘的平均旋转延迟为 2ms。 - 3.数据传输时间
指传输读写数据需要的时间,它主要取决于数据传输率。目前 IDE/ATA 能达到 133MB/s,SATA II 可达到 300MB/s 的接口数据传输率,数据传输时间通常远小于寻道时间和旋转延迟时间,计算时一般可以忽略。
通过上面的描述我们知道磁盘的读写性能消耗主要花在寻道和旋转延迟上,如果能减少这两个步骤所消耗的时间,读写速度将大大提升。
随机 IO 和顺序 IO
PageCache 页缓存,是操作系统为了提升磁盘的读写效率,将一部分内存用来加速文件读写的区域,如图 buffer/cache 所占的空间部分就是 PageCache 所使用(还有部分是 BufferCache 使用)。
PageCache 是操作系统用于缓存磁盘上的部分数据的内存区域,如果我们请求的数据正好在 PageCache 上,那么可以直接返回,免去了对磁盘的 IO 操作。
预读和回写
预读是指操作系统在将磁盘数据加载到内存中时,通常会将连续的后面几个页的数据也一起加载出来,这样如果后期读取的数据在这些页中,就可以直接从缓存读取,不需要再次从磁盘加载。
这个是出于一个原则:当读取一个数据时,很可能会对后续连续的数据进行读取。这个和 mysql 是类似的,尽管我们只读取一条数据,mysql 也会加载一页的数据出来。
回写是将数据写入到 PageCache,而不是直接写入磁盘,这和普通的内存写入效率是一样的。而 PageCache 的内容会由操作系统的 pdflush 异步线程在根据一定的策略在合适的时间同步到磁盘,pdflush 后面版本改成了效率更高的 bdi_writeback 机制。
可以看到使用 PageCache 是在程序和磁盘间使用内存做了一层中介,提升性能。但这并不是没有代价的,最直接的就是它占用了我们的实际内存,也就是上图的 free 空间,当实际内存不足时,就需要让操作系统释放 buffer/cache,毕竟这个是借用的。而写入 PageCache 也有丢失数据的风险,因为它是由操作系统异步写入磁盘。
顺序 IO 就是利用了 PageCache 实现的,由于大大降低了寻道和旋转延迟时间,其速度几乎接近于内存的读写速度。
而随机 IO 由于需要不断的进行寻道和旋转,效率很低。SSD 在设计上对寻道和旋转延迟上做了优化,随机读写效率有了很大提升,但是我们需要知道顺序读写始终有它的优势,可以想象我们再一张布满格子的纸上面写字,连续写的速度比随便挑格子写的速度要快得多,省去找格子和移动笔的时间。
kafka/rocketmq
kafka 和 rocketmq 存储消息都是持久化到磁盘上,它们就是利用磁盘的顺序 IO 来处理消息的读写,保持高性能。
我们可以看下 kafka 官网的介绍
标题就是:别担心文件系统的性能问题
翻译过来的主要内容就是:
- 1.文件系统的读写效率可以比想象中快得多
- 2.操作系统对顺序读写做了优化,其速度可以达到随机写入的 6000 倍,甚至比内存的随机读写还快
- kafka 对消息的读写使用的是 FileChannel 类(index 使用的是 mmap)
rocketmq 的设计参考了 kafka,也使用顺序写对消息进行持久化
源码:
rocketmq 使用 java 开发,主要是使用 MappedByteBuffer 对文件进行读写,它使用 mmap 的方式对地址进行映射,以减少数据拷贝的次数,这点我们在零复制有所介绍。
rocketmq 默认使用 MappedByteBuffer,而不是像 kafka 使用 FileChannel。FileChannel 并非直接写入 PageCache,而是先写入内存,再写到 PageCache,其 force()方法可以通知系统把数据刷到磁盘。
FileChannel 相比 MappedByteBuffer 多了一层写内存的动作,但 FileChannel 是基于 block 的操作,在数据比较大时仍然有优势(通常是 4kb 的整数倍时)。
所以两者没有哪个是文件 IO 的银弹,可以简单认为前者在写入小数据到文件时具有一定优势,后者对数据大小比较大时基于块的操作效率比较好,通常我们的 mq 传输的数据应该尽可能小。
Active mq 在社区的反响平平,原因就是它读写都是使用 RandomAccessFile 随机读写的方式,效率较低。
参考
- rocketmq while not use FileChannel.write
- MappedByteBuffer VS FileChannel ,孰强孰弱?
- 磁盘 io 那些事
- 文件 IO 操作的一些最佳实践
From jmilktea
END
文章来源:TrustZone
推荐阅读
更多物联网安全,PSA 等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA 技术交流群,请备注研究方向。