RocketMQ源码详解 | Broker篇 · 其二:文件系统

概述

在 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);

同时,在处理业务请求时,通用请求处理器是,通过调用 AsyncNettyRequestProcessorasyncProcessRequest 方法来处理连接和命令的,虽然也有同步的调用,但实际上大部分的业务处理 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);

在这里,它做了这几件事:

  1. 将消息通过内部消息(MessageExtBrokerInner)的格式保存
  2. 将重试消息、延时消息、事务消息由其他方法处理
  3. 除了完请求后,构造响应结果并返回

然后,我们进入了 Rocket 的存储组件层


存储组件层

这一层主要是负责操作下一层的逻辑文件对象来响应上一层的下发的请求。

工作的类是 MessageStore,主要的实现是 DefaultMessageStore


接着对"放入消息"这个命令来进行响应

// 检查持久化层的状态
PutMessageStatus checkStoreStatus = this.checkStoreStatus();
if (checkStoreStatus != PutMessageStatus.PUT_OK) {
   
  return CompletableFuture.completedFuture(new PutMessageResult(checkStoreStatus, null));
}

// 检查消息正确
PutMessageStatus msgCheckStatus = this.checkMessage(msg);
if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) {
   
  return CompletableFuture.completedFuture(new PutMessageResult(msgCheckStatus, null))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值