RocketMq 设计原理

一.消息存储

commitLog:

commitLog文件是消息持久化的表现形式。
producer提交完消息后,broker会将消息首先持久化到commitLog中
。commitLog的写入是顺序写入的。每个commitLog文件大小默认1G
。每个消息占一个偏移量。当文件写满了,就写入下一个文件

ConsumerQueue:

消费队列,RocketMq是采取数据与索引分离的架构。想一想,如果consumer直接通过commitLog进行消费,那每次消费时都需要按照topic遍历commitLog文件是十分低效的。此时consumerQueue记录了每个消息在CommitLog文件中的一个偏移量。那么消费时可以通过队列中的索引进行直接定位。

结构上分为三个部分
1.commitLog消息的偏移量
2.消息的长度
3.tag的hashCode

其中
“1”是为了快速查找起始消息位置
“2”是为了读取整条消息
“3”是为了通过tag进行消息过滤。不过由于存储的是tag的hashcode而非本身。所以消费者最后还要自身进行tag字符的比对。

分类上。队列按topic进行分类,一个topic下可以有多个队列,每个队列都有一个Id。创建topic时可以设置多个队列。

使用时建议按照服务的节点进行分配队列,保证每个consumer都可以进行消费。读队列写队列尽量保持一致,因为读队列和写队列实际上不是物理隔离的。读队列是面向消费者的说法,写队列是面向producer的说法。
比如:
读队列 :4
写队列:3
消费者有4个
此时4个消费者被负载均衡到4个队列上,而生产者只会投递到指定的3个队列上,此时一个消费者就处于无消息消费的情况。

另一种情况:
如果生产环境下有8台节点,此时topic下的队列数量只设置了4个队列
则会有4个节点的consumer处于空闲状态。

IndexFile:

可以参考JDK的hashMap的结构。每个元素的结构:
1.Key Hash
2.Commit Log Offset
3.TimeStamp
4.Next Index Offset

“1” 是用来通过Message Key查询消息
“2” 消息的索引
“3” 记录的是消息storeTimestamp之间的差
“4” 发生hash冲突时,通过此字段指向链表的下一个节点

通过Message Key查询消息时就是通过IndexFile进行查询的

顺序读写:

rocket中的队列文件,都是顺序读写的。在OS的pageCache和局部性访问原理(提升命中缓存概率)的加持下,顺序读写文件接近内存的读写速度。但是CommitLog文件的读基本都是随机的,pageCache命中的概率很小。此时设置调度算法为“Deadline”(采用SSD),随机读的性能也会有所提升。

文件映射:

使用了NIO中的MappedByteBuffer,通过内存映射,可以实现直接通过访问内存操作映射的文件读写,减少了传统IO(1.DMA将数据拷贝到内核缓冲区2.cpu把内核缓冲区拷贝到用户空间的缓冲区)的来回复制。

消息刷盘:

分为:
1.同步刷盘
2.异步刷盘

文件写操作时,由于OS会利用PageCache进行写入优化。导致可能文件数据写入了PageCache,而未真实的写入磁盘中。在未写入磁盘时就返回刷盘成功,这种方式称为异步刷盘。由OS另外开启一个线程进行消息落盘。

同步刷盘就是指必须等待消息成功落盘才会返回刷盘成功。

举个例子"FileChannel.force(true);" 熟悉NIO的同学一定会熟悉这个方法。
此方法就是在fileChannel.write()之后,强制刷盘。

二. RocketMq通信线程模型:

RocketMq采用的是Netty作为底层通信框架,其线程模型是遵循Reactor多线程模型(中间件不是面向移动端的,因此Reactor多线程模型相比主从Reactor线程模型更为方便)。RocketMq单独把通信层进行了一层抽象,实现了客户端程序"NettyRemotingClient"以及服务端程序"NettyRemotingServer"。
RocketMq所有的组件的通信都是基于这两个实现,通过注入不同的"NettyRequestProcessor"实现相应组件的功能。这里也能看出策略模式带来的解耦以及代码复用功效。

通信层由四种线程组进行工作:

1:eventLoopGroupBoos ------- 1条线程

此为Reactor主线程负责接收连接并生成通信套接字,同时绑定感兴趣的事件并注册到Selector上。------------NioEventLoopGroup: linux下使用EpollEventLoopGroup

2:eventLoopGroupSelector-------- 3条线程

selector线程其实就是我们常见的worker线程,此线程组负责处理网络IO读写(只负责读写,将就绪事件的字节流读到内存中)rocketMq默认3条线程。-----NioEventLoopGroup:linux下使用EpollEventLoopGroup

3:defaultEventExecutorGroup --------8条线程

主要负责业务的线程组,比如channelPipline中的处理器如SSLHandler、心跳检测Handler、ConnectionManngerHandler、编解码Handler,通过DefaultEventExecutorGroup进行处理。
其中NettyServerHandler(客户端为NettyClientHandler)是主要业务逻辑的入口。

4:ExecutorService ---------- 8条线程

此线程用来处理不同的请求,防止defaultEventExecutorGroup阻塞而出现的异步线程池。defaultEventExecutorGroup负责分发业务逻辑请求,并使用ExecutorService中的线程进行具体的逻辑处理。个人认为defaultEventExecutorGroup叫Seletor反而比较合适。

ProcessorTable:
Key:RequestCode (请求类型)
Value:Pari (对应请求的处理器,以及执行环境)。

通过请求头的code码到ProcessorTable中获取对应的处理器(RocketMq接收到对应消息后的处理逻辑的分支选取,都在对应模块包中的各种NettyRequestProcessor中,debug看源码是可以从这入手),对应的处理器都绑定了一个业务线程池。

综上,rocketMq的通信模型一共有四类线程池进行支撑。

三.负载均衡:

1.Producer负载均衡:

首先看三个类:
TopicPublishInfo(topic在producer本地缓存的路由信息):
RocketMq 设计原理_第1张图片
MessgaeQueue(队列描述信息,不包括队列内容):
RocketMq 设计原理_第2张图片
TopicRouteData(broker下的队列配置):
RocketMq 设计原理_第3张图片
首先看下发送队列的逻辑
RocketMq 设计原理_第4张图片
注意一下tryToFindTopicPublishInfo(msg.getTopic())方法
这是去寻找topic在本地缓存的topic路由信息(TopicPublishInfo)

下面的selectOneMessageQueue方法则进行负载均衡,一路跟踪其主要逻辑在 MQFaultStrategy中
RocketMq 设计原理_第5张图片
在这里插入图片描述
主要实现了两个逻辑
未开启延时规避的情况下,尽量不使用上次发送消息使用的队列
开启了延时规避则不使用在规避列表内的broker。(个人理解是为了让broker喘口气,策略上到达了规避结束时间点后,会再次使用)

2.Consumer负载均衡

简介:

consumer的负载均衡有两种,
1.broadcast(广播模式):一个消息,同一个消费者组下,所有消费者都可以消费一次
2.cluster(集群模式):同一个消费者组下,一个队列只会同时被一个消费者消费

具体逻辑

Consumer会启动一个RebalanceService线程,每20秒执行一次负载均衡。此线程的run方法会调用RebalanceImpl下的rebalanceByTopic方法去执行负载均衡。这里只讨论集群模式下的负载均衡(broadcast模式个人不推荐使用,除非有特殊需求,需要广播,比如聊天室)

1.rebalanceImpl下会缓存一个topicSubscribeInfoTable,里面存储了该topic下的所有的读队列

2.消费者会向broker获取某个topic下,某个consumerGroup下的所有消费者id

3.将上述“1”的队列进行排序,“2“的consumer进行排序。按照分页策略均衡分配。队列为数据,consumer为页数,每一页均衡分配数据即 每个consumer均衡分配这些队列。

这里只研究了分页策略也是默认的策略。

三.事务消息:

一阶段:

一阶段主要是消息的写入。broker在消息写入时会判断是否为事务消息,如果为事务消息则把消息的topic替换为 ”half topic“。并发送到half topic的队列上。这样消费者就无法感知此消息的存在了。

二阶段:

二阶段主要是对commit 或 rollback的操作。事实上rollback并不是删除原消息,只是把消息的状态改为回滚对应的状态就行了。而提交操作需要把消息真正发送到原topic供消费者进行消费。
为了实现此功能,出现了OP消息。此消息是为了确认消息的事务状态而诞生的。二阶段提交时会发送一个op消息。op消息会发送到 ”Op Topic”上,如果是提交操作,op消息会存储对应消息在“half Topic”的索引,为了方便后续操作的回查。

提交事务时,会根据OP消息中记录的half消息索引,查到对应消息,并把topic消息还原,走一遍写入的流程,发送到正确的队列上。

二阶段提交失败:

broker会根据处于悬停状态下的half消息,到同一个producerGroup下的某个producer进行事务回查,producer通过检查本地事务状态决定是提交还是回滚。多次回查失败(默认15次),则默认进行回滚。

你可能感兴趣的:(rocketMq)