架构
https://github.com/apache/rocketmq/blob/master/docs/cn/architecture.md rocketmq/docs/cn at master · apache/rocketmq · GitHub
RocketMQ & Kafka 对比
1. 文件结构
首先都是顺序写入,不过 RocketMQ 是把消息都存一个文件中,而 Kafka 是一个分区一个文件。 每个分区一个文件在迁移或者数据复制层面上来说更加得灵活。但是分区多了的话,写入需要频繁的在多个文件之间来回切换,对于每个文件来说是顺序写入的,但是从全局看其实算随机写入,并且读取的时候也是一样,算随机读。而就一个文件的 RocketMQ 就没这个问题。 消息存储整体架构:
[图片上传失败...(image-8002c1-1677753939472)]
kafka | rocket | |
---|---|---|
概念区分 | Topic : 主题,可以理解为一个队列 ,只是一个逻辑概念,真正存在的是分区。 | |
一个topic多个Partition(分区) | ||
Partation: 队列Topic的分区,一个Topic可以分为多个分区,用于高并发场景的负载功能; | 一个topic多个Queue(分片) | |
Queue是Topic在一个Broker上的分片等分为指定份数后的其中一份,是负载均衡过程中资源分配的基本单元。 | ||
文件存储 | 以 Partition 为单位,每个 Partition 包含一组消息文件(Segment file)和一组索引文件(Index) | 只有一个CommitLog文件 |
以 Broker 为单位,存储消息文件和索引文件,每个 Broker 只有一组消息文件,它把在这个 Broker 上的所有主题的消息都存在这一组消息文件中。 | ||
索引文件 | ||
ConsumerQueue消息消费队列索引文件:是按照主题和队列分别建立的,每个队列对应一组索引文件 | ||
存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName} | ||
IndexFile索引文件:一种可以通过key或时间区间来查询消息的方法 | ||
存储位置是:HOME\store\index{fileName} | ||
“按照Message Key查询消息”,主要是基于RocketMQ的IndexFile索引文件来实现的。RocketMQ的索引文件逻辑结构,类似JDK中HashMap的实现。 | ||
索引文件 | 稀疏索引,为了节省存储空间,它不会为每一条消息都创建索引,而是每隔几条消息创建一条索引。 | RocketMQ 中的索引是定长稠密索引,它为每一条消息都建立索引,每个索引的长度(注意不是消息长度)是固定的 20 个字节 |
文件存储的优缺点 | Kafka 以分区为单位,粒度更细,优点是更加灵活,很容易进行数据迁移和扩容。 | |
RocketMQ 以 Broker 为单位,较粗的粒度牺牲了灵活性,带来的好处是,在写入的时候,同时写入的文件更少,有更好的批量(不同主题和分区的数据可以组成一批一起写入),更多的顺序写入,尤其是在 Broker 上有很多主题和分区的情况下,有更好的写入性能。 |
2. 零拷贝优化
从发送消息来说 RocketMQ 用到了 mmap + write 的方式,并且通过预热来减少大文件 mmap 因为缺页中断产生的性能问题。而 Kafka 则用了 sendfile,相对而言我觉得 kafka 发送的效率更高,因为少了一次页缓存到 SocketBuffer 中的拷贝。并且 swap 问题也可以通过系统参数来设置。
中间件 | 实现方式 | 具体 |
---|---|---|
RocketMQ | mmap+write | 发送和消费消息时(针对CommitLog、ConsumeQueue)使用 mmap + write |
Kafka | mmap | Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入 |
sendfile | Customer从broker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络传输 |
kafka 为什么快?为什么rocketmq不用sendfile技术?
性能:Kafka吞吐量更高,单机百万/秒;RocketMQ单机10万/秒。
rocketmq使用了mmap,没有使用sendfile,但为什么rocketmq不使用sendfile, 因为rocketmq可以处理消息的顺序,消息的过滤,而Kafka不能有这些功能。 kafka中,sendfile压根就没有到达数据应用层,数据只在内核中中,不会进入用户进程的,也就是不会进入java程序,无法对数据进行操作(修改、排序)。
其中:MMap三次拷贝 sendfile两次拷贝,多出的一次是CPU拷贝 从内核拷贝到用户进程。
内存映射
总结:
- java nio实现:FileChannel.transferTo
执行顺序:sendfile-> mmap->传统的 I/O 方式
- sendfile只有两次DMA copy,MMAP有两次DMA copy,外加一次CPU copy
MMAP(内存映射)
MMAP(内存映射):将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。简单来说,就是实现磁盘文件到虚拟内存的直接传输,减少了内核态到用户态的数据拷贝。 另外这里给大家说明白的一点是,这个 mmap 技术在进行文件映射的时候,一般有大小限制,在 1.5GB~2GB 之间。所以 RocketMQ 才让CommitLog单个文件在1GB,ConsumeQueue文件在5.72MB,不会太大。 [图片上传失败...(image-52fb1d-1677753939472)]
MMAP有两次DMA copy,外加一次CPU copy
DMA 辅助的 sendfile
sendfile只有两次DMA copy, cup拷贝不再拷贝数据,而是拷贝缓冲区描述符。实现真正意义上的零拷贝,在 Java 中FileChannal.transferTo()底层用的就是sendfile。 DMA 辅助的 sendfile缺点是:只适用于将数据从文件拷贝到套接字上。
页缓存
PageCache(页缓存):文件系统缓存,加速文件的读写速度。我们都知道磁盘 IO 和内存 IO 的速度可是相差了好几个数量级。加入页缓存的目的就是为了使程序对文件进行顺序读写的速度接近于内存的读写速度。所以简单来说,对于数据的写入,OS 会先写入至 Cache 内,随后通过异步的方式由 pdflush 内核线程将 Cache 内的数据刷盘至物理磁盘上。对于数据的读取,如果一次读取文件时出现未命中 PageCache 的情况,OS 从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。
首先了解一下页缓存,页缓存是操作系统用来作为磁盘的一种缓存,减少磁盘的I/O操作。 在写入磁盘的时候其实是写入页缓存中,使得对磁盘的写入变成对内存的写入。写入的页变成脏页,然后操作系统会在合适的时候将脏页写入磁盘中。 在读取的时候如果页缓存命中则直接返回,如果页缓存 miss 则产生缺页中断,从磁盘加载数据至页缓存中,然后返回数据。 页缓存存在数据丢失的风险,例如机器突然断电,那些还未刷盘的脏页就丢失了。不过可以调用 fsync 强制刷盘,但是这样对于性能的损耗较大。因此一般建议通过多副本机制来保证消息的可靠,而不是同步刷盘。
引用
https://baijiahao.baidu.com/s?id=1673444160125100503&wfr=spider&for=pc