RocketMQ源码解析之消息存储

前言

在RocketMQ读写消息时,用的是Java NIO类库中的类,关于这些类的介绍和使用,可以先看我的前一篇文章Java传统IO和NIO有什么区别。接下来就从源码来分析当broker接收到消息时是如何处理的。

一、消息处理入口

消息处理入口:BrokerController.registerProcessor(),Broker为每一种请求的Code都注册了一个处理类,其中用于处理接收消息的类为SendMessageProcessor

        /**
         * SendMessageProcessor
         */
        SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
        sendProcessor.registerSendMessageHook(sendMessageHookList);
        sendProcessor.registerConsumeMessageHook(consumeMessageHookList);

        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);
        this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
        this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
        this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor);
        this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor);

当消息到达时,会调用SendMessageProcessor.processRequest()

    public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                          RemotingCommand request) throws RemotingCommandException {
   
        SendMessageContext mqtraceContext;
        switch (request.getCode()) {
   
            case RequestCode.CONSUMER_SEND_MSG_BACK:
                return this.consumerSendMsgBack(ctx, request);
            default:// 默认走这里
                SendMessageRequestHeader requestHeader = parseRequestHeader(request);
                if (requestHeader == null) {
   
                    return null;
                }

                mqtraceContext = buildMsgContext(ctx, requestHeader);
                this.executeSendMessageHookBefore(ctx, request, mqtraceContext);

                RemotingCommand response;
                if (requestHeader.isBatch()) {
   
                	// 处理批消息
                    response = this.sendBatchMessage(ctx, request, mqtraceContext, requestHeader);
                } else {
   
                	// 处理普通消息
                    response = this.sendMessage(ctx, request, mqtraceContext, requestHeader);
                }

                this.executeSendMessageHookAfter(response, mqtraceContext);
                return response;
        }
    }

接下来就来看看处理普通消息的过程,即sendMessage()方法。大致可以分为两步:

  1. 根据接收到的消息,封装成一个MessageExtBrokerInner对象
  2. 调用MessageStore.putMessage()方法
    private RemotingCommand sendMessage(final ChannelHandlerContext ctx,
                                        final RemotingCommand request,
                                        final SendMessageContext sendMessageContext,
                                        final SendMessageRequestHeader requestHeader) throws RemotingCommandException {
   
                                        
        // 部分代码省略


        final byte[] body = request.getBody();

        int queueIdInt = requestHeader.getQueueId();
        TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());

        if (queueIdInt < 0) {
   
            queueIdInt = Math.abs(this.random.nextInt() % 99999999) % topicConfig.getWriteQueueNums();
        }

		// 第一步:从请求中取出消息的相关内容,并封装成一个MessageExtBrokerInner对象。
		// 后续在broker内的消息处理都会使用这个MessageExtBrokerInner对象
        MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
        msgInner.setTopic(requestHeader.getTopic());
        msgInner.setQueueId(queueIdInt);

        if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) {
   
            return response;
        }

        msgInner.setBody(body);
        msgInner.setFlag(requestHeader.getFlag());
        MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties()));
        msgInner.setBornTimestamp(requestHeader.getBornTimestamp());
        msgInner.setBornHost(ctx.channel().remoteAddress());
        msgInner.setStoreHost(this.getStoreHost());
        msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes());
        String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName();
        MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName);
        msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
        PutMessageResult putMessageResult = null;
        Map<String, String> oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());
        String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
        if (traFlag != null && Boolean.parseBoolean(traFlag)) {
   
            if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
   
                response.setCode(ResponseCode.NO_PERMISSION);
                response.setRemark(
                    "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                        + "] sending transaction message is forbidden");
                return response;
            }
            putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
        } else {
   
        	// 第二步:普通消息的处理,执行MessageStore的putMessage方法
        	// 传入的参数就是上面构建的MessageExtBrokerInner对象
            putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
        }

        return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);

    }

接下来重点就是这个MessageStore.putMessage()方法啦,再看这个方法之前,我们先来了解一下接下来会涉及到的各种结构,对后面理解代码会很有帮助。

二、CommitLog结构

在这里插入图片描述

在这里插入图片描述
结合上面这两张图,首先,每个broker都有一个MessageStoreMessageStore是一个接口,它的实现类为DefaultMessageStoreDefaultMessageStore中有一个成员变量CommitLog,而CommitLog中又有一个MappedFileQueueMappedFileQueue中保存了一个MappedFile的List,其中每一个MappedFile都对应了磁盘上的一个文件,这些文件才是真正存储消息的地方。

可以这么理解,从我们用户的角度来看,所有消息都好像被保存到了一个很大的文件CommitLog上,而实际上RocketMQ在实现的时候把这个CommitLog拆成了一个个小文件MappedFile,而这些MappedFile都被一个MappedFileQueue所管理。

上面第二张图中CommitLog中那些数字代表什么?
在RocketMQ中,每个MappedFile默认大小都是1G,每个MappedFile的文件名都是一个20位的数字,如“00000000001073741824”,如果把左边的0都去掉,即"1073741824",这个数字就是当前MappedFile中第一个byte在整个CommitLog中的位置,也就是偏移量。假设给定一个CommitLog偏移量为1073741830,那么我们很容易就可以知道,这个数据一定是在第二个MappedFile中,并且在第二个MappedFile中的相对偏移量为1073741830 - 1073741824 = 6

当一个MappedFile写满以后,就会创建一个新的MappedFile然后往这个新的MappedFile写。

好了,接下来我们再回到上面的那个代码中的第二步,也就是创建了MessageExtBrokerInner对象以后调用的MessageStore.putMessage()方法。这里大家其实应该能猜到后面要干什么了吧,因为最终目的是要往MappedFile里面写数据, 那MessageStore.putMessage()肯定得先调用CommitLog的某个put方法,那CommitLog肯定会去MappedFileQueue找一个MappedFile,然后调用MappedFile的某个put方法。下面看实际代码

三、CommitLog处理消息写入主流程

代码DefaultMessageStore.putMessage()

    public PutMessageResult putMessage(MessageExtBrokerInner msg) {
   
    	
    	// 此处省略了broker状态和消息校验的代码
    	
        long beginTime = this.getSystemClock().now();
        // 果然调用了commitLog的putMessage方法,接下来看这个方法
        PutMessageResult result = this.commitLog.putMessage(msg);

        long elapsedTime = this.getSystemClock().now() - beginTime;
        if (elapsedTime > 500) {
   
            log.warn("putMessage not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, msg.getBody().length);
        }
        this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime);

        if (null == result || !result.isOk()) {
   
            this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
        }

        return result;
    }

代码:CommitLog.putMessage()

    public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
   
        // Set the storage time
        msg.setStoreTimestamp(System.currentTimeMillis());
        // Set the message body BODY CRC (consider the most appropriate setting
        // on the client)
        msg.setBodyCRC(UtilAll.crc32(msg.getBody()));
        // Back to Results
        AppendMessageResult result = null;

		// 一个数据统计的服务,不用管
        StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();

        String topic = msg.getTopic();
        int queueId = msg.getQueueId();

        final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
        // 处理事务消息,先不管
        if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
            || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
   
            // Delay Delivery
            if 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值