RocketMQ中PullConsumer的消息拉取源码分析

在PullConsumer中,有关消息的拉取RocketMQ提供了很多API,但总的来说分为两种,同步消息拉取和异步消息拉取


同步消息拉取
以同步方式拉取消息都是通过DefaultMQPullConsumerImpl的pullSyncImpl方法:

 1 private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
 2     long timeout)
 3     throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
 4     this.makeSureStateOK();
 5 
 6     if (null == mq) {
 7         throw new MQClientException("mq is null", null);
 8     }
 9 
10     if (offset < 0) {
11         throw new MQClientException("offset < 0", null);
12     }
13 
14     if (maxNums <= 0) {
15         throw new MQClientException("maxNums <= 0", null);
16     }
17 
18     this.subscriptionAutomatically(mq.getTopic());
19 
20     int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);
21 
22     long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;
23 
24     boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
25     PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
26         mq,
27         subscriptionData.getSubString(),
28         subscriptionData.getExpressionType(),
29         isTagType ? 0L : subscriptionData.getSubVersion(),
30         offset,
31         maxNums,
32         sysFlag,
33         0,
34         this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
35         timeoutMillis,
36         CommunicationMode.SYNC,
37         null
38     );
39     this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData);
40     if (!this.consumeMessageHookList.isEmpty()) {
41         ConsumeMessageContext consumeMessageContext = null;
42         consumeMessageContext = new ConsumeMessageContext();
43         consumeMessageContext.setConsumerGroup(this.groupName());
44         consumeMessageContext.setMq(mq);
45         consumeMessageContext.setMsgList(pullResult.getMsgFoundList());
46         consumeMessageContext.setSuccess(false);
47         this.executeHookBefore(consumeMessageContext);
48         consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString());
49         consumeMessageContext.setSuccess(true);
50         this.executeHookAfter(consumeMessageContext);
51     }
52     return pullResult;
53 }

首先通过subscriptionAutomatically方法检查Topic是否订阅

 

 1 public void subscriptionAutomatically(final String topic) {
 2     if (!this.rebalanceImpl.getSubscriptionInner().containsKey(topic)) {
 3         try {
 4             SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPullConsumer.getConsumerGroup(),
 5                 topic, SubscriptionData.SUB_ALL);
 6             this.rebalanceImpl.subscriptionInner.putIfAbsent(topic, subscriptionData);
 7         } catch (Exception ignore) {
 8         }
 9     }
10 }

若是没有就新建一条订阅数据保存在rebalanceImpl的subscriptionInner中


之后调用pullKernelImpl方法:

 1 public PullResult pullKernelImpl(
 2     final MessageQueue mq,
 3     final String subExpression,
 4     final String expressionType,
 5     final long subVersion,
 6     final long offset,
 7     final int maxNums,
 8     final int sysFlag,
 9     final long commitOffset,
10     final long brokerSuspendMaxTimeMillis,
11     final long timeoutMillis,
12     final CommunicationMode communicationMode,
13     final PullCallback pullCallback
14 ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
15     FindBrokerResult findBrokerResult =
16         this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
17             this.recalculatePullFromWhichNode(mq), false);
18     if (null == findBrokerResult) {
19         this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
20         findBrokerResult =
21             this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
22                 this.recalculatePullFromWhichNode(mq), false);
23     }
24 
25     if (findBrokerResult != null) {
26         {
27             // check version
28             if (!ExpressionType.isTagType(expressionType)
29                 && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
30                 throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
31                     + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
32             }
33         }
34         int sysFlagInner = sysFlag;
35 
36         if (findBrokerResult.isSlave()) {
37             sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
38         }
39 
40         PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
41         requestHeader.setConsumerGroup(this.consumerGroup);
42         requestHeader.setTopic(mq.getTopic());
43         requestHeader.setQueueId(mq.getQueueId());
44         requestHeader.setQueueOffset(offset);
45         requestHeader.setMaxMsgNums(maxNums);
46         requestHeader.setSysFlag(sysFlagInner);
47         requestHeader.setCommitOffset(commitOffset);
48         requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
49         requestHeader.setSubscription(subExpression);
50         requestHeader.setSubVersion(subVersion);
51         requestHeader.setExpressionType(expressionType);
52 
53         String brokerAddr = findBrokerResult.getBrokerAddr();
54         if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
55             brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
56         }
57 
58         PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
59             brokerAddr,
60             requestHeader,
61             timeoutMillis,
62             communicationMode,
63             pullCallback);
64 
65         return pullResult;
66     }
67 
68     throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
69 }

首先通过findBrokerAddressInSubscribe方法查找关于消息队列的Broker信息


这里的recalculatePullFromWhichNode方法:

 1 public long recalculatePullFromWhichNode(final MessageQueue mq) {
 2     if (this.isConnectBrokerByUser()) {
 3         return this.defaultBrokerId;
 4     }
 5 
 6     AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
 7     if (suggest != null) {
 8         return suggest.get();
 9     }
10 
11     return MixAll.MASTER_ID;
12 }

根据消息队列,在pullFromWhichNodeTable查找其对应的Broker的ID
pullFromWhichNodeTable记录了消息对了和BrokerID的映射

1 private ConcurrentMap<MessageQueue, AtomicLong/* brokerId */> pullFromWhichNodeTable =
2         new ConcurrentHashMap<MessageQueue, AtomicLong>(32);

(master的BrokerID为0,slave的BrokerID大于0)

 

findBrokerAddressInSubscribe方法:

 1 public FindBrokerResult findBrokerAddressInSubscribe(
 2     final String brokerName,
 3     final long brokerId,
 4     final boolean onlyThisBroker
 5 ) {
 6     String brokerAddr = null;
 7     boolean slave = false;
 8     boolean found = false;
 9 
10     HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
11     if (map != null && !map.isEmpty()) {
12         brokerAddr = map.get(brokerId);
13         slave = brokerId != MixAll.MASTER_ID;
14         found = brokerAddr != null;
15 
16         if (!found && !onlyThisBroker) {
17             Entry<Long, String> entry = map.entrySet().iterator().next();
18             brokerAddr = entry.getValue();
19             slave = entry.getKey() != MixAll.MASTER_ID;
20             found = true;
21         }
22     }
23 
24     if (found) {
25         return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
26     }
27 
28     return null;
29 }

这里就根据brokerAddrTable表查找该BrokerID对应的Broker的地址信息,以及是否是slave
封装为FindBrokerResult返回


若是没有找到Broker的路由信息,则通过updateTopicRouteInfoFromNameServer方法向NameServer请求更新,更新完成后再调用findBrokerAddressInSubscribe方法查找


之后会根据相应的信息封装请求消息头PullMessageRequestHeader

然后调用pullMessage方法:

 1 public PullResult pullMessage(
 2     final String addr,
 3     final PullMessageRequestHeader requestHeader,
 4     final long timeoutMillis,
 5     final CommunicationMode communicationMode,
 6     final PullCallback pullCallback
 7 ) throws RemotingException, MQBrokerException, InterruptedException {
 8     RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);
 9 
10     switch (communicationMode) {
11         case ONEWAY:
12             assert false;
13             return null;
14         case ASYNC:
15             this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
16             return null;
17         case SYNC:
18             return this.pullMessageSync(addr, request, timeoutMillis);
19         default:
20             assert false;
21             break;
22     }
23 
24     return null;
25 }

这里就可以看出我前面说的两种类型,同步拉取和异步拉取


pullMessageSync方法:

1 private PullResult pullMessageSync(
2     final String addr,
3     final RemotingCommand request,
4     final long timeoutMillis
5 ) throws RemotingException, InterruptedException, MQBrokerException {
6     RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
7     assert response != null;
8     return this.processPullResponse(response);
9 }

这里其实就是通过invokeSync方法,由Netty进行同步发送,将请求发送给Broker
关于消息的发送详见:

【RocketMQ中Producer消息的发送源码分析】

 

在收到响应后由processPullResponse方法处理
processPullResponse方法:

 1 private PullResult processPullResponse(
 2     final RemotingCommand response) throws MQBrokerException, RemotingCommandException {
 3     PullStatus pullStatus = PullStatus.NO_NEW_MSG;
 4     switch (response.getCode()) {
 5         case ResponseCode.SUCCESS:
 6             pullStatus = PullStatus.FOUND;
 7             break;
 8         case ResponseCode.PULL_NOT_FOUND:
 9             pullStatus = PullStatus.NO_NEW_MSG;
10             break;
11         case ResponseCode.PULL_RETRY_IMMEDIATELY:
12             pullStatus = PullStatus.NO_MATCHED_MSG;
13             break;
14         case ResponseCode.PULL_OFFSET_MOVED:
15             pullStatus = PullStatus.OFFSET_ILLEGAL;
16             break;
17 
18         default:
19             throw new MQBrokerException(response.getCode(), response.getRemark());
20     }
21 
22     PullMessageResponseHeader responseHeader =
23         (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);
24 
25     return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),
26         responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
27 }

根据响应的状态,设置PullStatus状态

然后通过decodeCommandCustomHeader方法,将响应中的信息解码
最后由PullResultExt封装消息信息

 1 public class PullResultExt extends PullResult {
 2     private final long suggestWhichBrokerId;
 3     private byte[] messageBinary;
 4 
 5     public PullResultExt(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
 6         List<MessageExt> msgFoundList, final long suggestWhichBrokerId, final byte[] messageBinary) {
 7         super(pullStatus, nextBeginOffset, minOffset, maxOffset, msgFoundList);
 8         this.suggestWhichBrokerId = suggestWhichBrokerId;
 9         this.messageBinary = messageBinary;
10     }
11     ......
12 }
13 
14 public class PullResult {
15     private final PullStatus pullStatus;
16     private final long nextBeginOffset;
17     private final long minOffset;
18     private final long maxOffset;
19     private List<MessageExt> msgFoundList;
20     
21     public PullResult(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
22         List<MessageExt> msgFoundList) {
23         super();
24         this.pullStatus = pullStatus;
25         this.nextBeginOffset = nextBeginOffset;
26         this.minOffset = minOffset;
27         this.maxOffset = maxOffset;
28         this.msgFoundList = msgFoundList;
29     }
30     ......
31 }

拉取到的消息可能是多条,具体内容在PullResult 中的msgFoundList保存,MessageExt是Message的超类

 

回到pullSyncImpl方法,在拉取到消息后,调用processPullResult方法:

 1 public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult,
 2     final SubscriptionData subscriptionData) {
 3     PullResultExt pullResultExt = (PullResultExt) pullResult;
 4 
 5     this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
 6     if (PullStatus.FOUND == pullResult.getPullStatus()) {
 7         ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
 8         List<MessageExt> msgList = MessageDecoder.decodes(byteBuffer);
 9 
10         List<MessageExt> msgListFilterAgain = msgList;
11         if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) {
12             msgListFilterAgain = new ArrayList<MessageExt>(msgList.size());
13             for (MessageExt msg : msgList) {
14                 if (msg.getTags() != null) {
15                     if (subscriptionData.getTagsSet().contains(msg.getTags())) {
16                         msgListFilterAgain.add(msg);
17                     }
18                 }
19             }
20         }
21 
22         if (this.hasHook()) {
23             FilterMessageContext filterMessageContext = new FilterMessageContext();
24             filterMessageContext.setUnitMode(unitMode);
25             filterMessageContext.setMsgList(msgListFilterAgain);
26             this.executeHook(filterMessageContext);
27         }
28 
29         for (MessageExt msg : msgListFilterAgain) {
30             String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
31             if (traFlag != null && Boolean.parseBoolean(traFlag)) {
32                 msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
33             }
34             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
35                 Long.toString(pullResult.getMinOffset()));
36             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
37                 Long.toString(pullResult.getMaxOffset()));
38         }
39 
40         pullResultExt.setMsgFoundList(msgListFilterAgain);
41     }
42 
43     pullResultExt.setMessageBinary(null);
44 
45     return pullResult;
46 }

首先调用updatePullFromWhichNode方法:

1 public void updatePullFromWhichNode(final MessageQueue mq, final long brokerId) {
2    AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
3     if (null == suggest) {
4         this.pullFromWhichNodeTable.put(mq, new AtomicLong(brokerId));
5     } else {
6         suggest.set(brokerId);
7     }
8 }

这里就会将pullFromWhichNodeTable中记录的消息队列和BrokerID的映射,更新为Broker发送过来的建议ID
结合上一篇博客来看,若是采用集群模式,就完成了消费者端的负载均衡


在PullStatus.FOUND情况下,会调用MessageDecoder的decodes方法,将CommitLog格式的消息数据进行解码,转化为真正可读的消息

之后会对Tag进行判断,设置了Tag,添加Tag消息记录

之后,在设置了FilterMessageHook钩子情况下,通过executeHook方法执行FilterMessageHook钩子的filterMessage方法:

 1 public void executeHook(final FilterMessageContext context) {
 2     if (!this.filterMessageHookList.isEmpty()) {
 3         for (FilterMessageHook hook : this.filterMessageHookList) {
 4             try {
 5                 hook.filterMessage(context);
 6             } catch (Throwable e) {
 7                 log.error("execute hook error. hookName={}", hook.hookName());
 8             }
 9         }
10     }
11 }

然后对消息进行属性设置


processPullResult完成后,若是设置了ConsumeMessageHook钩子,调用executeHookBefore和executeHookAfter方法,分别执行钩子中的consumeMessageBefore和consumeMessageAfter方法:

 1 public void executeHookBefore(final ConsumeMessageContext context) {
 2     if (!this.consumeMessageHookList.isEmpty()) {
 3         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
 4             try {
 5                 hook.consumeMessageBefore(context);
 6             } catch (Throwable ignored) {
 7             }
 8         }
 9     }
10 }
11 
12 public void executeHookAfter(final ConsumeMessageContext context) {
13     if (!this.consumeMessageHookList.isEmpty()) {
14         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
15             try {
16                 hook.consumeMessageAfter(context);
17             } catch (Throwable ignored) {
18             }
19         }
20     }
21 }

PullConsumer消息的同步拉取到此结束

 


异步消息拉取

异步拉取的API都通过pullAsyncImpl方法实现:

 1 private void pullAsyncImpl(
 2     final MessageQueue mq,
 3     final SubscriptionData subscriptionData,
 4     final long offset,
 5     final int maxNums,
 6     final PullCallback pullCallback,
 7     final boolean block,
 8     final long timeout) throws MQClientException, RemotingException, InterruptedException {
 9     this.makeSureStateOK();
10 
11     if (null == mq) {
12         throw new MQClientException("mq is null", null);
13     }
14 
15     if (offset < 0) {
16         throw new MQClientException("offset < 0", null);
17     }
18 
19     if (maxNums <= 0) {
20         throw new MQClientException("maxNums <= 0", null);
21     }
22 
23     if (null == pullCallback) {
24         throw new MQClientException("pullCallback is null", null);
25     }
26 
27     this.subscriptionAutomatically(mq.getTopic());
28 
29     try {
30         int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);
31 
32         long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;
33 
34         boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
35         this.pullAPIWrapper.pullKernelImpl(
36             mq,
37             subscriptionData.getSubString(),
38             subscriptionData.getExpressionType(),
39             isTagType ? 0L : subscriptionData.getSubVersion(),
40             offset,
41             maxNums,
42             sysFlag,
43             0,
44             this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
45             timeoutMillis,
46             CommunicationMode.ASYNC,
47             new PullCallback() {
48 
49                 @Override
50                 public void onSuccess(PullResult pullResult) {
51                     pullCallback
52                         .onSuccess(DefaultMQPullConsumerImpl.this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData));
53                 }
54 
55                 @Override
56                 public void onException(Throwable e) {
57                     pullCallback.onException(e);
58                 }
59             });
60     } catch (MQBrokerException e) {
61         throw new MQClientException("pullAsync unknow exception", e);
62     }
63 }

相比同步,参数多了个PullCallback,用于处理异步拉取后的回调


过程基本上个同步拉取类似,只不过在调用pullKernelImpl方法时,会创建一个PullCallback
在onSuccess和onException中,实际上调用了pullCallback的相应方法,这样就完成了异步的回调

在onSuccess回调的参数中,同同步方式类似,会通过processPullResult方法,对结果进一步加工


之后的pullKernelImpl方法和同步一样

只不过最后调用了pullMessageAsync方法:

 1 private void pullMessageAsync(
 2     final String addr,
 3     final RemotingCommand request,
 4     final long timeoutMillis,
 5     final PullCallback pullCallback
 6 ) throws RemotingException, InterruptedException {
 7     this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
 8         @Override
 9         public void operationComplete(ResponseFuture responseFuture) {
10             RemotingCommand response = responseFuture.getResponseCommand();
11             if (response != null) {
12                 try {
13                     PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);
14                     assert pullResult != null;
15                     pullCallback.onSuccess(pullResult);
16                 } catch (Exception e) {
17                     pullCallback.onException(e);
18                 }
19             } else {
20                 if (!responseFuture.isSendRequestOK()) {
21                     pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
22                 } else if (responseFuture.isTimeout()) {
23                     pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
24                         responseFuture.getCause()));
25                 } else {
26                     pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
27                 }
28             }
29         }
30     });
31 }

这里实际上也是通过Netty完成异步发送
详见:

【RocketMQ中Producer消息的发送源码分析】

 


由于是异步发送,这里又设置了一个回调InvokeCallback
当请求发送完成,收到响应后,就会执行InvokeCallback的operationComplete方法,

在operationComplete方法中,和同步一样,执行processPullResponse方法,处理响应
之后调用pullCallback的onSuccess方法,也就是刚才创建的回调接口,进而执行用户传入的回调接口的方法


消息异步拉取也就到此结束

 

posted @ 2019-08-13 16:22  松饼人  阅读(2218)  评论(0编辑  收藏  举报