Source Version
4.8.0
demo code for the official Consumer consumption message
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); // 1. Set NamesrvAddr address consumer.setNamesrvAddr("127.0.0.1:9876"); // 2. Set Consumer Location Optional Value Reference Enumeration ConsumeFromWhere consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // 3. Set Subscription Topics consumer.subscribe("TopicTest", "*"); // 4. Set up consumer messaging business logic consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 5. Consumer startup consumer.start();
As you can see, this is the simplest use of consumer messages. Let's take a closer look at the source code
12,334 are currently set some properties of consumers (DefaultMQPushConsumer). We don't go into specific analysis first, but do it later when we actually use it. We start with 5 consumers directly
@Override public void start() throws MQClientException { // 1. Set up consumer groupID setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); // 2. Consumer startup this.defaultMQPushConsumerImpl.start(); if (null != traceDispatcher) { try { traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); } catch (MQClientException e) { log.warn("trace dispatcher start failed ", e); } } }
- Set up consumer groupID
- Consumer startup, we enter defaultMQPushConsumerImpl.start(); Method
public synchronized void start() throws MQClientException { switch (this.serviceState) { // Consumer just created, not started case CREATE_JUST: log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(), this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode()); this.serviceState = ServiceState.START_FAILED; // 1. Check configuration parameters this.checkConfig(); /** * 2. Get subscription messages from top * copy DefaultMQPushConsumer's subscription information to RebalanceService */ this.copySubscription(); // 3. Cluster mode sets process id to consumer instance name otherwise uses pid if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) { this.defaultMQPushConsumer.changeInstanceNameToPID(); } // 4. Create mqClient cache client and topic information, each service has only one instance of a process this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook); // 5. Set up consumer groups this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup()); // 6. Set up consumption mode subcluster mode and broadcast mode this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel()); // 7. Set default avg for load balancing algorithm this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy()); // 8. Set up mq client this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); // 9. PullRequest encapsulates the implementation class, encapsulates the communication interface with the broker and subsequently cancels the message from the broker. this.pullAPIWrapper = new PullAPIWrapper( mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode()); // 10. Set the hook function that the message is called by the client filter this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); // 11. Set up message consumption progress memory if (this.defaultMQPushConsumer.getOffsetStore() != null) { this.offsetStore = this.defaultMQPushConsumer.getOffsetStore(); } else { switch (this.defaultMQPushConsumer.getMessageModel()) { // Broadcast mode local persistent message consumption progress memory case BROADCASTING: this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); break; // Cluster mode persisted message consumption progress memory case CLUSTERING: this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); break; default: break; } // Set Cluster Mode Persist Message Consumption Progress Storage this.defaultMQPushConsumer.setOffsetStore(this.offsetStore); } // 12. Load consumption progress remotely and locally, locally through file this.offsetStore.load(); // 13. There are two implementation classes for building consumer service consumeMessageService interface: MessageListenerOrderly handles sequential consumption, and MessageListenerConcurrently handles concurrent consumption if (this.getMessageListenerInner() instanceof MessageListenerOrderly) { this.consumeOrderly = true; this.consumeMessageService = new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner()); } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) { // Asynchronous message concurrent consumption this.consumeOrderly = false; this.consumeMessageService = new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner()); } // 14. Cleanup pending message threads started this.consumeMessageService.start(); // 15. Register (cache) consumer to guarantee CID singleton boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown()); throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } /** * 16. Start MQClientInstance Consumer Pull-Off Interest Core Method * The PullMessageService and RebalanceService threads are started */ mQClientFactory.start(); log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup()); // 17. Set consumer client status to running state this.serviceState = ServiceState.RUNNING; break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The PushConsumer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } // Update topic routing and subscription information from NameServer this.updateTopicSubscribeInfoWhenSubscriptionChanged(); this.mQClientFactory.checkClientInBroker(); // Send a heartbeat, synchronize consumer configuration to broker, synchronize FilterClass to FilterServer(PushConsumer) this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); this.mQClientFactory.rebalanceImmediately(); }
The code above has detailed comments, so let's take a closer look here
- Check client status first
private volatile ServiceState serviceState = ServiceState.CREATE_JUST;
You can see that the initial state is CREATE_JUST goes into case CREATE_JUST code block, if RUNNING, START_FAILED, SHUTDOWN_ALREADY status throws an exception
Let's now analyze case CREATE_ Code in JUST
- First set the client state to Start Failure: START_FAILED
- Detect configuration parameters and throw exceptions if there are empty configurations or configuration errors
- Get subscription messages from top
this.copySubscription();
private void copySubscription() throws MQClientException { try { /** * key : topic * value: sub expression * Remove Subscription Relationship Table */ Map<String, String> sub = this.defaultMQPushConsumer.getSubscription(); if (sub != null) { for (final Map.Entry<String, String> entry : sub.entrySet()) { final String topic = entry.getKey(); final String subString = entry.getValue(); // Building SubscriptionData mainly includes some properties of the subscription such as SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(), topic, subString); // Save the assembled subscription relationship in rebalanceImpl this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); } } // Register listener if (null == this.messageListenerInner) { this.messageListenerInner = this.defaultMQPushConsumer.getMessageListener(); } switch (this.defaultMQPushConsumer.getMessageModel()) { case BROADCASTING: break; case CLUSTERING: /** * If it is cluster mode, retry topic to join subscriptionInner. * Message retry is in consumer group, top is%RETRY%+consumer group name */ final String retryTopic = MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()); SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(), retryTopic, SubscriptionData.SUB_ALL); this.rebalanceImpl.getSubscriptionInner().put(retryTopic, subscriptionData); break; default: break; } } catch (Exception e) { throw new MQClientException("subscription exception", e); } }
- Cluster mode sets process id to consumer instance name otherwise uses pid
public void changeInstanceNameToPID() { if (this.instanceName.equals("DEFAULT")) { this.instanceName = String.valueOf(UtilAll.getPid()); } }
- Create mqClient cache client and topic information, each service has only one instance of a process, let's go to getOrCreateMQClientInstance method to see
private ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable = new ConcurrentHashMap<String, MQClientInstance>(); public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { String clientId = clientConfig.buildMQClientId(); MQClientInstance instance = this.factoryTable.get(clientId); if (null == instance) { instance = new MQClientInstance(clientConfig.cloneClientConfig(), this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance); if (prev != null) { instance = prev; log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); } else { log.info("Created new MQClientInstance for clientId:[{}]", clientId); } } return instance; }
- Set up consumer groups
- Set up consumption mode subcluster mode and broadcast mode
- Set default avg for load balancing algorithm
- Set up mq client
- PullRequest encapsulates the implementation class, encapsulates the communication interface with the broker, which is then used to cancel messages from the broker
- Set Hook Function for Message Called by Client Filter
- Set Message Consumption Progress Memory
- Loading consumption progress is remote and local. Loading the current remote implementation class locally through a file is an empty method. Only local loading has a specific implementation. Let's look at the implementation of load in LocalFileOffsetStore
//Cached table with MessageQueue as key-consumption offset value private ConcurrentMap<MessageQueue, AtomicLong> offsetTable = new ConcurrentHashMap<MessageQueue, AtomicLong>(); public void load() throws MQClientException { //Read the json file from the local disk - serialize and encapsulate it into a map OffsetSerializeWrapper offsetSerializeWrapper = this.readLocalOffset(); if (offsetSerializeWrapper != null && offsetSerializeWrapper.getOffsetTable() != null) { //Save in Cache Table offsetTable.putAll(offsetSerializeWrapper.getOffsetTable()); for (MessageQueue mq : offsetSerializeWrapper.getOffsetTable().keySet()) { AtomicLong offset = offsetSerializeWrapper.getOffsetTable().get(mq); log.info("load consumer's offset, {} {} {}", this.groupName, mq, offset.get()); } } }
- There are two implementation classes for building consumer service consumeMessageService interface: MessageListenerOrderly handles sequential consumption, and MessageListenerConcurrently handles concurrent consumption
- Thread messages that start cleanup of expired messages expire in 15 minutes, cleanup once in 15 minutes, and start with a 15-minute delay. Let's take a look at the start method in ConsumeMessageConcurrentlyService
public void start() { this.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { @Override public void run() { // Clean up exception messages cleanExpireMsg(); } }, this.defaultMQPushConsumer.getConsumeTimeout(), this.defaultMQPushConsumer.getConsumeTimeout(), TimeUnit.MINUTES); }
- Register (cache) consumer, guarantee CID singleton, mQClientFactory is created in 5 steps, let's take a look at the registerConsumer method
private final ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>(); public boolean registerConsumer(final String group, final MQConsumerInner consumer) { if (null == group || null == consumer) { return false; } MQConsumerInner prev = this.consumerTable.putIfAbsent(group, consumer); if (prev != null) { log.warn("the consumer group[" + group + "] exist already."); return false; } return true; }
You can see that just putting consumer in consumerTable, not registering with nameServer
- Starting the MQClientInstance consumer pull-out core method starts the PullMessageService thread and the RebalanceService thread, and we enter the mQClientFactory start method
public void start() throws MQClientException { synchronized (this) { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; // If not specified,looking address from name server // 1. Get the namesrvAddr address if (null == this.clientConfig.getNamesrvAddr()) { this.mQClientAPIImpl.fetchNameServerAddr(); } /** * 2. Start request-response channel netty Related Network Request Encapsulation * Start a netty client */ this.mQClientAPIImpl.start(); // 3. Start various schedule tasks to start some timers this.startScheduledTask(); // 4. Start pull service /** * The core method opens the pull-and-cancel message thread, and the method logic is in this class's run method @{link PullMessageService run method} */ this.pullMessageService.start(); // 5. Start rebalance service this.rebalanceService.start(); // 6. Start push service sends a message requiring re-entry this.defaultMQProducer.getDefaultMQProducerImpl().start(false); log.info("the client factory [{}] start OK", this.clientId); // 7. Set serviceState status to running state this.serviceState = ServiceState.RUNNING; break; case START_FAILED: throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null); default: break; } } }
The comment above is more detailed, let's analyze which timers 3 started
private void startScheduledTask() { // Try to get NameServer address every 2 minutes if (null == this.clientConfig.getNamesrvAddr()) { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); } catch (Exception e) { log.error("ScheduledTask fetchNameServerAddr exception", e); } } }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); } // Attempt to update subject routing information by default every 30S this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { MQClientInstance.this.updateTopicRouteInfoFromNameServer(); } catch (Exception e) { log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e); } } }, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS); // Broker heartbeat detection every 30S this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { MQClientInstance.this.cleanOfflineBroker(); MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); } catch (Exception e) { log.error("ScheduledTask sendHeartbeatToAllBroker exception", e); } } }, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS); // //Persist ConsumeOffset every 5 seconds by default this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { MQClientInstance.this.persistAllConsumerOffset(); } catch (Exception e) { log.error("ScheduledTask persistAllConsumerOffset exception", e); } } }, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS); //Check thread pool adapters by default every 1S this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { MQClientInstance.this.adjustThreadPool(); } catch (Exception e) { log.error("ScheduledTask adjustThreadPool exception", e); } } }, 1, 1, TimeUnit.MINUTES); }
Then we'll come back and focus on how to pull the interest rate off.
// run method implementation to start thread thread in PullMessageService this.pullMessageService.start(); private final LinkedBlockingQueue<PullRequest> pullRequestQueue = new LinkedBlockingQueue<PullRequest>(); public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { // Pull Request from LinkedBlockingQueue Blocks waiting if queue is empty PullRequest pullRequest = this.pullRequestQueue.take(); this.pullMessage(pullRequest); } catch (InterruptedException ignored) { } catch (Exception e) { log.error("Pull Message Service Run Method exception", e); } } log.info(this.getServiceName() + " service end"); }
You can see that PullRequest is obtained primarily from pullRequestQueue and consumed messages. So you can determine where the pullRequestQueue put value to is the true implementation of message pulling
Then let's focus on how the pullRequestQueue queue is put into the data, and look at the call chain discovery that the main method calls are
PullRequest pullRequest = this.pullRequestQueue.take();
You can see two places, so let's look at these two approaches together
// DefaultMQPushConsumerImpl.class public void executePullRequestImmediately(final PullRequest pullRequest) { this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest); } // PullMessageService.class public void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { if (!isStopped()) { this.scheduledExecutorService.schedule(new Runnable() { @Override public void run() { PullMessageService.this.executePullRequestImmediately(pullRequest); } }, timeDelay, TimeUnit.MILLISECONDS); } else { log.warn("PullMessageServiceScheduledThread has shutdown"); } }
You can see that the executePullRequestImmediatelymethod is to put the pullRequest immediately, the executePullRequestLater method is to delay putting the pullRequest, when to delay putting the pullRequest, when to put the pullRequest immediately in case of an exception, and when to put it in immediately. Let's look at the call chain of this method executePullRequestImmediately:
There are two places to call
- pullMessage method in DefaultMQPushConsumerImpl.class
- ```dispatchPullRequest method in RebalancePushImpl.class, which is load balancing for message queues
Pull Client Message
So it's clear that the pullMessage` method in DefaultMQPushConsumerImpl.class is the core method of message pulling. Let's look at this method
public void pullMessage(final PullRequest pullRequest) { // 1. Get processing queue ProcessQueue final ProcessQueue processQueue = pullRequest.getProcessQueue(); // 2. If drop=true, return if (processQueue.isDropped()) { log.info("the pull request[{}] is dropped.", pullRequest.toString()); return; } //3. Then update the last time the message queue was pulled pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis()); try { //4. If the consumer service state is not ServiceState.RUNNING, the default delay is 3 seconds before execution this.makeSureStateOK(); } catch (MQClientException e) { log.warn("pullMessage exception, consumer state not ok", e); //4.1 Delayed pullRequest operation this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); return; } //5. Whether to pause or not, if there is a delay of 3s execution, I currently do not find any call pauses, which may be reserved for later if (this.isPause()) { log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup()); this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND); return; } //6. Message pulling has traffic control, which is triggered when the number of processQueue messages not consumed reaches (default 1000) long cachedMessageCount = processQueue.getMsgCount().get(); long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024); if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) { //PullRequest is delayed by 50 ms and placed in LinkedBlockQueue, printing warnings every 1000 times triggered this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "the cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", this.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); } return; } //7. Trigger flow control when the total size of message bodies not consumed in processQueue is larger than (default 100m), if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) { this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); if ((queueFlowControlTimes++ % 1000) == 0) { log.warn( "the cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", this.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); } return; } //8. If it is not a sequential message, the maximum spacing between messages in processQueue is determined to be the difference between the maximum and minimum positions of messages. If the difference is greater than the default value of 2000, flow control is triggered if (!this.consumeOrderly) { if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) { this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) { log.warn( "the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}", processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(), pullRequest, queueMaxSpanFlowControlTimes); } return; } } else { if (processQueue.isLocked()) { if (!pullRequest.isLockedFirst()) { final long offset = this.rebalanceImpl.computePullFromWhere(pullRequest.getMessageQueue()); boolean brokerBusy = offset < pullRequest.getNextOffset(); log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}", pullRequest, offset, brokerBusy); if (brokerBusy) { log.info("[NOTIFYME]the first time to pull message, but pull request offset larger than broker consume offset. pullRequest: {} NewOffset: {}", pullRequest, offset); } pullRequest.setLockedFirst(true); pullRequest.setNextOffset(offset); } } else { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); log.info("pull message later because not locked in broker, {}", pullRequest); return; } } //9. Get Subscription Information for Themes final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic()); if (null == subscriptionData) { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); log.warn("find the consumer's subscription failed, {}", pullRequest); return; } final long beginTimestamp = System.currentTimeMillis(); //10.new A callback method that will be called after pulling a message on the broker side PullCallback pullCallback = new PullCallback() { @Override public void onSuccess(PullResult pullResult) { if (pullResult != null) { pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult, subscriptionData); switch (pullResult.getPullStatus()) { case FOUND: long prevRequestOffset = pullRequest.getNextOffset(); pullRequest.setNextOffset(pullResult.getNextBeginOffset()); long pullRT = System.currentTimeMillis() - beginTimestamp; DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(), pullRT); long firstMsgOffset = Long.MAX_VALUE; if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) { DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); } else { firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset(); DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size()); boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList()); DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest( pullResult.getMsgFoundList(), processQueue, pullRequest.getMessageQueue(), dispatchToConsume); if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) { DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval()); } else { DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); } } if (pullResult.getNextBeginOffset() < prevRequestOffset || firstMsgOffset < prevRequestOffset) { log.warn( "[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}", pullResult.getNextBeginOffset(), firstMsgOffset, prevRequestOffset); } break; case NO_NEW_MSG: case NO_MATCHED_MSG: pullRequest.setNextOffset(pullResult.getNextBeginOffset()); DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest); DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); break; case OFFSET_ILLEGAL: log.warn("the pull request offset illegal, {} {}", pullRequest.toString(), pullResult.toString()); pullRequest.setNextOffset(pullResult.getNextBeginOffset()); pullRequest.getProcessQueue().setDropped(true); DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() { @Override public void run() { try { DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(), pullRequest.getNextOffset(), false); DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue()); DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue()); log.warn("fix the pull request offset, {}", pullRequest); } catch (Throwable e) { log.error("executeTaskLater Exception", e); } } }, 10000); break; default: break; } } }
The source code here is longer, so let's focus on the core method of message pulling
// Enter this.pullAPIWrapper.pullKernelImpl method public PullResult pullKernelImpl( final MessageQueue mq, final String subExpression, final String expressionType, final long subVersion, final long offset, final int maxNums, final int sysFlag, final long commitOffset, final long brokerSuspendMaxTimeMillis, final long timeoutMillis, final CommunicationMode communicationMode, final PullCallback pullCallback ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException { //15. Find Broker Information FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), this.recalculatePullFromWhichNode(mq), false); //16. If no broker is found, retrieve the information from the nameServer if (null == findBrokerResult) { this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic()); findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), this.recalculatePullFromWhichNode(mq), false); } if (findBrokerResult != null) { { // check version if (!ExpressionType.isTagType(expressionType) && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) { throw new MQClientException("The broker[" + mq.getBrokerName() + ", " + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null); } } int sysFlagInner = sysFlag; // Is it from broker if (findBrokerResult.isSlave()) { sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner); } // Build message request header PullMessageRequestHeader requestHeader = new PullMessageRequestHeader(); requestHeader.setConsumerGroup(this.consumerGroup); requestHeader.setTopic(mq.getTopic()); requestHeader.setQueueId(mq.getQueueId()); requestHeader.setQueueOffset(offset); requestHeader.setMaxMsgNums(maxNums); requestHeader.setSysFlag(sysFlagInner); requestHeader.setCommitOffset(commitOffset); requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis); requestHeader.setSubscription(subExpression); requestHeader.setSubVersion(subVersion); requestHeader.setExpressionType(expressionType); // Get broker address String brokerAddr = findBrokerResult.getBrokerAddr(); if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) { brokerAddr = computePullFromWhichFilterServer(mq.getTopic(), brokerAddr); } /** * True Message Pull Implements Class Important Methods */ PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage( brokerAddr, requestHeader, timeoutMillis, communicationMode, pullCallback); return pullResult; } throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null); }
The first few are building some request headers, let's not look at them, but focus on this.mQClientFactory.getMQClientAPIImpl().pullMessage method, which is the real message fetching
public PullResult pullMessage( final String addr, final PullMessageRequestHeader requestHeader, final long timeoutMillis, final CommunicationMode communicationMode, final PullCallback pullCallback ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader); switch (communicationMode) { case ONEWAY: assert false; return null; // Asynchronous Pull Cancel Interest case ASYNC: this.pullMessageAsync(addr, request, timeoutMillis, pullCallback); return null; // Synchronize pull-out messages case SYNC: return this.pullMessageSync(addr, request, timeoutMillis); default: assert false; break; } return null; }
Here we look at the implementation of asynchronous message fetching
private void pullMessageAsync( final String addr, final RemotingCommand request, final long timeoutMillis, final PullCallback pullCallback ) throws RemotingException, InterruptedException { this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { RemotingCommand response = responseFuture.getResponseCommand(); if (response != null) { try { PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr); assert pullResult != null; pullCallback.onSuccess(pullResult); } catch (Exception e) { pullCallback.onException(e); } } else { if (!responseFuture.isSendRequestOK()) { pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause())); } else if (responseFuture.isTimeout()) { pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request, responseFuture.getCause())); } else { pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause())); } } } }); }
You can see that the interest rate is pulled based on Netty. Source is a bit too many, just stayed there, and then there is time to continue to analyze the source of consumer consumption messages, where consumers pull out the source of interest is almost analyzed
Source address with comment
Continuous updates
https://github.com/weihubeats/rocketmq
Reference resources
About me
I think the article is good. Please pay attention to me by scanning the code