3.RocketMQ消息生产与存储

消息生产过程

  1. producer发送消息之前,会从Nameserver获取该topic的路由表信息
  2. NS返回topic的路由表和broker列表
  3. producer通过指定的Queue选择策略,从Queue列表中选择一个,用于后续存储消息
  4. producer对消息的一些特殊处理,如消息本身大于4M压缩
  5. Producer向选择出的Queue对应的Broker发送RPC请求,将消息发送给该Queue
  • 路由表:MQClientInstance#topicRouteTable源码中底层是一个Map,key为topic名称,Value是一个QueueData实例列表.QueueData是一个broker中该topic所有Queue对应一个QueueData. 涉及到该topic的broker,一个broker对应一个QueueData. QueueData中包含brokerName.
  • Broker列表:MQClientInstance#brokerAddrTable底层也是一个Map.key为brokerName,value为BrokerData. 一套brokerName名称相同的Master-Slave小集群对应一个BrokerData. BrokerData包含brokerName和一个map.该map的key为brokerId,value为broker对应地址. brokderId为0表示为master非0表示slave

Queue路由选择算法

  • 轮询算法: 默认 保证每一个queue均匀获取消息,可能存在的问题某些broker上的queue投递延迟严重,从而导致producer缓存队列出现大量消息积压,影响消息的投递性能
  • 最小投递延迟算法: 该算法会统计每次消息投递的消息延迟,然后统计出结果将消息投递到延迟最小的Queue.如延迟相同采用轮询投递.该算法可以有效提高消息投递性能. 存在的问题在于可能出现Queue的分配不均匀,导致延迟小的queue存在大量的消息,从而对该queue的消费者压力增大,降低消息的消费能力,导致MQ中的消息堆积

消息存储

RocketMQ消息存储在本地文件系统中,默认在当前用户家目录下的store目录中

-rw-r--r--. 1 root root    0 Jul 13 08:28 abort
-rw-r--r--. 1 root root 4096 Jul 13 08:28 checkpoint
drwxr-xr-x. 2 root root   34 Apr 30 08:30 commitlog
drwxr-xr-x. 2 root root  280 Jul 13 08:28 config
drwxr-xr-x. 9 root root  199 Apr 29 18:30 consumequeue
drwxr-xr-x. 2 root root   31 Apr 29 18:14 index
-rw-r--r--. 1 root root    4 Jul 13 08:28 lock
  • abort: broker启动之后自动创建,正常关闭broker会自动删除.如果在没有启动Broker出现这个文件说明之前broker非正常关闭
  • checkpoint: 存储着commitlog consumequeue index文件的最后刷盘时间戳
  • commitlog: 消息真实存储的文件
  • config: broker运行期间的配置数据
  • consumequeue: 队列信息存放在该目录
  • Index: 存在消息索引文件IndexFile
  • lock: 运行期间使用的全局锁资源

commitlog

[root@node1 store]# cd commitlog/
[root@node1 commitlog]# ll
total 56
-rw-r--r--. 1 root root 1073741824 Apr 29 18:44 00000000000000000000

又称mappedFile.当前broker中所有信息都会落盘到这些mappedFile文件中.每一个mappedFile文件大小为1G(>=1G),文件名由20位十进制数构成,表示当前文件的第一条消息的起始位置偏移量

  • broker中所有的mappedFile文件中的commitlog offset是连续的
  • 一个broker只会包含一个commitlog目录,所有的mappedFile文件都是存放该目录中.broker存放时没有按照topic进行分类存放
  • mappedFile文件是顺序写

消息单元

3.RocketMQ消息生产与存储_第1张图片

mappedFile文件由多个消息单元组成

名称 描述
MsgLen 消息长度
PhysicalOffset 消息物理地址
Body 消息体
BodyLength 消息体长度
Topic 主题
TopicLength 主题长度
BornHost 生产者信息
BornTimestamp 消息发送时间戳
QueueId 消息所在队列Id
QueueOffset 消息在Queue中存储的偏移量

consumerqueue

[root@node1 consumequeue]# ll
total 0
drwxr-xr-x. 3 root root 15 Apr 29 18:27 %RETRY%pointGroup
drwxr-xr-x. 6 root root 42 Apr 29 18:20 RMQ_SYS_TRACE_TOPIC
drwxr-xr-x. 3 root root 15 Apr 29 18:14 RMQ_SYS_TRANS_HALF_TOPIC
drwxr-xr-x. 3 root root 15 Apr 29 18:25 RMQ_SYS_TRANS_OP_HALF_TOPIC
drwxr-xr-x. 5 root root 33 Apr 29 18:27 SCHEDULE_TOPIC_XXXX
drwxr-xr-x. 3 root root 15 Apr 29 18:30 TRANS_CHECK_MAX_TIME_TOPIC
drwxr-xr-x. 4 root root 24 Apr 29 18:29 txMsg
[root@node1 consumequeue]# cd txMsg/
[root@node1 txMsg]# ll
total 0
drwxr-xr-x. 2 root root 34 Apr 29 18:29 0
drwxr-xr-x. 2 root root 34 Apr 29 18:25 2

3.RocketMQ消息生产与存储_第2张图片

RocketMQ为每一个topic在~/store/consumequeue中创建一个目录,目录名为topic的名称.该目录下再会为该topic的Queue创建一个目录,目录名为Queue的Id.每个目录中存放了若干个consumequeue文件.

consumequeue文件是commitlog的索引文件可以定位到具体的消息

consume文件中每一条记录成为索引条目

3.RocketMQ消息生产与存储_第3张图片

每一个consumequeue文件中可以包含30W个索引条目,每一个索引条目大小固定,固定20个字节

一个consumequeue文件中所有消息的topic一定相同,但是每条消息的tag可能是不同的

名称 描述
CommitLog offset 消息在mappedFile文件中的偏移量CommitLog Offset
Msg length 消息长度
TagHashCode 消息Tag的hashcode值

文件读写

3.RocketMQ消息生产与存储_第4张图片

消息写入

  • broker根据queueId,获取该消息对应索引条目要在consumerqueue中写入偏移量 QueueOffset
  • 将queueId queueOffset等数据与消息一起封装成消息单元
  • 将消息单元写入commitLog
  • 同时形成消息的索引条目
  • 将索引条目分发到对应的consumequeue中

消息拉取

  • consumer获取到消息所在Queue的消费偏移量offset,计算出其要消费的消息的offset
    • 消费进度,consumer对某一个Queue的消费offset,消费到了第几条消息
    • 即将消费offset = 消费offset+1
  • consumer向broker发送拉取请求,其中会包含要拉取消息的queue 消息offset 和消息的tag
  • broker计算在该consumequeue中的queueOffset
    • queueOffset = 消息offset * 20 (因为一个索引条目固定20字节)
  • 从queueOffset开始往后查找第一个指定tag的索引条目
  • 解析该索引条目的前8个字节,即解析commitlog offset和 消息长度,即可定位到该消息在commitlog中commitlog offset
  • 从对应的commitlog offset中读取消息单元发送给consumer

高性能分析

  • RocketMQ对文件读写使用了mmap零拷贝技术,将对文件的操作转化为直接对内存地址的操作
  • 顺序写 consumequeue和commitlog文件都是采用了顺序存放
  • pagecache预读机制 是的consumequeue文件的读取几乎接近内存读取

pageCache机制,页缓存技术,OS对文件的缓存机制,加速文件读写. 一般来说,程序对文件的顺序读写速度几乎接近与内存读写速度,主要是因为OS使用了pagecache技术对读写访问操作进行了优化,将一部分的内存用于PageCache

  1. 写: OS会将数据写入PageCache中,随后以异步方式由page dirty push 内存线程将cache中的数据刷盘到物理磁盘
  2. 读: 首先从pagecache中读取,未命中,OS从物理磁盘上加载数据到pagecache中,并会顺序将起相邻的数据块的数据一并进行预读取操作

RocketMQ中可能会影响性能的是对commitlog文件的读取。因为对commitlog文件来说,读取消息时会产生大量的随机访问,而随机访问会严重影响性能。不过,如果选择合适的系统IO调度算法,比如设置调度算法为Deadline(采用SSD固态硬盘的话),随机读的性能也会有所提升

kafka与RocketMQ对比

  • RocketMQ的commitlog+consumequeue 类似与kafka中的parttion分区目录,mappedFile文件类似与kafka的segement
  • kafka中的topic分割成了一个或多个parttion, parttion是一个物理概念,对应为topic目录下的一个或多个目录. 每个parttion中包含了若干segment存放具体消息
  • kafka没有二级分类标签tag概念
  • kafka消息存放结构: topic -> partition -> segment

IndexFile

RocketMQ提供了根据Key进行消息查询功能.主要是通过store目录下的index子目录中的IndexFile进行索引快速查询. 索引的数据是消息中包含了Key的消息,被发送到broker时写入,如果消息中没有包含Key,则不会写入
3.RocketMQ消息生产与存储_第5张图片

每个broker会包含一组indexFile,每个IndexFile都以一个时间戳命名(创建时的时间戳).每个IndexFile由:IndexHeader slots indexes索引数据.每个Indexfile包含500W个slot,每个slot可能被挂载多个Index索引单元

IndexHeader固定40字节

3.RocketMQ消息生产与存储_第6张图片

  • beginTimestamp: 该Indexfile第一条消息存储时间
  • endTimestamp: 该Indexfile最后一条消息存储时间
  • beginPhyoffset: 该indexfile第一条消息在commitlog中的偏移量
  • endPyhoffset: 该indexfile最后消息在commitlog中的偏移量
  • hashSlotCount: 已经填充有Index的slot数量,并不是每个slot槽下都挂载有index索引单元,这里统计的是所有挂载了index索引单元的slot槽的数量
  • indexCount: 包含的索引单元个数(统计当前indexFile中所有slot下挂载的所有Index索引单元之和)

indexFile中slot与index索引单元关系

3.RocketMQ消息生产与存储_第7张图片

key的hash值%500W得到具体的slot槽位,然后将该slot的值改为index索引单元的IndexNo,根据该IndexNo可以计算出该Index单元在indexfile中的位置.

为了解决hash冲突,每个索引单元增加的preIndexNo,用于记录该索引单元下的前一个索引单元.slot中始终存放的是最新的索引单元的IndexNo,通过slot的indexNo找到最新的索引单元再通过索引单元的preIndexNo去找之前的所有Index索引单元

indexNo是一个在indexFile中的流水号,从 0 开始依次递增。即在一个indexFile中所有indexNo是以此递增的。indexNo在index索引单元中是没有体现的,其是通过indexes中依次数出来的

索引单元

3.RocketMQ消息生产与存储_第8张图片

  • keyHash: 消息中指定的key的hash值
  • phyOffset: 当前key的消息在commitlog偏移量
  • timeDiff: 当前Key对应的消息的存储时间与当前indexFile文件创建时间的时间差
  • preIndexNo: 当前slot下该索引单元的前一个索引单元的indexNo

indexFile的创建

根据业务Key查询时,查询条件除了Key之外,还需要指定一个查询时间戳,表示查询不大于该时间戳的最新消息.此时indexFile以时间戳命名的话就可以简化查询

index文件创建时机

  • 当第一条带Key消息发送的时候,没有IndexFile,此时会创建第一个Indexfile
  • 当一个Indexfile挂载的索引单元超过2000W时,会创建新的indexFile. 当带Key消息发送过来之后,系统找到最新的IndexFile,并从indexHeader中获取到IndexCount如果大于2000W则创建

通过Key的查询流程

  1. 计算消息key的slot槽位序号 (找slot序号)
    slot = hash(msg.key) % 500W

  2. 计算slot序号为n的slot在Indexfile的起始位置 (定位slot)

    slot(n) = 40 + (n - 1) * 4

    40: indexHeader的位置

    n-1: slot位置

    4 一个slot占4字节

  3. 计算indexNo为m的index在indexFile中的位置 (定位索引单元)

    index(m)位置 = 40 + 500w * 4 + (m - 1) * 20 (式子3)

    40: indexHeader的位置

    500w * 4: 500w个slot 每个slot 4字节

    (m-1)*20: m为索引单元的位置 20 一个索引单元固定20字节

3.RocketMQ消息生产与存储_第9张图片

你可能感兴趣的:(RocketMQ,java-rocketmq,rocketmq,java)