RocketMQ 的消息交互源码解析
创建Topic的时候为何要指定MessageQueue数量?
- 简单来说,就是你要指定你的这个Topic对应了多少个队列,也就是多少个MessageQueue。
- MessageQueue就是RocketMQ中非常关键的一个数据分片机制,他通过MessageQueue将一个Topic的数据拆分为了很多个数据分片,然后在每个Broker机器上都存储一些MessageQueue。
- Topic是一个逻辑上的概念,实际上在每个broker上以queue的形式保存,也就是说每个topic在broker上会划分成几个逻辑队列,每个逻辑队列保存一部分消息数据,但是保存的消息数据实际上不是真正的消息数据,而是指向commit log的消息索引。
生产者发送消息的时候写入哪个MessageQueue?
- 生产者会跟NameServer进行通信获取Topic的路由数据, 以生产者从NameServer中就会知道,一个Topic有几个MessageQueue,哪些MessageQueue在哪台Broker机器上
- 让一个Topic中的数据分散在多个MessageQueue中,进而分散在多个Broker机器上,实现RocketMQ集群分布式存储海量的消息数据了
如果某个Broker出现故障该怎么办?
- 如果某个Broker临时出现故障了,比如Master Broker挂了,此时正在等待的其他Slave Broker自动热切换为Master Broker,那么这个时候对这一组Broker就没有Master Broker可以写入了
- 如果你还是按照之前的策略来均匀把数据写入各个Broker上的MessageQueue,那么会导致你在一段时间内,每次访问到这个挂掉的Master Broker都会访问失败,这个似乎不是我们想要的样子。
- 对于这个问题,通常来说建议大家在Producer中开启一个开关,就是
sendLatencyFaultEnable
- 一旦打开了这个开关,那么他会有一个自动容错机制,比如如果某次访问一个Broker发现网络延迟有500ms,然后还无法访问,那么就会自动回避访问这个Broker一段时间,比如接下来3000ms内,就不会访问这个Broker了
RocketMQ 是如何持久化消息的
1、为什么Broker数据存储是最重要的一个环节
- roker数据存储实际上才是一个MQ最核心的环节,他决定了生产者消息写入的吞吐量,决定了消息不能丢失,决定了消费者获取消息的吞吐量,这些都是由他决定的
2、CommitLog消息顺序写入机制
- 当生产者的消息发送到一个Broker上的时候,他接收到了一条消息,接着他会对这个消息做什么事情?首先第一步,他会把这个消息直接写入磁盘上的一个日志文件,叫做CommitLog,直接顺序写入这个文件
- 这个CommitLog是很多磁盘文件,每个文件限定最多1GB,Broker收到消息之后就直接追加写入这个文件的末尾,就跟上面的图里一样。如果一个CommitLog写满了1GB,就会创建一个新的CommitLog文件。
RocketMq是如何写入数据的
设定一个topic -> 根据设定的MessageQueue个数 -> 分不在不同的master Broker里边 -> 每个MessageQueue是由多个 CommitLog组成 -> Commit是采用顺序读写。加上OS PageCache来保证写入性能 -> 首先。OS PageCache是基于内存的缓冲池。采用异步刷盘或者同步刷盘顺序写入磁盘 (异步刷盘宕机是会有可能导致数据丢失的
- DLedger 相当于替换了 CommitLog
- DLedger CommitLog 来构建出机器上的MessageQueue
- Broker机器刚刚启动的时候,就是靠这个DLedger基于Raft协议实现的leader选举机制,互相投票选举出来一个Leader,其他人就是Follower,然后只有Leader可以接收数据写入,Follower只能接收Leader同步过来的数据
- DLedger收到一条数据之后,会标记为uncommitted状态,然后他会通过自己的DLedgerServer组件把这个uncommitted数据发送给Follower Broker的DLedgerServer。
- 接着Follower Broker的DLedgerServer收到uncommitted消息之后,必须返回一个ack给Leader Broker的DLedgerServer,然后如果Leader Broker收到超过半数的Follower Broker返回ack之后,就会将消息标记为committed状态。
- 也就是说。当leaderBroker收到消息之后会同步给 FollowerBroker 节点。当节点响应ack之后主节点才会返回给生产者ack
源码索引
消息发送源码解析
- 消息发送
producer.send(msg);
- ->
defaultMQProducerImpl.sendDefaultImpl
- ->
this.tryToFindTopicPublishInfo 从 NameService 获取 Topic路由信息(本地有缓存就从缓存中获取)
- ->
this.selectOneMessageQueue 选择一个消息队列 queue
- ->
this.sendKernelImpl 调用发送核心方法
- ->
mQClientFactory.getMQClientAPIImpl().sendMessage 进行发送
- ->
- ->
MQClientAPIImpl#sendMessageSync
- ->
remotingClient.invokeSync 调用netty方法发送 RequestCode.SEND_MESSAGE 消息
- broker接受到消息的处理
- ->
NettyServerHandler#channelRead0
- ->
NettyRemotingAbstract#processMessageReceived
- ->
NettyRemotingAbstract#processRequestCommand 处理客户端的请求消息
- ->
processor.asyncProcessRequest 客户端发送的是异步消息,不需要同步返回成功
- ->
SendMessageProcessor#asyncProcessRequest 进入消息处理
- ->
AbstractSendMessageProcessor#parseRequestHeader 解析请求
- ->
SendMessageProcessor#asyncSendMessage 异步保存发送的消息
- ->
this.brokerController.getMessageStore().asyncPutMessage(msgInner) MessageStore存储消息
- ->
MessageStore#asyncPutMessage 异步保存发送的消息
- ->
MessageStore#putMessage 保存发送的消息
- ->
DefaultMessageStore#asyncPutMessages DefaultMessageStore保存消息默认实现
- ->
this.commitLog.asyncPutMessages(messageExtBatch) 保存发送的消息
- ->
CommitLog#asyncPutMessages 保存发送的消息
- ->
mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback, putMessageContext) mappedFile
- ->
CommitLog#submitFlushRequest 提交刷盘 (异步 / 同步)
- ->
CommitLog#submitReplicaRequest 将消息同步到从节点。可配置备份多少个
- ->
消息保存完毕
rocketMq 同步消息的原理
-
netty调用方会触发的动作
-
RemotingClient#invokeSync
-
RemotingClient#invokeSyncImpl 发送同步方法的实现
-
this.responseTable.put(opaque, responseFuture) 这个很关键,将opaque 存到响应的 responseTable里边
-
然后下方 responseFuture.waitResponse(timeoutMillis) 会阻塞当前请求
-
netty被调用方会触发的动作
-
NettyRemotingAbstract 然后我们看此处的方法。
-
RemotingResponseCallback callback = new RemotingResponseCallback() 构建了一个远程
-
如果请求是同步请求的话,一定会触发 callback.callback(response);
final RemotingResponseCallback callback = new RemotingResponseCallback() {
@Override
public void callback(RemotingCommand response) {
doAfterRpcHooks(remoteAddr, cmd, response);
if (!cmd.isOnewayRPC()) {
if (response != null) {
response.setOpaque(opaque);
response.markResponseType();
try {
ctx.writeAndFlush(response);
} catch (Throwable e) {
log.error("process request over, but response failed", e);
log.error(cmd.toString());
log.error(response.toString());
}
} else {
}
}
}
};
-
请观察一下这个的实现。
response.setOpaque(opaque);
想当于将请求的 opaque 塞入到了response里边。 -
然后将
ctx.writeAndFlush(response);
到调用方 -
然后回到调用方
-
NettyRemotingAbstract#processMessageReceived 检查到是 RESPONSE_COMMAND 响应的请求
-
responseFuture.putResponse 会设置 responseCommand 并且 countDownLatch.countDown 释放之前阻塞的请求
RocketMq是如何拉取消息的
发送请求:
consumer.start();
defaultMQPushConsumerImpl.start()
->mQClientFactory.start()
->pullMessageService.start()
->this.pullRequestQueue.take()
关键点是 pullRequestQueue 这个队列,首次会从rebalanceService.start()
获取到消息->impl.pullMessage(pullRequest)
拉取消息 ->this.pullAPIWrapper.pullKernelImpl
发送拉取消息请求 ->this.mQClientFactory.getMQClientAPIImpl().pullMessage
发送RequestCode.PULL_MESSAGE
消息 ->
服务器处理请求
PullMessageProcessor
专门处理RequestCode.PULL_MESSAGE
消息 ->PullMessageProcessor#processRequest
处理请求 ->ResponseCode.SUCCESS
找到消息的话直接请求客户端 ->ResponseCode.PULL_NOT_FOUND
说明消息消费到最后一条,等待新消息的进入 ->this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest)
将“长轮询pullRequest”对象 交给 长轮询服务 ->pullRequestTable.putIfAbsent(key, mpr)
将请求放入pullRequestTable
->- broker在启动的时候 启动了
PullRequestHoldService
这个服务 -> PullRequestHoldService
定时扫描pullRequestTable
关心的数据中是否有新消息->PullRequestHoldService#checkHoldRequest
核心逻辑,循环pullRequestTable
内容并检查指定的offset
->PullRequestHoldService#notifyMessageArriving
通知消息到达->notifyMessageArriving
取出pullRequestTable
指定key的List<PullRequest>
循环判断 offset是否 大于其中一个 PullRequest的数据, 如果是 ->this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup()
启动一个线程,执行 ->PullMessageProcessor.this.processRequest(channel, request, false)
用 PullRequest 查到最新的消息 ->channel.writeAndFlush
将数据发送回客户端 ->
接受服务端返回的长轮训消息或者是及时响应消息
PullCallback
处理回调消息 ->DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest
执行 调用方提供的 callback方法DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest)
将请求放回 pullRequestQueue 队列中- 重复循环拉取操作
RocketMq是如何发送事务消息的
-
TransactionMQProducer
使用特殊的 producer 才能发送事务消息 -
producer.setTransactionListener(transactionListener);
设置事务监听 -
请自己实现事务监听
TransactionListener
executeLocalTransaction
立即执行事务,检查是否提交事务;checkLocalTransaction
检查本地事务状态 -
设置好准备参数之后,进行发送数据,使用方法
sendMessageInTransaction
发送事务消息 -
sendMessageInTransaction
:MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
设置事务的标识this.send(msg)
半消息发送transactionListener.executeLocalTransaction(msg, arg)
发送成功之后执行本地事务 获得本地事务的执行状态this.endTransaction(msg, sendResult, localTransactionState, localException);
给 broker反馈本地事务的状态,如果给返回的不是提交状态 Rocketmq 会有一个TransactionalMessageCheckService
服务轮训调用checkLocalTransaction
检查本地事务状态- 至此就完结了事务消息的提交
-
this.send(msg)
半消息发送 broker解析 (向broker发送了 带有事务标识的消息) -
broker 接到消息
SendMessageProcessor#asyncSendMessage
-
判断如果为事务消息,则调用
this.brokerController.getTransactionalMessageService().prepareMessage(msgInner)
存储数据 -
TransactionalMessageServiceImpl#prepareMessage
-
transactionalMessageBridge.putHalfMessage(messageInner)
-
parseHalfMessageInner(messageInner)
-
msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
修改topic并且落盘。存储半消息 -
至此,broker半消息存储成功, 后续会由
TransactionalMessageCheckService
来检查状态。 或者当消息发送完毕executeLocalTransaction
返回提交之后。通知broker提交消息