• RocketMQ(4.8.0)——Broker读写分离机制


    Broker读写分离机制

      在 RocketMQ 中,有2处地方使用到 "读写分离" 机制。

      Broker Master-Slave 读写分离:写操作到 Master Broker,从 Slave Broker 读取消息。Broker 配置为 slaveReadEnable=True(默认False),消息占用内存百度分配置为 accessMessageInMemoryMaxRatio=40(默认)。

      Broker Direct Memory-Page Cache 读写:写消息到 Direct Memory(直接内存,简称 DM),从操作系统的 Page Cache 中读取消息。Master Broker 配置读写分离开关为 tranientStorePoolEnable=True(默认为False),写入 DM 存储数量,配置 trainsientStorePoolSize 至少大于0(默认为5,建议不修改),刷盘类型配置为 flushDiskType=FlushDiskType.ASYNC_FLUSH,即异步输盘。

      首先我们来讲 Master-Slave 读写分离机制。通常,都是 Master 提供读写处理,如果 Master 负载较高,就从 Slave 读取,整个过程如下:

      该机制的实现分为以下两个步骤。

      第一步:Broker 在处理 Pull 消息时,计算下次是否从 Slave 拉取消息,是通过 org.apache.rocketmq.store.DefaultMessageStore.getMessage() 方法实现的,代码路径:D: ocketmq-masterstoresrcmainjavaorgapache ocketmqstoreDefaultMessageStore.java,代码如下:

     1                 SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(offset);
     2                 if (bufferConsumeQueue != null) {
     3                     try {
     4                         status = GetMessageStatus.NO_MATCHED_MESSAGE;
     5 
     6                         long nextPhyFileStartOffset = Long.MIN_VALUE;
     7                         long maxPhyOffsetPulling = 0;   #表示拉取的最大消息位点。
     8 
     9                         int i = 0;
    10                         final int maxFilterMessageCount = Math.max(16000, maxMsgNums * ConsumeQueue.CQ_STORE_UNIT_SIZE);
    11                         final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded();
    12                         ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit();
    13                         for (; i < bufferConsumeQueue.getSize() && i < maxFilterMessageCount; i += ConsumeQueue.CQ_STORE_UNIT_SIZE) {
    14                             long offsetPy = bufferConsumeQueue.getByteBuffer().getLong();
    15                             int sizePy = bufferConsumeQueue.getByteBuffer().getInt();
    16                             long tagsCode = bufferConsumeQueue.getByteBuffer().getLong();
    17 
    18                             maxPhyOffsetPulling = offsetPy;
    19 
    20                             if (nextPhyFileStartOffset != Long.MIN_VALUE) {
    21                                 if (offsetPy < nextPhyFileStartOffset)
    22                                     continue;
    23                             }
    24 
    25                             boolean isInDisk = checkInDiskByCommitOffset(offsetPy, maxOffsetPy);  #表示当前 Master Broker 存储的所有消息的最大物理位点
    26 
    27                             if (this.isTheBatchFull(sizePy, maxMsgNums, getResult.getBufferTotalSize(), getResult.getMessageCount(),
    28                                 isInDisk)) {
    29                                 break;
    30                             }
    31 
    32                             boolean extRet = false, isTagsCodeLegal = true;
    33                             if (consumeQueue.isExtAddr(tagsCode)) {
    34                                 extRet = consumeQueue.getExt(tagsCode, cqExtUnit);
    35                                 if (extRet) {
    36                                     tagsCode = cqExtUnit.getTagsCode();
    37                                 } else {
    38                                     // can't find ext content.Client will filter messages by tag also.
    39                                     log.error("[BUG] can't find consume queue extend file content!addr={}, offsetPy={}, sizePy={}, topic={}, group={}",
    40                                         tagsCode, offsetPy, sizePy, topic, group);
    41                                     isTagsCodeLegal = false;
    42                                 }
    43                             }
    44 
    45                             if (messageFilter != null
    46                                 && !messageFilter.isMatchedByConsumeQueue(isTagsCodeLegal ? tagsCode : null, extRet ? cqExtUnit : null)) {
    47                                 if (getResult.getBufferTotalSize() == 0) {
    48                                     status = GetMessageStatus.NO_MATCHED_MESSAGE;
    49                                 }
    50 
    51                                 continue;
    52                             }
    53 
    54                             SelectMappedBufferResult selectResult = this.commitLog.getMessage(offsetPy, sizePy);
    55                             if (null == selectResult) {
    56                                 if (getResult.getBufferTotalSize() == 0) {
    57                                     status = GetMessageStatus.MESSAGE_WAS_REMOVING;
    58                                 }
    59 
    60                                 nextPhyFileStartOffset = this.commitLog.rollNextFile(offsetPy);
    61                                 continue;
    62                             }
    63 
    64                             if (messageFilter != null
    65                                 && !messageFilter.isMatchedByCommitLog(selectResult.getByteBuffer().slice(), null)) {
    66                                 if (getResult.getBufferTotalSize() == 0) {
    67                                     status = GetMessageStatus.NO_MATCHED_MESSAGE;
    68                                 }
    69                                 // release...
    70                                 selectResult.release();
    71                                 continue;
    72                             }
    73 
    74                             this.storeStatsService.getGetMessageTransferedMsgCount().incrementAndGet();
    75                             getResult.addMessage(selectResult);
    76                             status = GetMessageStatus.FOUND;
    77                             nextPhyFileStartOffset = Long.MIN_VALUE;
    78                         }
    79 
    80                         if (diskFallRecorded) {
    81                             long fallBehind = maxOffsetPy - maxPhyOffsetPulling;
    82                             brokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, fallBehind);
    83                         }
    84 
    85                         nextBeginOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
    86 
    87                         long diff = maxOffsetPy - maxPhyOffsetPulling;  #diff:是 maxOffsetPy 和 maxPhyOffsetPulling 两者的差值,表示还有多少消息没有拉取
    88                         long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE #StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE:表示当前 Master Broker 全部的物理内存大小。
           #memory:Broker 认为可使用最大内存,该值可以通过 accessMessageInMemoryMaxRatio 配置项决定,默认 accessMessageInMemoryMaxRatio=40,如果物理内存为 100MB,那么memory=40MB。
    89                             * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0)); 
    #设置下次从 Master 或 Slave 拉取消息 90 getResult.setSuggestPullingFromSlave(diff > memory);#表示没有拉取的消息比分配的内存大,如果diff>memory的值为True,则说明此事Master Broker内存繁忙,该从slave拉 91 } finally { 92 93 bufferConsumeQueue.release(); 94 } 95 }

       第二步:通知客户端下次从哪个 Broker 拉取消息。在消费者 Pull 消息返回结果时,根据第一步设置的 suggestPullingFromSlave 值返回给消费者,该过程通过 D: ocketmq-masterrokersrcmainjavaorgapache ocketmqrokerprocessorPullMessageProcessor.java 中 processRequest()方法实现,具体代码如下:

      1 private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend)
      2         throws RemotingCommandException {
      3         RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class);
      4         final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader();
      5         final PullMessageRequestHeader requestHeader =
      6             (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class);
      7 
      8         response.setOpaque(request.getOpaque());
      9 
     10         log.debug("receive PullMessage request command, {}", request);
     11 
     12         if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) {
     13             response.setCode(ResponseCode.NO_PERMISSION);
     14             response.setRemark(String.format("the broker[%s] pulling message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1()));
     15             return response;
     16         }
     17 
     18         SubscriptionGroupConfig subscriptionGroupConfig =
     19             this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup());
     20         if (null == subscriptionGroupConfig) {
     21             response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST);
     22             response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)));
     23             return response;
     24         }
     25 
     26         if (!subscriptionGroupConfig.isConsumeEnable()) {
     27             response.setCode(ResponseCode.NO_PERMISSION);
     28             response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup());
     29             return response;
     30         }
     31 
     32         final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag());
     33         final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag());
     34         final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag());
     35 
     36         final long suspendTimeoutMillisLong = hasSuspendFlag ? requestHeader.getSuspendTimeoutMillis() : 0;
     37 
     38         TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
     39         if (null == topicConfig) {
     40             log.error("the topic {} not exist, consumer: {}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel));
     41             response.setCode(ResponseCode.TOPIC_NOT_EXIST);
     42             response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)));
     43             return response;
     44         }
     45 
     46         if (!PermName.isReadable(topicConfig.getPerm())) {
     47             response.setCode(ResponseCode.NO_PERMISSION);
     48             response.setRemark("the topic[" + requestHeader.getTopic() + "] pulling message is forbidden");
     49             return response;
     50         }
     51 
     52         if (requestHeader.getQueueId() < 0 || requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) {
     53             String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]",
     54                 requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress());
     55             log.warn(errorInfo);
     56             response.setCode(ResponseCode.SYSTEM_ERROR);
     57             response.setRemark(errorInfo);
     58             return response;
     59         }
     60 
     61         SubscriptionData subscriptionData = null;
     62         ConsumerFilterData consumerFilterData = null;
     63         if (hasSubscriptionFlag) {
     64             try {
     65                 subscriptionData = FilterAPI.build(
     66                     requestHeader.getTopic(), requestHeader.getSubscription(), requestHeader.getExpressionType()
     67                 );
     68                 if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) {
     69                     consumerFilterData = ConsumerFilterManager.build(
     70                         requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getSubscription(),
     71                         requestHeader.getExpressionType(), requestHeader.getSubVersion()
     72                     );
     73                     assert consumerFilterData != null;
     74                 }
     75             } catch (Exception e) {
     76                 log.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getSubscription(),
     77                     requestHeader.getConsumerGroup());
     78                 response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED);
     79                 response.setRemark("parse the consumer's subscription failed");
     80                 return response;
     81             }
     82         } else {
     83             ConsumerGroupInfo consumerGroupInfo =
     84                 this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup());
     85             if (null == consumerGroupInfo) {
     86                 log.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup());
     87                 response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);
     88                 response.setRemark("the consumer's group info not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));
     89                 return response;
     90             }
     91 
     92             if (!subscriptionGroupConfig.isConsumeBroadcastEnable()
     93                 && consumerGroupInfo.getMessageModel() == MessageModel.BROADCASTING) {
     94                 response.setCode(ResponseCode.NO_PERMISSION);
     95                 response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] can not consume by broadcast way");
     96                 return response;
     97             }
     98 
     99             subscriptionData = consumerGroupInfo.findSubscriptionData(requestHeader.getTopic());
    100             if (null == subscriptionData) {
    101                 log.warn("the consumer's subscription not exist, group: {}, topic:{}", requestHeader.getConsumerGroup(), requestHeader.getTopic());
    102                 response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);
    103                 response.setRemark("the consumer's subscription not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));
    104                 return response;
    105             }
    106 
    107             if (subscriptionData.getSubVersion() < requestHeader.getSubVersion()) {
    108                 log.warn("The broker's subscription is not latest, group: {} {}", requestHeader.getConsumerGroup(),
    109                     subscriptionData.getSubString());
    110                 response.setCode(ResponseCode.SUBSCRIPTION_NOT_LATEST);
    111                 response.setRemark("the consumer's subscription not latest");
    112                 return response;
    113             }
    114             if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) {
    115                 consumerFilterData = this.brokerController.getConsumerFilterManager().get(requestHeader.getTopic(),
    116                     requestHeader.getConsumerGroup());
    117                 if (consumerFilterData == null) {
    118                     response.setCode(ResponseCode.FILTER_DATA_NOT_EXIST);
    119                     response.setRemark("The broker's consumer filter data is not exist!Your expression may be wrong!");
    120                     return response;
    121                 }
    122                 if (consumerFilterData.getClientVersion() < requestHeader.getSubVersion()) {
    123                     log.warn("The broker's consumer filter data is not latest, group: {}, topic: {}, serverV: {}, clientV: {}",
    124                         requestHeader.getConsumerGroup(), requestHeader.getTopic(), consumerFilterData.getClientVersion(), requestHeader.getSubVersion());
    125                     response.setCode(ResponseCode.FILTER_DATA_NOT_LATEST);
    126                     response.setRemark("the consumer's consumer filter data not latest");
    127                     return response;
    128                 }
    129             }
    130         }
    131 
    132         if (!ExpressionType.isTagType(subscriptionData.getExpressionType())
    133             && !this.brokerController.getBrokerConfig().isEnablePropertyFilter()) {
    134             response.setCode(ResponseCode.SYSTEM_ERROR);
    135             response.setRemark("The broker does not support consumer to filter message by " + subscriptionData.getExpressionType());
    136             return response;
    137         }
    138 
    139         MessageFilter messageFilter;
    140         if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) {
    141             messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData,
    142                 this.brokerController.getConsumerFilterManager());
    143         } else {
    144             messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData,
    145                 this.brokerController.getConsumerFilterManager());
    146         }
    147 
    148         final GetMessageResult getMessageResult =
    149             this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
    150                 requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);
    151         if (getMessageResult != null) {
    152             response.setRemark(getMessageResult.getStatus().name());
    153             responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());
    154             responseHeader.setMinOffset(getMessageResult.getMinOffset());
    155             responseHeader.setMaxOffset(getMessageResult.getMaxOffset());
    156 
    157             if (getMessageResult.isSuggestPullingFromSlave()) {
    158                 responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
    159             } else {
    160                 responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    161             }
    162 
    163             switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) {
    164                 case ASYNC_MASTER:
    165                 case SYNC_MASTER:
    166                     break;
    167                 case SLAVE:
    168                     if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
    169                         response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
    170                         responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    171                     }
    172                     break;
    173             }
    174        # slave 读取开关配置
    175             if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
    176                 // consume too slow ,redirect to another machine
    177                 if (getMessageResult.isSuggestPullingFromSlave()) {
    178                     responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
    179                 }
    180                 // consume ok
    181                 else {
    182                     responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
    183                 }
    184             } else {
    185                 responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    186             }
    187 
    188             switch (getMessageResult.getStatus()) {
    189                 case FOUND:
    190                     response.setCode(ResponseCode.SUCCESS);
    191                     break;
    192                 case MESSAGE_WAS_REMOVING:
    193                     response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
    194                     break;
    195                 case NO_MATCHED_LOGIC_QUEUE:
    196                 case NO_MESSAGE_IN_QUEUE:
    197                     if (0 != requestHeader.getQueueOffset()) {
    198                         response.setCode(ResponseCode.PULL_OFFSET_MOVED);
    199 
    200                         // XXX: warn and notify me
    201                         log.info("the broker store no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}",
    202                             requestHeader.getQueueOffset(),
    203                             getMessageResult.getNextBeginOffset(),
    204                             requestHeader.getTopic(),
    205                             requestHeader.getQueueId(),
    206                             requestHeader.getConsumerGroup()
    207                         );
    208                     } else {
    209                         response.setCode(ResponseCode.PULL_NOT_FOUND);
    210                     }
    211                     break;
    212                 case NO_MATCHED_MESSAGE:
    213                     response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
    214                     break;
    215                 case OFFSET_FOUND_NULL:
    216                     response.setCode(ResponseCode.PULL_NOT_FOUND);
    217                     break;
    218                 case OFFSET_OVERFLOW_BADLY:
    219                     response.setCode(ResponseCode.PULL_OFFSET_MOVED);
    220                     // XXX: warn and notify me
    221                     log.info("the request offset: {} over flow badly, broker max offset: {}, consumer: {}",
    222                         requestHeader.getQueueOffset(), getMessageResult.getMaxOffset(), channel.remoteAddress());
    223                     break;
    224                 case OFFSET_OVERFLOW_ONE:
    225                     response.setCode(ResponseCode.PULL_NOT_FOUND);
    226                     break;
    227                 case OFFSET_TOO_SMALL:
    228                     response.setCode(ResponseCode.PULL_OFFSET_MOVED);
    229                     log.info("the request offset too small. group={}, topic={}, requestOffset={}, brokerMinOffset={}, clientIp={}",
    230                         requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueOffset(),
    231                         getMessageResult.getMinOffset(), channel.remoteAddress());
    232                     break;
    233                 default:
    234                     assert false;
    235                     break;
    236             }
    237 
    238             if (this.hasConsumeMessageHook()) {
    239                 ConsumeMessageContext context = new ConsumeMessageContext();
    240                 context.setConsumerGroup(requestHeader.getConsumerGroup());
    241                 context.setTopic(requestHeader.getTopic());
    242                 context.setQueueId(requestHeader.getQueueId());
    243 
    244                 String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER);
    245 
    246                 switch (response.getCode()) {
    247                     case ResponseCode.SUCCESS:
    248                         int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount();
    249                         int incValue = getMessageResult.getMsgCount4Commercial() * commercialBaseCount;
    250 
    251                         context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_SUCCESS);
    252                         context.setCommercialRcvTimes(incValue);
    253                         context.setCommercialRcvSize(getMessageResult.getBufferTotalSize());
    254                         context.setCommercialOwner(owner);
    255 
    256                         break;
    257                     case ResponseCode.PULL_NOT_FOUND:
    258                         if (!brokerAllowSuspend) {
    259 
    260                             context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS);
    261                             context.setCommercialRcvTimes(1);
    262                             context.setCommercialOwner(owner);
    263 
    264                         }
    265                         break;
    266                     case ResponseCode.PULL_RETRY_IMMEDIATELY:
    267                     case ResponseCode.PULL_OFFSET_MOVED:
    268                         context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS);
    269                         context.setCommercialRcvTimes(1);
    270                         context.setCommercialOwner(owner);
    271                         break;
    272                     default:
    273                         assert false;
    274                         break;
    275                 }
    276 
    277                 this.executeConsumeMessageHookBefore(context);
    278             }
    279 
    280             switch (response.getCode()) {
    281                 case ResponseCode.SUCCESS:
    282 
    283                     this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
    284                         getMessageResult.getMessageCount());
    285 
    286                     this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
    287                         getMessageResult.getBufferTotalSize());
    288 
    289                     this.brokerController.getBrokerStatsManager().incBrokerGetNums(getMessageResult.getMessageCount());
    290                     if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) {
    291                         final long beginTimeMills = this.brokerController.getMessageStore().now();
    292                         final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId());
    293                         this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(),
    294                             requestHeader.getTopic(), requestHeader.getQueueId(),
    295                             (int) (this.brokerController.getMessageStore().now() - beginTimeMills));
    296                         response.setBody(r);
    297                     } else {
    298                         try {
    299                             FileRegion fileRegion =
    300                                 new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult);
    301                             channel.writeAndFlush(fileRegion).addListener(new ChannelFutureListener() {
    302                                 @Override
    303                                 public void operationComplete(ChannelFuture future) throws Exception {
    304                                     getMessageResult.release();
    305                                     if (!future.isSuccess()) {
    306                                         log.error("transfer many message by pagecache failed, {}", channel.remoteAddress(), future.cause());
    307                                     }
    308                                 }
    309                             });
    310                         } catch (Throwable e) {
    311                             log.error("transfer many message by pagecache exception", e);
    312                             getMessageResult.release();
    313                         }
    314 
    315                         response = null;
    316                     }
    317                     break;
    318                 case ResponseCode.PULL_NOT_FOUND:
    319 
    320                     if (brokerAllowSuspend && hasSuspendFlag) {
    321                         long pollingTimeMills = suspendTimeoutMillisLong;
    322                         if (!this.brokerController.getBrokerConfig().isLongPollingEnable()) {
    323                             pollingTimeMills = this.brokerController.getBrokerConfig().getShortPollingTimeMills();
    324                         }
    325 
    326                         String topic = requestHeader.getTopic();
    327                         long offset = requestHeader.getQueueOffset();
    328                         int queueId = requestHeader.getQueueId();
    329                         PullRequest pullRequest = new PullRequest(request, channel, pollingTimeMills,
    330                             this.brokerController.getMessageStore().now(), offset, subscriptionData, messageFilter);
    331                         this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest);
    332                         response = null;
    333                         break;
    334                     }
    335 
    336                 case ResponseCode.PULL_RETRY_IMMEDIATELY:
    337                     break;
    338                 case ResponseCode.PULL_OFFSET_MOVED:
    339                     if (this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE
    340                         || this.brokerController.getMessageStoreConfig().isOffsetCheckInSlave()) {
    341                         MessageQueue mq = new MessageQueue();
    342                         mq.setTopic(requestHeader.getTopic());
    343                         mq.setQueueId(requestHeader.getQueueId());
    344                         mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName());
    345 
    346                         OffsetMovedEvent event = new OffsetMovedEvent();
    347                         event.setConsumerGroup(requestHeader.getConsumerGroup());
    348                         event.setMessageQueue(mq);
    349                         event.setOffsetRequest(requestHeader.getQueueOffset());
    350                         event.setOffsetNew(getMessageResult.getNextBeginOffset());
    351                         this.generateOffsetMovedEvent(event);
    352                         log.warn(
    353                             "PULL_OFFSET_MOVED:correction offset. topic={}, groupId={}, requestOffset={}, newOffset={}, suggestBrokerId={}",
    354                             requestHeader.getTopic(), requestHeader.getConsumerGroup(), event.getOffsetRequest(), event.getOffsetNew(),
    355                             responseHeader.getSuggestWhichBrokerId());
    356                     } else {
    357                         responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
    358                         response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
    359                         log.warn("PULL_OFFSET_MOVED:none correction. topic={}, groupId={}, requestOffset={}, suggestBrokerId={}",
    360                             requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueOffset(),
    361                             responseHeader.getSuggestWhichBrokerId());
    362                     }
    363 
    364                     break;
    365                 default:
    366                     assert false;
    367             }
    368         } else {
    369             response.setCode(ResponseCode.SYSTEM_ERROR);
    370             response.setRemark("store getMessage return null");
    371         }
    372 
    373         boolean storeOffsetEnable = brokerAllowSuspend;
    374         storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag;
    375         storeOffsetEnable = storeOffsetEnable
    376             && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
    377         if (storeOffsetEnable) {
    378             this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),
    379                 requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());
    380         }
    381         return response;
    382     }

      通过查看以上代码第168行,我们知道要想从 Slave 读取消息,需要设置 slaveReadEnable=True,此时会根据第一步返回的 suggestPullingFromSlave 值告诉客户端下次可以从哪个 Broker 拉取消息。suggestPullingFromSlave=1 表示从 Slave 拉取,suggestPullingFromSlave=0 表示从 Master 拉取。

      在了解了 Master-Slave 读写分离机制后,接着讲 Direct Memory-Page Cache 的读写分离机制:

      以上逻辑通过 D: ocketmq-masterstoresrcmainjavaorgapache ocketmqstoreMappedFile.java 中 appendMessagesInner(),具体代码如下:

     1     public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) {
     2         assert messageExt != null;
     3         assert cb != null;
     4 
     5         int currentPos = this.wrotePosition.get();
     6 
     7         if (currentPos < this.fileSize) {
     8             ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice();
     9             byteBuffer.position(currentPos);
    10             AppendMessageResult result;
    11             if (messageExt instanceof MessageExtBrokerInner) {
    12                 result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt);
    13             } else if (messageExt instanceof MessageExtBatch) {
    14                 result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt);
    15             } else {
    16                 return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
    17             }
    18             this.wrotePosition.addAndGet(result.getWroteBytes());
    19             this.storeTimestamp = result.getStoreTimestamp();
    20             return result;
    21         }
    22         log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize);
    23         return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
    24     }

      这段代码中,writeBuffer 表示 从 DM 中申请的缓存;mappendByteBuffer 表示从 Page Cache 中申请的缓存。如果 Broker 设置 transientStorePoolEnable=true,并且异步刷盘,则存储层srcmainjavaorgapache ocketmqstoreDefaultMessageStore.java 在初始化时会调用 TransientStorePool.init() 方法(按照配置的 Buffer 个数)初始化 writeBuffer。代码如下: 

     1     public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager,
     2         final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig) throws IOException {
     3         this.messageArrivingListener = messageArrivingListener;
     4         this.brokerConfig = brokerConfig;
     5         this.messageStoreConfig = messageStoreConfig;
     6         this.brokerStatsManager = brokerStatsManager;
     7         this.allocateMappedFileService = new AllocateMappedFileService(this);
     8         if (messageStoreConfig.isEnableDLegerCommitLog()) {
     9             this.commitLog = new DLedgerCommitLog(this);
    10         } else {
    11             this.commitLog = new CommitLog(this);
    12         }
    13         this.consumeQueueTable = new ConcurrentHashMap<>(32);
    14 
    15         this.flushConsumeQueueService = new FlushConsumeQueueService();
    16         this.cleanCommitLogService = new CleanCommitLogService();
    17         this.cleanConsumeQueueService = new CleanConsumeQueueService();
    18         this.storeStatsService = new StoreStatsService();
    19         this.indexService = new IndexService(this);
    20         if (!messageStoreConfig.isEnableDLegerCommitLog()) {
    21             this.haService = new HAService(this);
    22         } else {
    23             this.haService = null;
    24         }
    25         this.reputMessageService = new ReputMessageService();
    26 
    27         this.scheduleMessageService = new ScheduleMessageService(this);
    28 
    29         this.transientStorePool = new TransientStorePool(messageStoreConfig);
    30 
    31         if (messageStoreConfig.isTransientStorePoolEnable()) {
    32             this.transientStorePool.init();
    33         }
    34 
    35         this.allocateMappedFileService.start();
    36 
    37         this.indexService.start();
    38 
    39         this.dispatcherList = new LinkedList<>();
    40         this.dispatcherList.addLast(new CommitLogDispatcherBuildConsumeQueue());
    41         this.dispatcherList.addLast(new CommitLogDispatcherBuildIndex());
    42 
    43         File file = new File(StorePathConfigHelper.getLockFile(messageStoreConfig.getStorePathRootDir()));
    44         MappedFile.ensureDirOK(file.getParent());
    45         lockFile = new RandomAccessFile(file, "rw");
    46     }

      初始化 writeBuffer 后,当生产者将消息发送到 Broker时,Broker 将消息写入 writeBuffer,然后被异步转存服务不断地从 DM 中 Commit 到 Page Cache 中。消费者此时从哪儿读取数据呢?消费者拉取消息的实现在 D: ocketmq-masterstoresrcmainjavaorgapache ocketmqstoreMappedFile.java 中 selectMappedBuffer() 方法中,具体代码如下:

    1             if (this.hold()) {
    2                 ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
    3                 byteBuffer.position(pos);
    4                 ByteBuffer byteBufferNew = byteBuffer.slice();
    5                 byteBufferNew.limit(size);
    6                 return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);

      从代码中可以看到,消费者始终从 mappedByteBuffer(即 Page Cache)读取消息。

      读写分离能够最大限度地提供吞吐量,同时会增加数据不一致性的风险,建议生产环境谨慎使用。

  • 相关阅读:
    Kafka科普系列 | Kafka中的事务是什么样子的?
    RabbitMQ和Kafka,更加便捷高效的消息队列使用方式,请放心食用
    艰涩难懂,不存在的,消息队列其实很简单
    这七个关于分布式消息服务的常见问题,你知道吗?
    别再犯低级错误,带你了解更新缓存的四种Desigh Pattern
    详细介绍redis的集群功能,带你了解真正意义上的分布式
    教你简单理解分布式与传统单体架构的区别
    新手向:从不同的角度来详细分析Redis
    Java多线程Runnable与Callable区别与拓展
    项目中是用eCharts
  • 原文地址:https://www.cnblogs.com/zuoyang/p/14463423.html
Copyright © 2020-2023  润新知