AliMQ(RocketMQ)源碼(六)MQClientInstance的start()方法

MQClientInstance的start()方法,客戶端的start()

                    // Start request-response channel
                    this.mQClientAPIImpl.start();
                    // Start various schedule tasks
                    this.startScheduledTask();
                    // Start pull service
                    this.pullMessageService.start();
                    // Start rebalance service
                    this.rebalanceService.start();
                    // Start push service
                    this.defaultMQProducer.getDefaultMQProducerImpl().start(false);

一、this.mQClientAPIImpl.start();

這個方法實則是NettyRemotingClient的start()方法,這個方法中先是創建了Netty客戶端,然後調用了兩個定時執行的任務:

        this.timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    NettyRemotingClient.this.scanResponseTable();
                } catch (Throwable e) {
                    log.error("scanResponseTable exception", e);
                }
            }
        }, 1000 * 3, 1000);

        this.timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    NettyRemotingClient.this.scanChannelTablesOfNameServer();
                } catch (Exception e) {
                    log.error("scanChannelTablesOfNameServer exception", e);
                }
            }
        }, 1000 * 3, 10 * 1000);

這兩個任務一個是刪除過期的請求,一個是將沒有連接響應的nameServer斷開連接。

二、this.startScheduledTask();

這個方法中執行了多個定時任務,包括:

  • 2分鐘更新一次nameServer的地址
  • 30秒更新一次topic的路由信息
  • 30秒對Broker發送一次心跳檢測,並將下線的broker刪除
  • 5秒持久化一次consumer的offset
  • 1分鐘調整一次線程池,這個定時任務其實什麼都沒有執行。

三、this.pullMessageService.start();

這個方法調用了PullMessageService的run()方法,裏面主要是調用了DefaultMQPushConsumerImpl.pullMessage方法,

pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());
  • 設置最後一次拉取的時間
this.makeSureStateOK();
  • 檢驗客戶端狀態是否正常
        long cachedMessageCount = processQueue.getMsgCount().get();
        long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024);

        if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
            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;
        }

        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;
        }
  • 查看已緩存的message的條數和message的內存大小,如果超過了設置的緩存值,就不拉取消息了。
pullCallback
  • 拉取消息後的回調處理
        if (sd != null) {
            if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
                subExpression = sd.getSubString();
                subProperties = sd.getPropertiesStr();
            }

            classFilter = sd.isClassFilterMode();
        }
  • 如果設置了isPostSubscriptionWhenPull=true,則以後拉取消息會帶上最新的訂閱信息
this.pullAPIWrapper.pullKernelImpl(
  • 真實的拉取消息

四、this.pullAPIWrapper.pullKernelImpl

        FindBrokerResult findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                this.recalculatePullFromWhichNode(mq), false);
  • 根據brokeName、隊列節點獲取拉取消息的broker地址
            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.setSubProperties(subProperties);
            requestHeader.setExpressionType(expressionType);
  • 設置請求的消息頭
            case ASYNC:
                this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
  • 拉取消息默認調用ASYNC的方法,如果拉取消息成功,會調用pullCallBack的OnSuccess和OnException方法

五、this.rebalanceService.start();

RebalanceImpl是consume的重新負載,什麼意思呢?就是消費者與消費隊列的對應關係,我們來思考一個問題,比如現在有4個消息隊列(q1,q2,q3,q4),3個消費者(m1,m2,m3),那麼消費者與消息隊列的對應關係是什麼呢?我們按照一個輪詢算法來表示, m1(q1,q4) m2(q2) m3(q3),如果此時q2消息隊列失效(所在的broker掛了),那麼消息隊列的消費就需要重新分配,RebalanceImpl就是幹這事的,該類的調用軌跡如下:(MQClientInstance start --> (this.rebalanceService.start()) —> RebalanceService.run(this.mqClientFactory.doRebalance()) —> MQConsumerInner.doRebalance(DefaultMQPushConsumerImpl) —>RebalanceImpl.doRebalance
在這裏着重說明一點:消息隊列數量與消費者關係:1個消費者可以消費多個隊列,但1個消息隊列只會被一個消費者消費;如果消費者數量大於消息隊列數量,則有的消費者會消費不到消息(集羣模式)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章