Kafka和RocketMQ底层存储之那些你不知道的事
大家好,我是yes。
我们都知道 RocketMQ 和 Kafka 消息都是存在磁盘中的,那为什么消息存磁盘读写还可以这么快?有没有做了什么优化?都是存磁盘它们两者的实现之间有什么区别么?各自有什么优缺点?
今天我们就来一探究竟。
先说下快的主要原因就是顺序读写、mmap、sendfile。我们先来看看这几点,然后再盘一下 RocketMQ 和 Kafka是如何应用的。
存储介质-磁盘
一般而言消息中间件的消息都存储在本地文件中,因为从效率来看直接放本地文件是最快的,并且稳定性最高。毕竟要是放类似数据库等第三方存储中的话,就多一个依赖少一份安全,并且还有网络的开销。
那对于将消息存入磁盘文件来说一个流程的瓶颈就是磁盘的写入和读取。我们知道磁盘相对而言读写速度较慢,那通过磁盘作为存储介质如何实现高吞吐呢?
顺序读写
答案就是顺序读写。
首先了解一下页缓存,页缓存是操作系统用来作为磁盘的一种缓存,减少磁盘的I/O操作。
在写入磁盘的时候其实是写入页缓存中,使得对磁盘的写入变成对内存的写入。写入的页变成脏页,然后操作系统会在合适的时候将脏页写入磁盘中。
在读取的时候如果页缓存命中则直接返回,如果页缓存 miss 则产生缺页中断,从磁盘加载数据至页缓存中,然后返回数据。
并且在读的时候会预读,根据局部性原理当读取的时候会把相邻的磁盘块读入页缓存中。在写入的时候会后写,写入的也是页缓存,这样存着可以将一些小的写入操作合并成大的写入,然后再刷盘。
而且根据磁盘的构造,顺序 I/O 的时候,磁头几乎不用换道,或者换道的时间很短。
根据网上的一些测试结果,顺序写盘的速度比随机写内存还要快。
当然这样的写入存在数据丢失的风险,例如机器突然断电,那些还未刷盘的脏页就丢失了。不过可以调用 fsync 强制刷盘,但是这样对于性能的损耗较大。
因此一般建议通过多副本机制来保证消息的可靠,而不是同步刷盘。
可以看到顺序 I/O 适应磁盘的构造,并且还有预读和后写。 RocketMQ 和 Kafka 都是顺序写入和近似顺序读取。它们都采用文件追加的方式来写入消息,只能在日志文件尾部写入新的消息,老的消息无法更改。
mmap-文件内存映射
从上面可知访问磁盘文件会将数据加载到页缓存中,但是页缓存属于内核空间,用户空间访问不了,因此数据还需要拷贝到用户空间缓冲区。

可以看到数据需要从页缓存再经过一次拷贝程序才能访问的到,因此还可以通过mmap来做一波优化,利用内存映射文件来避免拷贝。
简单的说文件映射就是将程序虚拟页面直接映射到页缓存上,这样就无需有内核态再往用户态的拷贝,而且也避免了重复数据的产生。并且也不必再通过调用read或write方法对文件进行读写,可以通过映射地址加偏移量的方式直接操作。

sendfile-零拷贝
既然消息是存在磁盘中的,那消费者来拉消息的时候就得从磁盘拿。我们先来看看一般发送文件的流程是如何的。

简单说下DMA是什么,全称 Direct Memory Access ,它可以独立地直接读写系统内存,不需要 CPU 介入,像显卡、网卡之类都会用DMA。
可以看到数据其实是冗余的,那我们来看看mmap之后的发送文件流程是怎样的。

可以看到上下文切换的次数没有变化,但是数据少拷贝一份,这和我们上文提到的mmap能达到的效果是一样的。
但是数据还是冗余了一份,这不是可以直接把数据从页缓存拷贝到网卡不就好了嘛?sendfile就有这个功效。我们先来看看Linux2.1版本中的sendfile。

因为就一个系统调用就满足了发送的需求,相比 read + write 或者 mmap + write 上下文切换肯定是少了的,但是好像数据还是有冗余啊。是的,因此 Linux2.4 版本的 sendfile + 带 「分散-收集(Scatter-gather)」的DMA。实现了真正的无冗余。

这就是我们常说的零拷贝,在 Java 中FileChannal.transferTo() 底层用的就是sendfile。
接下来我们看看以上说的几点在 RocketMQ 和 Kafka中是如何应用的。
RocketMQ 和 Kafka 的应用
RocketMQ
采用Topic混合追加方式,即一个 CommitLog 文件中会包含分给此 Broker 的所有消息,不论消息属于哪个 Topic 的哪个 Queue 。

所以所有的消息过来都是顺序追加写入到 CommitLog 中,并且建立消息对应的 CosumerQueue ,然后消费者是通过 CosumerQueue 得到消息的真实物理地址再去 CommitLog 获取消息的。可以将 CosumerQueue 理解为消息的索引。
在 RocketMQ 中不论是 CommitLog 还是 CosumerQueue 都采用了 mmap。

在发消息的时候默认用的是将数据拷贝到堆内存中,然后再发送。我们来看下代码。

可以看到这个配置 transferMsgByHeap 默认是 true ,那我们再看消费者拉消息时候的代码。

可以看到 RocketMQ 默认把消息拷贝到堆内 Buffer 中,再塞到响应体里面发送。但是可以通过参数配置不经过堆,不过也并没有用到真正的零拷贝,而是通过mapedBuffer 发送到 SocketBuffer 。
所以 RocketMQ 用了顺序写盘、mmap。并没有用到 sendfile ,还有一步页缓存到 SocketBuffer 的拷贝。
然后拉消息的时候严格的说对于 CommitLog 来说读取是随机的,因为 CommitLog 的消息是混合的存储的,但是从整体上看,消息还是从 CommitLog 顺序读的,都是从旧数据到新数据有序的读取。并且一般而言消息存进去马上就会被消费,因此消息这时候应该还在页缓存中,所以不需要读盘。

而且我们在上面提到,页缓存会定时刷盘,这刷盘不可控,并且内存是有限的,会有swap等情况,而且mmap其实只是做了映射,当真正读取页面的时候产生缺页中断,才会将数据真正加载到内存中,这对于消息队列来说可能会产生监控上的毛刺。
因此 RocketMQ 做了一些优化,有:文件预分配和文件预热。
文件预分配
CommitLog 的大小默认是1G,当超过大小限制的时候需要准备新的文件,而 RocketMQ 就起了一个后台线程 AllocateMappedFileService,不断的处理 AllocateRequest,AllocateRequest其实就是预分配的请求,会提前准备好下一个文件的分配,防止在消息写入的过程中分配文件,产生抖动。
文件预热
有一个warmMappedFile方法,它会把当前映射的文件,每一页遍历多去,写入一个0字节,然后再调用mlock 和 madvise(MADV_WILLNEED)。

我们再来看下this.mlock,内部其实就是调用了mlock 和 madvise(MADV_WILLNEED)。

mlock:可以将进程使用的部分或者全部的地址空间锁定在物理内存中,防止其被交换到swap空间。
madvise:给操作系统建议,说这文件在不久的将来要访问的,因此,提前读几页可能是个好主意。
RocketMQ 小结
顺序写盘,整体来看是顺序读盘,并且使用了 mmap,不是真正的零拷贝。又因为页缓存的不确定性和 mmap 惰性加载(访问时缺页中断才会真正加载数据),用了文件预先分配和文件预热即每页写入一个0字节,然后再调用mlock 和 madvise(MADV_WILLNEED)。
Kafka
Kafka 的日志存储和 RocketMQ 不一样,它是一个分区一个文件。

Kafka 的消息写入对于单分区来说也是顺序写,如果分区不多的话从整体上看也算顺序写,它的日志文件并没有用到 mmap,而索引文件用了 mmap。但发消息 Kafka 用到了零拷贝。
对于消息的写入来说 mmap 其实没什么用,因为消息是从网络中来。而对于发消息来说 sendfile 对比 mmap+write 我觉得效率更高,因为少了一次页缓存到 SocketBuffer 中的拷贝。
来看下Kafka发消息的源码,最终调用的是 FileChannel.transferTo,底层就是 sendfile。

从 Kafka 源码中我没看到有类似于 RocketMQ的 mlock 等操作,我觉得原因是首先日志也没用到 mmap,然后 swap 其实可以通过 Linux 系统参数 vm.swappiness 来调节,这里建议设置为1,而不是0。
假设内存真的不足,设置为 0 的话,在内存耗尽的情况下,又不能 swap,则会突然中止某些进程。设置个 1,起码还能拖一下,如果有良好的监控手段,还能给个机会发现一下,不至于突然中止。
RocketMQ & Kafka 对比
首先都是顺序写入,不过 RocketMQ 是把消息都存一个文件中,而 Kafka 是一个分区一个文件。
每个分区一个文件在迁移或者数据复制层面上来说更加得灵活。
但是分区多了的话,写入需要频繁的在多个文件之间来回切换,对于每个文件来说是顺序写入的,但是从全局看其实算随机写入,并且读取的时候也是一样,算随机读。而就一个文件的 RocketMQ 就没这个问题。
从发送消息来说 RocketMQ 用到了 mmap + write 的方式,并且通过预热来减少大文件 mmap 因为缺页中断产生的性能问题。而 Kafka 则用了 sendfile,相对而言我觉得 kafka 发送的效率更高,因为少了一次页缓存到 SocketBuffer 中的拷贝。
并且 swap 问题也可以通过系统参数来设置。
最后
这篇文章中间写 RocketMQ 卡壳了,源码还是不太熟,有点绕。 多亏丁威大佬的点拨。不然我就陷入了死胡同出不来了。
最后再推荐下丁威大佬和周继锋大佬的《RocketMQ技术内幕:RocketMQ架构设计与实现原理》。对 RocketMQ 有兴趣的同学可以看看。
文章如果哪里有纰漏请抓紧联系我,感谢!
我是 yes,从一点点到亿点点,我们下篇见。
往期推荐:

Kafka和RocketMQ底层存储之那些你不知道的事的更多相关文章
- Kafka与RocketMq文件存储机制对比
一个商业化消息队列的性能好坏,其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一. 开头问题 kafka文件结构和rocketMQ文件结构是什么样子?特点是什么? 一.目录结构 Kafk ...
- MQ初窥门径【面试必看的Kafka和RocketMQ存储区别】
MQ初窥门径 全称(message queue)消息队列,一个用于接收消息.存储消息并转发消息的中间件 应用场景 用于解决的场景,总之是能接收消息并转发消息 用于异步处理,比如A服务做了什么事情,异步 ...
- kafka 和 rocketMQ 的数据存储
kafka 版本:1.1.1 一个分区对应一个文件夹,数据以 segment 文件存储,segment 默认 1G. 分区文件夹: segment 文件: segment 的命名规则是怎样的? kaf ...
- 30分钟带你了解「消息中间件」Kafka、RocketMQ
消息中间件的应用场景 主流 MQ 框架及对比 说明 Kafka 优点 Kafka 缺点 RocketMQ Pulsar 发展趋势 各公司发展 Kafka Kafka 是什么? Kafka 术语 Kaf ...
- Kafka vs RocketMQ——单机系统可靠性-转自阿里中间件
引言 前几期的评测中,我们对比了Kafka和RocketMQ的吞吐量和稳定性,本期我们要引入一个新的评测标准--软件可靠性. 何为"可靠性"? 先看下面这种情况:有A,B两辆越野汽 ...
- Kafka vs RocketMQ——单机系统可靠性
引言 前几期的评测中,我们对比了Kafka和RocketMQ的吞吐量和稳定性,本期我们要引入一个新的评测标准——软件可靠性. 何为“可靠性”? 先看下面这种情况:有A,B两辆越野汽车,在城市的周边地区 ...
- RabbitMQ、Kafka、RocketMQ的优劣势
今天我们一起来探讨: 全量的消息队列究竟有哪些? Kafka.RocketMQ.RabbitMQ的优劣势比较 以及消息队列的选型 最全MQ消息队列有哪些 那么目前在业界有哪些比较知名的消息引擎呢?如下 ...
- Kafka vs RocketMQ——多Topic对性能稳定性的影响-转自阿里中间件
引言 上期我们对比了RocketMQ和Kafka在多Topic场景下,收发消息的对比测试,RocketMQ表现稳定,而Kafka的TPS在64个Topic时可以保持13万,到了128个Topic就跌至 ...
- Kafka vs RocketMQ—— Topic数量对单机性能的影响-转自阿里中间件
引言 上一期我们对比了三类消息产品(Kafka.RabbitMQ.RocketMQ)单纯发送小消息的性能,受到了程序猿们的广泛关注,其中大家对这种单纯的发送场景感到并不过瘾,因为没有任何一个网站的业务 ...
随机推荐
- js byte字节流和数字,字符串之间的转换,包含无符和有符之间的转换
var NumberUtil={ //byte数组转换为int整数 bytesToInt2:function(bytes, off) { var b3 = bytes[off] & 0xFF; ...
- 从零开始的SpringBoot项目 ( 三 ) 项目打包( jar包篇 )
一.准备工作 1.工具:Idea , maven 2.首先得保证 pom 有 maven 插件 二.开始打包 找到最右边的Maven Projects,点击进去,选择需要打包的项目,并点击 insta ...
- FJOI2020 游记
Day -1 啥都不会,药丸 看了看统考题,好难,爆零的节奏 文化课OI双爆炸 尽力吧 Day 0 花三个多小时才到考场 福州真的好热 签到 在小礼堂待了一会,顺便给手机充了电 四点试机,今年用了新系 ...
- 无法创建新虚拟机: 无法打开配置文件“F:\BigData\vm12\centos01\centos01.vmx”: 拒绝访问。
退出,右键vmware选择以管理员身份运行即可.
- codeblocks显示:不支持的16位应用程序 解决办法
我是win10 64位系统,写c++运行就会显示不兼容16位应用程序.以前编出来的exe还能用,今天编出的就炸了. 试了用vs编译.vs能用. 试了网上找的各种解决方案, 360修复, 注册表, 重构 ...
- Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能。
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能. 下面是一个效果图 ...
- Mysql宽字节注入 ---学习笔记
转自:https://blog.csdn.net/niexinming/article/details/49109683 先补充一点背景:大 家都知道PHP在开启magic_quotes_gpc或者使 ...
- webdriver实现简单的窗口切换
webdriver实现简单的窗口切换,也只能是简单的,因为目前处于学习阶段,复杂的情况现在还没碰到过.之前写过关于一个小demo的总结,就有提到过在新开窗口进行操作的情况,用以下一句就可以搞定了,la ...
- web前端常见安全问题
1,SQL注入 2,XSS 3,CSRF 4.文件上传漏洞 1,SQL注入:这个比较常见,可能大家也听说过,就是URL里面如果有对数据库进行操作的参数时,要做一下特殊的处理,否则被别有用心的人利用的话 ...
- input历史快捷-变黄解决
一: <form action="loginAction" method="post" autocomplete="off">给 ...