3.kafka-rocketmq对比-MQ

底层存储

kafka

Kafka部分名词解释如下:

  • Broker:消息中间件处理结点,一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群。

  • Topic:一类消息,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发。

  • Partition:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。

  • Segment:partition物理上由多个segment组成。

  • offset:每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息.(只在partition下唯一)

  • 同一个topic下有多个不同partition,每个partition为一个目录,命名规则为topic名称+partition的有序序号

    image-20240923111352406
  • 一个partition相当于一个巨型文件,被顺序分配到了多个segment文件中,文件名为当前第一条消息的offset

    image-20240923111647323

  • segment文件组成

    • xxxx.log日志数据文件,按照固定的格式存储生产者分送过来的消息数据
    • xxxx.index:索引文件,该文件主要记录了哪条消息(offset)写在对应的xxxx.log文件中的哪个位置(position),查询消息时首先查询该文件,然后再去日志数据文件中读取对应的消息数据。(稀疏索引存储的,也就是每隔几条消息数据建一条索引。所以在查找该条消息时只需要找到对应的消息编号最近的索引位置,然后再在数据文件中顺序查找。)

image-20240923111653552

当消费者给出一个offset,需要查找该消息:

例如读取offset=368776的message,需要通过下面2个步骤查找。

  • 第一步二分查找segment文件,因为文件名是按照offset来命名的
  • 找到正确的 .index 文件后,Kafka 使用该索引文件中的记录,定位消息在 .log 文件中的大致物理位置。由于 Kafka 的索引是稀疏的,可能并不会直接定位到目标 offset 对应的精确位置,因此 Kafka 会从索引定位的物理地址开始,顺序读取 .log 文件,直到找到目标 offset 的消息。

为什么需要index文件:

​ segment.log文件中存储的每一条消息长度是不一样的,因此如果直接查找需要遍历整个大的log文件,一条一条对比offset,而有了index文件后,变成了O(log n)(二分查找索引)+ O(k)(扫描少量消息)

写(生产)消息:

  • index文件较小,可以直接用mmap进行内存映射
  • segment文件较大,可以采用普通的write(FileChannel.write),由于是顺序写PageCache,可以达到很高的性能

读(消费)消息:

  • index文件仍然通过mmap读,缺页中断的可能性较小
  • segment可以使用sendfile进行零拷贝的发送给消费者,达到非常高的性能
1
2
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
// 单次系统调用,以及3 or 2 次数据拷贝

image-20240923115252328

存在问题:

在Kafka文件读写中,针对segment文件写入是采用顺序write的,但是如果topic数量以及下面parition数量一多,从操作系统的角度看就变成了随机写入,导致写入性能下降。

image-20240923113541061

rocketmq

  1. 所有的消息数据(不同topic、不同队列)都写入同一个文件(当然物理存储时还是会以固定大小进行文件切割),这个文件称之为CommitLog
  2. 为了加速消费消息,建立轻量级索引,为每个队列新增ConsumeQueue文件,存储消息在CommitLog中的文件偏移量,而不是像kafka一样存储完整的消息内容 topic/queue/file
  3. 现在读取变成了两次,先去queue读偏移,再去CommitLog读数据

image-20240923191338314

写(生产)消息:

  • 无论是ConsumeQueue还是CommitLog都使用mmap进行写

读(消费)消息:

  • 无论是ConsumeQueue还是CommitLog都使用mmap进行读
1
2
3
4
buf = mmap(file, len);
write(sockfd, buf, len);

// 两次系统调用 3次数据拷贝(减少一次内核到用户拷贝)

image-20240923115304549

小结

Kafka RocketMQ
文件结构 1.index索引文件 +log文件
2.多个队列多个segment消息文件
1.ConsumeQueue索引文件
2.多个队列一个CommitLog消息文件
IO写 1.index文件采用mmap 2.segment采用write 均采用mmap
IO读 1.index文件采用mmap 2.segment采用sendfile 均采用mmap
优点 1.每个topic的队列互不影响
2.数据量较大(>4kb)时写入性能更高
3.消费时可以利用sendfile+DMA零拷贝机制
1.commitLog完全顺序写
2.单机可以配置更多队列
缺点 1.单机队列配置较多时,有性能损失 1.随机读遇到缺页中断成本高
2.不能利用sendfile机制

架构上做减法

功能

rocketmq

  • tag过滤
  • 支持事务
  • 延时队列
  • 死信队列

sendfile更快,为什么rocketmq不使用?

sendfile 更适用于简单的文件传输,而 RocketMQ 需要对消息数据进行一定的处理和转换(例如死信队列)。mmap 允许直接在用户态中对文件数据进行操作,提供了更大的灵活性。

参考