第十六章-消费者-PUSH方式(二)-消息拉取

16.2 触发拉取消息动作

MQ的消费模式可以大致分为两种,一种是推Push,一种是拉Pull。

  • Push是服务端主动推送消息给客户端,优点是及时性较好,但如果客户端没有做好流控,一旦服务端推送大量消息到客户端时,就会导致客户端消息堆积甚至崩溃。
  • Pull是客户端需要主动到服务端取数据,优点是客户端可以依据自己的消费能力进行消费,但拉取的频率也需要用户自己控制,拉取频繁容易造成服务端和客户端的压力,拉取间隔长又容易造成消费不及时。

RocketMQ既提供了Push模式也提供了Pull模式。但是这里的Push模式是阉割版的,并不是上面介绍的服务端主动推送,而是客户端处理的推送,这个怎么理解呢?实际还是客户端发起Pull消息的动作,成功后再回调方法来完成。要想把两个Push消息从获取到完成的整条链路贯穿,得先了解以下几个服务:PullMessageService、RebalanceService,这两个服务的启动都在章节16.1中启动了,现在顺藤摸瓜来看看瓜在哪里。

this.pullMessageService.start();

PullMessageService 服务先启动,看看源码

public void run() {
    log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        try {
			// pullRequestQueue 是一个BlockingQueue,阻塞队列,这个任务就是从这个队列中取任务来执行,这里就存在一个问题,谁给队列中放入的任务。现在一头雾水,不妨再看看 RebalanceService
            PullRequest pullRequest = this.pullRequestQueue.take();
            // 执行消息拉取的任务,看`章节16.3`
            this.pullMessage(pullRequest);
        } catch (InterruptedException ignored) {
        } catch (Exception e) {
            log.error("Pull Message Service Run Method exception", e);
        }
    }

    log.info(this.getServiceName() + " service end");
}

this.rebalanceService.start();

RebalanceService 服务启动,源码如下:

public void run() {
    log.info(this.getServiceName() + " service started");

    while (!this.isStopped()) {
        // 等候 waitInterval 时间后运行
        this.waitForRunning(waitInterval);
        // 负载均衡处理,继续往下看
        this.mqClientFactory.doRebalance();
    }

    log.info(this.getServiceName() + " service end");
}

MQClientInstance.doRebalance

public void doRebalance() {
    // consumerTable 存放的是消费者组对应的 MQClientInstance 实例,一个消费者组在一个客户端内,只会有一个消费实例对象
    for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
        MQConsumerInner impl = entry.getValue();
        if (impl != null) {
            try {
                // 这里直接看 DefaultMQPushConsumerImpl 的实现,另外 DefaultMQPullConsumerImpl 的实现会在Pull模式中讲
                impl.doRebalance();
            } catch (Throwable e) {
                log.error("doRebalance exception", e);
            }
        }
    }
}

DefaultMQPushConsumerImpl.doRebalance

public void doRebalance() {
    if (!this.pause) {
        // 这里又跳转到 RebalanceImpl 类实现了
        this.rebalanceImpl.doRebalance(this.isConsumeOrderly());
    }
}

RebalanceImpl.doRebalance

public void doRebalance(final boolean isOrder) {
    //这是订阅数据,在前面示例中有一行代码:consumer.subscribe("TopicTest", "*"),订阅一个或多个topic,并指定tag过滤条件,这里指定*表示接收所有tag的消息
    Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
    if (subTable != null) {
        for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
            final String topic = entry.getKey();
            try {
                // 按topic负载,其实就是决定当前客户端消费哪些队列
                this.rebalanceByTopic(topic, isOrder);
            } catch (Throwable e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("rebalanceByTopic Exception", e);
                }
            }
        }
    }

    this.truncateMessageQueueNotMyTopic();
}

RebalanceImpl.rebalanceByTopic

private void rebalanceByTopic(final String topic, final boolean isOrder) {
    switch (messageModel) {
        case BROADCASTING: { // 广播模式,忽略
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            if (mqSet != null) {
                boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
                if (changed) {
                    this.messageQueueChanged(topic, mqSet, mqSet);
                    log.info("messageQueueChanged {} {} {} {}",
                             consumerGroup,
                             topic,
                             mqSet,
                             mqSet);
                }
            } else {
                log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
            }
            break;
        }
        case CLUSTERING: { // 集群模式
            // topicSubscribeInfoTable 存放的是topic对应的消息队列集合,从Namesrv中获取
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            // cidAll 存放的是topic对应的消息者集合,从Namesrv中获取
            List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
            if (null == mqSet) { // 消息队列肯定不能为空
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                }
            }

            if (null == cidAll) { // 消费者也不能为空
                log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
            }
			// 两者都不为空
            if (mqSet != null && cidAll != null) {
                // 复制一份,不破坏原值
                List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
                mqAll.addAll(mqSet);
				// 排个序
                Collections.sort(mqAll);
                Collections.sort(cidAll);
				// 队列分配策略,默认是平均分配
                AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;

                List<MessageQueue> allocateResult = null;
                try {
                    // 分配后,得到该消费者客户端可以消费的队列集合
                    allocateResult = strategy.allocate(
                        this.consumerGroup,
                        this.mQClientFactory.getClientId(),
                        mqAll,
                        cidAll);
                } catch (Throwable e) {
                    log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
                              e);
                    return;
                }
				// 去重
                Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
                if (allocateResult != null) {
                    allocateResultSet.addAll(allocateResult);
                }
				/*
				这个方法主要干了以下几件事:
				1.创建ProcessQueue,并将其与MessageQueue关联起来形成mq-pq的k->v对存储在内存中
				2.剔除无效的mq
				3.剔除过时的mq
				以上三种情况,任何一种成立,change都会为true
				*/
                boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                if (changed) {
                    log.info(
                        "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
                        strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
                        allocateResultSet.size(), allocateResultSet);
                    // mq改变后,要更新本地缓存,同时发送心跳告知broker
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }
            }
            break;
        }
        default:
            break;
    }
}

RebalanceImpl.updateProcessQueueTableInRebalance

private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
                                                   final boolean isOrder) {
    boolean changed = false;
	// 先遍历 processQueueTable 主要看看里面的mq是否在要用的mqSet里面,不在就从 processQueueTable 移除
    Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<MessageQueue, ProcessQueue> next = it.next();
        MessageQueue mq = next.getKey();
        ProcessQueue pq = next.getValue();

        if (mq.getTopic().equals(topic)) {
            if (!mqSet.contains(mq)) { // 不在
                pq.setDropped(true);
                // 移除
                if (this.removeUnnecessaryMessageQueue(mq, pq)) {
                    it.remove();
                    changed = true;
                    log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq);
                }
            } else if (pq.isPullExpired()) { // 拉取过时,即:与上一次拉取时间超过2分钟
                switch (this.consumeType()) {
                    case CONSUME_ACTIVELY: // PULL方式
                        break;
                    case CONSUME_PASSIVELY: // PUSH方式
                        pq.setDropped(true);
                        // 移除
                        if (this.removeUnnecessaryMessageQueue(mq, pq)) {
                            it.remove();
                            changed = true;
                            log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it",
                                      consumerGroup, mq);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }
	// 默认情况下 processQueueTable 是没有数据的,也就是说上面的那段不会执行。下面的逻辑其实就是新增
    List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
    for (MessageQueue mq : mqSet) {
        if (!this.processQueueTable.containsKey(mq)) {
            if (isOrder && !this.lock(mq)) {
                log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
                continue;
            }
			// 因为是创建,所以这里要先重置 mq 对应的消费偏移值
            this.removeDirtyOffset(mq);
            // 创建 pq 对象
            ProcessQueue pq = new ProcessQueue();
            // 计算从哪个偏移开始拉取,默认都是0
            long nextOffset = this.computePullFromWhere(mq);
            if (nextOffset >= 0) {
                // 把 mq->pq的对应关系放到 processQueueTable map 中
                ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
                if (pre != null) { // map 中原来就有,证明拉取请求已经有了,不重复
                    log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
                } else {
                    log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
                    // map 中没有,则新建一个拉取请求,这里到关键时候了,这里创建的就是拉取消息的请求,然后由拉取消息的服务去处理,由谁处理呢,继续看下面的 dispatchPullRequest 方法
                    PullRequest pullRequest = new PullRequest();
                    pullRequest.setConsumerGroup(consumerGroup);
                    pullRequest.setNextOffset(nextOffset);
                    pullRequest.setMessageQueue(mq);
                    pullRequest.setProcessQueue(pq);
                    pullRequestList.add(pullRequest);
                    changed = true;
                }
            } else {
                log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
            }
        }
    }

    // 分发请求到push或pull,这里先看push方式
    this.dispatchPullRequest(pullRequestList);

    return changed;
}

RebalancePushImpl.dispatchPullRequest

public void dispatchPullRequest(List<PullRequest> pullRequestList) {
    for (PullRequest pullRequest : pullRequestList) {//遍历拉取请求
        // 继续执行拉取动作
        this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
        log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
    }
}

DefaultMQPushConsumerImpl.executePullRequestImmediately

public void executePullRequestImmediately(final PullRequest pullRequest) {
    // 这一步才算是真正接触到拉取服务 PullMessageService 了
    this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest);
}

PullMessageService.executePullRequestImmediately

public void executePullRequestImmediately(final PullRequest pullRequest) {
    try {
        // 终于走到这一行了,把拉取请求放入阻塞队列,本节的一开始就讲了这个阻塞队列,在pullMessageService.start() 方法中,现在这个方法可以开始执行内部拉取任务了,消息的拉取和处理看`章节16.3`
        this.pullRequestQueue.put(pullRequest);
    } catch (InterruptedException e) {
        log.error("executePullRequestImmediately pullRequestQueue.put", e);
    }
}

16.3 消息拉取准备

PullMessageService.pullMessage

private void pullMessage(final PullRequest pullRequest) {
    // 消费组对应的客户端实例,在章节`16.1`中有介绍
    final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
    if (consumer != null) {
        DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
        // 继续调用
        impl.pullMessage(pullRequest);
    } else {
        log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
    }
}

DefaultMQPushConsumerImpl.pullMessage

public void pullMessage(final PullRequest pullRequest) {
    final ProcessQueue processQueue = pullRequest.getProcessQueue();
    // 检验pq状态
    if (processQueue.isDropped()) {
        log.info("the pull request[{}] is dropped.", pullRequest.toString());
        return;
    }
	// 设置最后拉取时间戳为当前时间戳
    pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());

    try {
        // 服务状态检查
        this.makeSureStateOK();
    } catch (MQClientException e) {
        log.warn("pullMessage exception, consumer state not ok", e);
        // 失败了,就过一段时间继续执行
        this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
        return;
    }

    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;
    }
	// 本地缓存中(未被消费)已缓存的消息条数
    long cachedMessageCount = processQueue.getMsgCount().get();
    // 本地缓存中(未被消费)已缓存的消息占用大小M
    long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024);
	// 缓存消息数不能大于默认1000
    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;
    }
	// 缓存消息大小不能大于默认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;
    }
	// 非顺序消息,也就是并发模式
    if (!this.consumeOrderly) {
        // 消费偏移最大跨度不能超过默认值2000
        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, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
            log.info("pull message later because not locked in broker, {}", pullRequest);
            return;
        }
    }
	// topic对应的订阅数据
    final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
    if (null == subscriptionData) { // 订阅数据肯定不能是空的
        this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
        log.warn("find the consumer's subscription failed, {}", pullRequest);
        return;
    }
	// 记录开始时间
    final long beginTimestamp = System.currentTimeMillis();
	// 因为RocketMQ的PUSH是假PUSH,实际只是客户端通过异步调用模拟实现的PUSH,所以这里需要有一个回调类,并分别实现成功和失败回调
    PullCallback pullCallback = new PullCallback() {
        @Override
        public void onSuccess(PullResult pullResult) {
            // 内容先省略,等消息处理章节中再讲
        }

        @Override
        public void onException(Throwable e) {
             // 内容先省略,等消息处理章节中再讲
        }
    };

    boolean commitOffsetEnable = false;
    long commitOffsetValue = 0L;
    if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
        // 缓存中读取已提交的消费偏移值
        commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);
        if (commitOffsetValue > 0) {
            commitOffsetEnable = true;
        }
    }

    String subExpression = null;
    boolean classFilter = false;
    SubscriptionData sd = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
    if (sd != null) {
        if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
            subExpression = sd.getSubString();
        }
     	// 过滤模式,忽略
        classFilter = sd.isClassFilterMode();
    }
	// 标识
    int sysFlag = PullSysFlag.buildSysFlag(
        commitOffsetEnable, // commitOffset
        true, // suspend
        subExpression != null, // subscription
        classFilter // class filter
    );
    try {
        // 真正开始拉取消息的方法,下一节讲
        this.pullAPIWrapper.pullKernelImpl(
            pullRequest.getMessageQueue(),
            subExpression,
            subscriptionData.getExpressionType(),
            subscriptionData.getSubVersion(),
            pullRequest.getNextOffset(),
            this.defaultMQPushConsumer.getPullBatchSize(),
            sysFlag,
            commitOffsetValue,
            BROKER_SUSPEND_MAX_TIME_MILLIS,
            CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
            CommunicationMode.ASYNC, // 这一行关注一下,表示通信模式为异步(PUSH)就是用这种
            pullCallback
        );
    } catch (Exception e) {
        log.error("pullKernelImpl exception", e);
        this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
    }
}

16.4 消息拉取

pullAPIWrapper.pullKernelImpl

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 {
    // 要拉取消息,得从broker中拉,所以先要查找要用的broker
    FindBrokerResult findBrokerResult =
        this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                                                          this.recalculatePullFromWhichNode(mq), false);
    if (null == findBrokerResult) {
        // 没查到,就再从 namesrv 中找
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
        findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                                                              this.recalculatePullFromWhichNode(mq), false);
    }

    if (findBrokerResult != null) {
        {
            // 版本检查
            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;
		//  如果是从节点,就要清空 commitOffsetFlag 标志 为0
        if (findBrokerResult.isSlave()) {
            sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
        }
		// 组装拉取消息请求
        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);

        String brokerAddr = findBrokerResult.getBrokerAddr();
        if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
            brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
        }
		// 拉取(唉,这个拉取操作层次太深了,还得往里走,Java代码就是这样,层次太深,所以源码看起来费劲,相比之下C/C++的源码没这么多层),继续往里看吧
        PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
            brokerAddr,
            requestHeader,
            timeoutMillis,
            communicationMode,
            pullCallback);

        return pullResult;
    }

    throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}

MQClientAPIImpl.pullMessage

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;
        case ASYNC: // PUSH模式是异步的,所以看这里
            this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
            return null;
        case SYNC: // 这个是PULL模式用的,后续章节会讲
            return this.pullMessageSync(addr, request, timeoutMillis);
        default:
            assert false;
            break;
    }

    return null;
}

MQClientAPIImpl.pullMessageAsync

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 {
                    // 处理结果,细节后面也会讲,看`章节16.5`
                    PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);
                    assert pullResult != null;
                    // pullCallback 也是一个回调,在`章节16.3`中有描述,具体细节,后面也会讲,看`章节16.5`
                    pullCallback.onSuccess(pullResult);
                } catch (Exception e) {
                    pullCallback.onException(e);
                }
            } else {
                if (!responseFuture.isSendRequestOK()) {
                    // pullCallback 也是一个回调,在`章节16.3`中有描述,具体细节,后面也会讲,看`章节16.5`
                    pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                } else if (responseFuture.isTimeout()) {
                    // pullCallback 也是一个回调,在`章节16.3`中有描述,具体细节,后面也会讲,看`章节16.5`
                    pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
                                                                   responseFuture.getCause()));
                } else {
                    // pullCallback 也是一个回调,在`章节16.3`中有描述,具体细节,后面也会讲,看`章节16.5`
                    pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
                }
            }
        }
    });
}

NettyRemotingClient.invokeAsync

public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback)
    throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException,
RemotingSendRequestException {
    // 记录当前时间
    long beginStartTime = System.currentTimeMillis();
    // netty Channel
    final Channel channel = this.getAndCreateChannel(addr);
    if (channel != null && channel.isActive()) {
        try {
            // 调用前钩子,想像成拦截器,这是可以用户自定义扩展的
            doBeforeRpcHooks(addr, request);
            // 执行钩子时间差
            long costTime = System.currentTimeMillis() - beginStartTime;
            // 判断超时时间是否已超
            if (timeoutMillis < costTime) {
                throw new RemotingTooMuchRequestException("invokeAsync call timeout");
            }
            // 进一步调用
            this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, invokeCallback);
        } catch (RemotingSendRequestException e) {
            log.warn("invokeAsync: send request exception, so close the channel[{}]", addr);
            this.closeChannel(addr, channel);
            throw e;
        }
    } else {
        // 关闭channel
        this.closeChannel(addr, channel);
        throw new RemotingConnectException(addr);
    }
}

NettyRemotingClient.invokeAsyncImpl

public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
                            final InvokeCallback invokeCallback)
    throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    // 记录当前时间
    long beginStartTime = System.currentTimeMillis();
    // 唯一标识当前请求的id,防止因为网络或其他异常产生时请求重发,造成的重复处理/消费
    final int opaque = request.getOpaque();
    // 信号量获取,关于信号量的知识,可以看我另一篇文章`https://blog.csdn.net/zhang527294844/article/details/133925733`
    boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
    if (acquired) {
        final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
        long costTime = System.currentTimeMillis() - beginStartTime;
        // 继续做超时时间判断
        if (timeoutMillis < costTime) {
            once.release();
            throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
        }
		// 将响应处理封装成Future,并存放到 responseTable map 中,等待消息拉取成功后再处理
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
        this.responseTable.put(opaque, responseFuture);
        try {
            // 客户端channel(这里就是消费端),发送请求到服务端broker,并注册拉取后的监听
            channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                @Override
                // operationComplete 方法由谁调用呢,先猜测一下,应该是由channel收到拉取的消息后,再调用,后续再源码验证
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (f.isSuccess()) { // 拉取成功,设置发送状态成功标志
                        responseFuture.setSendRequestOK(true);
                        return;
                    }
                    // 请求失败处理
                    requestFail(opaque);
                    log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
                }
            });
        } catch (Exception e) {
            responseFuture.release();
            log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
            throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
        }
    } else {
        if (timeoutMillis <= 0) {
            throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
        } else {
            String info =
                String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
                              timeoutMillis,
                              this.semaphoreAsync.getQueueLength(),
                              this.semaphoreAsync.availablePermits()
                             );
            log.warn(info);
            throw new RemotingTimeoutException(info);
        }
    }
}

截止目前,终于把发送拉取消息的请求和动作讲完了,接下来就要看服务端返回消息后客户端接收后的一些操作,这就需要用到 NettyClientHandler 类,这个类在章节16.1中有介绍,可以回头看看。

class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        // 就这一行代码,继续看咯
        processMessageReceived(ctx, msg);
    }
}

NettyRemotingAbstract.processMessageReceived

public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
    final RemotingCommand cmd = msg;
    if (cmd != null) {
        switch (cmd.getType()) {
            case REQUEST_COMMAND:
                processRequestCommand(ctx, cmd);
                break;
            case RESPONSE_COMMAND: // 这里是处理服务端返回,所以是response
                processResponseCommand(ctx, cmd);
                break;
            default:
                break;
        }
    }
}

NettyRemotingAbstract.processResponseCommand

public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
    final int opaque = cmd.getOpaque();
    // 从缓存map responseTable 中取出本次请求opaque的待处理respose future 任务
    final ResponseFuture responseFuture = responseTable.get(opaque);
    if (responseFuture != null) {
        responseFuture.setResponseCommand(cmd);

        responseTable.remove(opaque);

        if (responseFuture.getInvokeCallback() != null) {
            // 回调
            executeInvokeCallback(responseFuture);
        } else {
            responseFuture.putResponse(cmd);
            responseFuture.release();
        }
    } else {
        log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
        log.warn(cmd.toString());
    }
}

NettyRemotingAbstract.executeInvokeCallback

private void executeInvokeCallback(final ResponseFuture responseFuture) {
    boolean runInThisThread = false;
    // 线程池
    ExecutorService executor = this.getCallbackExecutor();
    if (executor != null) {
        try {
            // 提交任务
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 执行回调
                        responseFuture.executeInvokeCallback();
                    } catch (Throwable e) {
                        log.warn("execute callback in executor exception, and callback throw", e);
                    } finally {
                        responseFuture.release();
                    }
                }
            });
        } catch (Exception e) {
            runInThisThread = true;
            log.warn("execute callback in executor exception, maybe executor busy", e);
        }
    } else {
        runInThisThread = true;
    }
	// 如果没有指定线程池,就用当前线程执行
    if (runInThisThread) {
        try {
            // 执行回调
            responseFuture.executeInvokeCallback();
        } catch (Throwable e) {
            log.warn("executeInvokeCallback Exception", e);
        } finally {
            responseFuture.release();
        }
    }
}

ResponseFuture.executeInvokeCallback

public void executeInvokeCallback() {
    if (invokeCallback != null) {
        if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) {
            // operationComplete 方法,一看就熟悉吧,前面有介绍
            invokeCallback.operationComplete(this);
        }
    }
}

好了,至此,拉取消息的整套动作也已完成,并已做了一些初始处理操作,将消息已存放到本地缓存了,接下来就是要如何把消息发送给消费者去消费,这个就下一节来讲。

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值