概述
在 Broker 的通用请求处理器将一个消息进行分发后,就来到了 Broker 的专门处理消息存储的业务处理器部分。本篇文章,我们将要探讨关于 RocketMQ 高效的原因之一:文件结构的良好设计与对 Page Cache 的极致"压榨"。
文件系统的结构设计
在 RocketMQ 的 Broker 中,有一类叫做 CommitLog 的文件,所有在该 Broker 上的 Topic 上的消息都会顺序的写入到这个文件中。
该消息的元信息存储着消息所在的 Topic 与 Queue。当消费者要进行消费时,会通过 ConsumerQueue 文件来找到自己想要消费的队列。
该队列不存储具体的消息,而是存储消息的基本信息与偏移量。消费者通过偏移量去 CommitLog 中找到自己需要消费的信息然后取出,就可以进行消费。
并且,Broker 还可以对 CommitLog 来建立 Hash 索引文件 IndexFile,这样就可以通过 消息的 key 来找到消息。
官网上的这张图很好的表示了三类文件之间的关系。当然这章我们还是先只来看 CommitLog,其他两个留给下一章。
消息管理的结构层次
在学习 Broker 对于消息的处理时,我们可以跟着下面这张图走,这样可以对 Broker 的文件系统有一个清晰的了解
上图的主要思路来源于 该图 ,由于找不到原作者,故进行了重制与拓展
业务处理层
在 上一篇文章 中,我们看到在 BrokerController
中,SendMessageProcessor
注册了以下请求码
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor); |
|
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor); |
|
this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor); |
|
this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor); |
同时,在处理业务请求时,通用请求处理器是,通过调用 AsyncNettyRequestProcessor
的 asyncProcessRequest
方法来处理连接和命令的,虽然也有同步的调用,但实际上大部分的业务处理 handler 都实现了异步的请求处理方法。
@Override |
|
public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception {
|
|
asyncProcessRequest(ctx, request).thenAcceptAsync(responseCallback::callback, this.brokerController.getSendMessageExecutor()); |
|
} |
在 SendMessageProcessor
中,它首先是构造了一个异步处理方法(asyncProcessRequest
),然后由自己的线程池去执行(thenAcceptAsync
)。
根据上一章最后的那张线程模型的图,我们能知道构造这个异步方法和该方法的调用,都是通过自己的线程池来执行的,所以和同步执行的区别不是很大。
进入到异步方法的构造
public CompletableFuture<RemotingCommand> asyncProcessRequest(ChannelHandlerContext ctx, |
|
RemotingCommand request) throws RemotingCommandException {
|
|
final SendMessageContext mqtraceContext; |
|
switch (request.getCode()) {
|
|
case RequestCode.CONSUMER_SEND_MSG_BACK: |
|
return this.asyncConsumerSendMsgBack(ctx, request); |
|
default: |
|
// 重建请求头 |
|
SendMessageRequestHeader requestHeader = parseRequestHeader(request); |
|
if (requestHeader == null) {
|
|
return CompletableFuture.completedFuture(null); |
|
} |
|
// 构建 MQ trace 上下文 |
|
mqtraceContext = buildMsgContext(ctx, requestHeader); |
|
this.executeSendMessageHookBefore(ctx, request, mqtraceContext); |
|
if (requestHeader.isBatch()) {
|
|
return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader); |
|
} else {
|
|
return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader); |
|
} |
|
} |
|
} |
在这里对不同的请求进行分别处理,我们现在在意的是 SEND_MESSAGE
,所以先进入到 asyncSendMessage
// 重组 |
|
final RemotingCommand response = preSend(ctx, request, requestHeader); |
|
// 构建响应头 |
|
final SendMessageResponseHeader responseHeader = (SendMessageResponseHeader)response.readCustomHeader(); |
|
if (response.getCode() != -1) {
|
|
return CompletableFuture.completedFuture(response); |
|
} |
|
final byte[] body = request.getBody(); |
|
// 获取队列与 Topic 配置 |
|
int queueIdInt = requestHeader.getQueueId(); |
|
TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); |
|
if (queueIdInt < 0) {
|
|
queueIdInt = randomQueueId(topicConfig.getWriteQueueNums()); |
|
} |
|
// 以内部消息的格式存储 |
|
MessageExtBrokerInner msgInner = new MessageExtBrokerInner(); |
|
msgInner.setTopic(requestHeader.getTopic()); |
|
msgInner.setQueueId(queueIdInt); |
|
// 对于重试消息和延迟消息的处理 |
|
if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) {
|
|
return CompletableFuture.completedFuture(response); |
|
} |
|
msgInner.setBody(body); |
|
/* pass:这里设置了一堆其他属性 */ |
|
CompletableFuture<PutMessageResult> putMessageResult = null; |
|
String transFlag = origProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); |
|
if (transFlag != null && Boolean.parseBoolean(transFlag)) {
|
|
if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
|
|
response.setCode(ResponseCode.NO_PERMISSION); |
|
response.setRemark( |
|
"the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1() |
|
+ "] sending transaction message is forbidden"); |
|
return CompletableFuture.completedFuture(response); |
|
} |
|
putMessageResult = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner); |
|
} else {
|
|
putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner); |
|
} |
|
// 对于写入结果,构造响应 |
|
return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt); |
在这里,它做了这几件事:
- 将消息通过内部消息(
MessageExtBrokerInner
)的格式保存 - 将重试消息、延时消息、事务消息由其他方法处理
- 除了完请求后,构造响应结果并返回
然后,我们进入了 Rocket 的存储组件层
存储组件层
这一层主要是负责操作下一层的逻辑文件对象来响应上一层的下发的请求。
工作的类是 MessageStore
,主要的实现是 DefaultMessageStore
接着对"放入消息"这个命令来进行响应
// 检查持久化层的状态 |
|
PutMessageStatus checkStoreStatus = this.checkStoreStatus(); |
|
if (checkStoreStatus != PutMessageStatus.PUT_OK) {
|
|
return CompletableFuture.completedFuture(new PutMessageResult(checkStoreStatus, null)); |