第十七章-消费者-PULL方式

17.1 准备阶段

官方示例

public class PullConsumerTest {
    public static void main(String[] args) throws MQClientException {
        // 初始化consumer,并设置consumer group name
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_5");
        // 设置NameServer地址 
        consumer.setNamesrvAddr("127.0.0.1:9876");
        // 启动Consumer
        consumer.start();
        try {
            // 设置要主动拉取的 mq
            MessageQueue mq = new MessageQueue();
            mq.setQueueId(0);
            mq.setTopic("TopicTest");
            mq.setBrokerName("jinrongtong-MacBook-Pro.local");
            long offset = 26;// 指定从哪个偏移值开始拉取
            // 拉取消息
            PullResult pullResult = consumer.pull(mq, "*", offset, 32);
            if (pullResult.getPullStatus().equals(PullStatus.FOUND)) {// 拉取成功
                System.out.printf("%s%n", pullResult.getMsgFoundList());
                // 自己更新拉取偏移
                consumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        consumer.shutdown();
    }
}

话不多说,更多的介绍,可以直接看官网,这直接上源码,那还是从 consumer.start() 这一行代码开始。

DefaultMQPullConsumer.start

@Override
public void start() throws MQClientException {
    // 通过命名空间处理一下消费者组
    this.setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
    // pull消费的实现类启动
    this.defaultMQPullConsumerImpl.start();
}

DefaultMQPullConsumerImpl.start

public synchronized void start() throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST: // 启动时,看这块
             // 先设置成失败的状态
            this.serviceState = ServiceState.START_FAILED;
			// 检查配置项
            this.checkConfig();
 			// 复制订阅信息,这里所做的事就是将订阅信息从 DefaultMQPushConsumerImpl 类的map 复制到 RebalanceImpl 类的map(rebalanceImpl.getSubscriptionInner())中,因为最终做消息处理的是 RebalanceImpl去与服务端打交道
            this.copySubscription();
			// 集群模式,实例名改成对应的进程Id
            if (this.defaultMQPullConsumer.getMessageModel() == MessageModel.CLUSTERING) {
                this.defaultMQPullConsumer.changeInstanceNameToPID();
            }
		   // 获取或创建MQClientInstance实例,这个类很重要,同时涵盖了生产者和消费者的操作,具体使用时会讲
            this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQPullConsumer, this.rpcHook);
		   //设置消费者组名
            this.rebalanceImpl.setConsumerGroup(this.defaultMQPullConsumer.getConsumerGroup());
            //设置消费模式
            this.rebalanceImpl.setMessageModel(this.defaultMQPullConsumer.getMessageModel());
            //设置分配mq策略(决定消费哪个mq的内容)
            this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPullConsumer.getAllocateMessageQueueStrategy());
            //设置mq客户端实例对象
            this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);
		   //创建实际拉取消息的对象PullAPIWrapper
            this.pullAPIWrapper = new PullAPIWrapper(
                mQClientFactory,
                this.defaultMQPullConsumer.getConsumerGroup(), isUnitMode());
            //注册消息过滤处理器列表
            this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);
			//先在本地缓存中获取offsetstore,如果没有就去远程broker获取,offsetstore其实就是存放消费者消费mq的偏移量,防止重复消费
            if (this.defaultMQPullConsumer.getOffsetStore() != null) {
                this.offsetStore = this.defaultMQPullConsumer.getOffsetStore();
            } else {
                switch (this.defaultMQPullConsumer.getMessageModel()) {
                    case BROADCASTING:// 广播模式,不做讲解
                        this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup());
                        break;
                    case CLUSTERING://集群模式
                        // 集群模式下,用RemoteBrokerOffsetStore对象来处理消费偏移
                        this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPullConsumer.getConsumerGroup());
                        break;
                    default:
                        break;
                }
                this.defaultMQPullConsumer.setOffsetStore(this.offsetStore);
            }
  			// 加载本机偏移信息,集群模式用的是 RemoteBrokerOffsetStore 类对象,默认load没有实现内容,这是一个钩子函数,子类可自行实现
            this.offsetStore.load();
			//将消费实例对象注册到本地内存consumerTable(ConcurrentMap)中,一个消费者组在一个客户端内,只会有一个消费实例对象
            boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPullConsumer.getConsumerGroup(), this);
            if (!registerOK) {
                //注册失败,恢复状态为创建,并向上抛出异常
                this.serviceState = ServiceState.CREATE_JUST;

                throw new MQClientException("The consumer group[" + this.defaultMQPullConsumer.getConsumerGroup()
                                            + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
                                            null);
            }
		   //上面都是一些检查和数据准备工作,这一步才是真正启动服务的工作,启动内部实现,这个在`章节16.1`的 MQClientInstance.start 方法中有讲,可以回头去看看
            mQClientFactory.start();
            log.info("the consumer [{}] start OK", this.defaultMQPullConsumer.getConsumerGroup());
            this.serviceState = ServiceState.RUNNING;
            break;
        case RUNNING:
        case START_FAILED:
        case SHUTDOWN_ALREADY:
            throw new MQClientException("The PullConsumer service state not OK, maybe started once, "
                                        + this.serviceState
                                        + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
                                        null);
        default:
            break;
    }

}

17.2 触发拉取消息动作

前面一顿操作后,对几个主要的服务(消息拉取、负载均衡等)做了启动,现在先顺着启动后的服务往下走,由于这一章主要讲PULL方式,所以要贯穿PULL消息从获取到完成的整条链路,也得先了解RebalanceService和PullMessageService两个服务。

PullMessageService.start()

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

    while (!this.isStopped()) {
        try {
			// pullRequestQueue 是一个BlockingQueue,阻塞队列,这个任务就是从这个队列中取任务来执行,这里就存在一个问题,谁给队列中放入的任务。现在一头雾水,不妨再看看 RebalanceService
            PullRequest pullRequest = this.pullRequestQueue.take();
            // 执行消息拉取的任务,看`章节17.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");
}

RebalanceService.start()

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 {
                // 这里直接看 DefaultMQPullConsumerImpl 的实现
                impl.doRebalance();
            } catch (Throwable e) {
                log.error("doRebalance exception", e);
            }
        }
    }
}

DefaultMQPullConsumerImpl.doRebalance

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

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,这里看pull方式
    this.dispatchPullRequest(pullRequestList);

    return changed;
}

RebalancePullImpl.dispatchPullRequest

从源码看,pull方式的dispatchPullRequest实现为空,这就证明走不下去了,那这条路就不通了,接下来怎么办呢?还是得从pull的概念来推导,pull意味着主动拉取,那既然是主动,肯定就不能靠几个服务启动后就解决了,必须得有一个用户发起的主动操作,回想一下,示例中有一行代码:

PullResult pullResult = consumer.pull(mq, “*”, offset, 32);

一看就明白,这个就是用户主动拉取的操作,接下来就是讲解这个动作的源码了。

public void dispatchPullRequest(List<PullRequest> pullRequestList) {
}

17.3 消息拉取

DefaultMQPullConsumer 有8个pull方法,供用户使用。

/**
  * mq  指定要从哪个cq拉取
  * subExpression 指定消息过滤tags
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
*/
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums);
}
/**
  * mq  指定要从哪个cq拉取
  * subExpression 指定消息过滤tags
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * timeout 拉取超时
*/
@Override
public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, timeout);
}
/**
  * mq  指定要从哪个cq拉取
  * messageSelector 指定消息选择器:tags或sql
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
*/
@Override
public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums);
}
/**
  * mq  指定要从哪个cq拉取
  * messageSelector 指定消息选择器:tags或sql
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * timeout 拉取超时
*/
@Override
public PullResult pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums, long timeout)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    return this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, timeout);
}
/**
  * mq  指定要从哪个cq拉取
  * messageSelector 指定消息选择器:tags或sql
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * pullCallback 拉取回调
*/
@Override
public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback)
    throws MQClientException, RemotingException, InterruptedException {
    this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback);
}
/**
  * mq  指定要从哪个cq拉取
  * subExpression 指定消息过滤tags
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * pullCallback 拉取回调
  * timeout 拉取超时
*/
@Override
public void pull(MessageQueue mq, String subExpression, long offset, int maxNums, PullCallback pullCallback,
                 long timeout)
    throws MQClientException, RemotingException, InterruptedException {
    this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), subExpression, offset, maxNums, pullCallback, timeout);
}
/**
  * mq  指定要从哪个cq拉取
  * messageSelector 指定消息选择器:tags或sql
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * pullCallback 拉取回调
*/
@Override
public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums,
                 PullCallback pullCallback)
    throws MQClientException, RemotingException, InterruptedException {
    this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback);
}
/**
  * mq  指定要从哪个cq拉取
  * messageSelector 指定消息选择器:tags或sql
  * offset 拉取起始偏移
  * maxNums 拉取的最大消息数
  * pullCallback 拉取回调
  * timeout 拉取超时
*/
@Override
public void pull(MessageQueue mq, MessageSelector messageSelector, long offset, int maxNums,
                 PullCallback pullCallback, long timeout)
    throws MQClientException, RemotingException, InterruptedException {
    this.defaultMQPullConsumerImpl.pull(queueWithNamespace(mq), messageSelector, offset, maxNums, pullCallback, timeout);
}

这么多pull方法,就不一一介绍了,选择一个最简单的,也就是第一个pull吧,继续往里探。

DefaultMQPullConsumerImpl.pull

public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 增加一个默认10秒的拉取超时参数
    return pull(mq, subExpression, offset, maxNums, this.defaultMQPullConsumer.getConsumerPullTimeoutMillis());
}

public PullResult pull(MessageQueue mq, String subExpression, long offset, int maxNums, long timeout)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 将topic、tags等封装成subscriptionData对象,供后续环节使用
    SubscriptionData subscriptionData = getSubscriptionData(mq, subExpression);
    // 继续调用 pullSyncImpl
    return this.pullSyncImpl(mq, subscriptionData, offset, maxNums, false, timeout);
}

DefaultMQPullConsumerImpl.pullSyncImpl

private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
                                long timeout)
    throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    this.makeSureStateOK();

    if (null == mq) { // mq 不能为空
        throw new MQClientException("mq is null", null);
    }

    if (offset < 0) { // offset肯定要大于等于0
        throw new MQClientException("offset < 0", null);
    }

    if (maxNums <= 0) {// maxNums肯定要大于0
        throw new MQClientException("maxNums <= 0", null);
    }
	// 自动订阅当前topic消息,此时tags是*,表示不做过滤
    this.subscriptionAutomatically(mq.getTopic());

    int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

    long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

    boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
    // 真正拉取的核心方法,这个方法在讲PUSH方式的时候已经写过了,大家可以直接回到`章节16.4`
    PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
        mq,
        subscriptionData.getSubString(),// tags值
        subscriptionData.getExpressionType(), // 对应类型,默认TAG,这里也只讲TAG
        isTagType ? 0L : subscriptionData.getSubVersion(),// 判断是否TAG类型
        offset, // 拉取起始偏移
        maxNums, // 拉取最大数
        sysFlag,
        0,
        // broker无响应最大超时,默认20s
        this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
        timeoutMillis, // 拉取超时
        CommunicationMode.SYNC, // 同步拉取
        null
    );
    // 拉取完成,处理拉取结果,这个在讲PUSH的时候也讲过,直接回头看`章节16.5`
    this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData);
    // 重置 topic 为无 namespace
    this.resetTopic(pullResult.getMsgFoundList());
    // 执行自定义hook
    if (!this.consumeMessageHookList.isEmpty()) {
        ConsumeMessageContext consumeMessageContext = null;
        consumeMessageContext = new ConsumeMessageContext();
        consumeMessageContext.setNamespace(defaultMQPullConsumer.getNamespace());
        consumeMessageContext.setConsumerGroup(this.groupName());
        consumeMessageContext.setMq(mq);
        consumeMessageContext.setMsgList(pullResult.getMsgFoundList());
        consumeMessageContext.setSuccess(false);
        this.executeHookBefore(consumeMessageContext);
        consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString());
        consumeMessageContext.setSuccess(true);
        this.executeHookAfter(consumeMessageContext);
    }
    return pullResult;
}

整个拉取操作与PUSH方式的章节16.4中大量重叠,但是在方法 MQClientAPIImpl.pullMessage 中分道扬镳。

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: // 同步拉取,现在需要讲的
            return this.pullMessageSync(addr, request, timeoutMillis);
        default:
            assert false;
            break;
    }

    return null;
}

MQClientAPIImpl.pullMessageSync

private PullResult pullMessageSync(
    final String addr,
    final RemotingCommand request,
    final long timeoutMillis
) throws RemotingException, InterruptedException, MQBrokerException {
    // 调用同步拉取方法
    RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
    assert response != null;
    // 处理拉取结果,这个方法内部很简单,就不单独拎出来写了
    return this.processPullResponse(response);
}

NettyRemotingClient.invokeSync

public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
    throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
    // 记录开始时间
    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 RemotingTimeoutException("invokeSync call timeout");
            }
            // 进一步调用同步方法
            RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
            // 调用前钩子,想像成拦截器,这是可以用户自定义扩展的
            doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
            return response; // 返回结果
        } catch (RemotingSendRequestException e) {
            log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
            this.closeChannel(addr, channel); // 产生异常,关闭channel资源
            throw e;
        } catch (RemotingTimeoutException e) {
            if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
                this.closeChannel(addr, channel);// 产生异常,关闭channel资源
                log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
            }
            log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
            throw e;
        }
    } else {
        // channel状态非激活,关闭channel资源,并往上抛出异常
        this.closeChannel(addr, channel);
        throw new RemotingConnectException(addr);
    }
}

NettyRemotingClient.invokeSyncImpl

public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
                                      final long timeoutMillis)
    throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    // 标记唯一请求id
    final int opaque = request.getOpaque();

    try {
        // 将响应处理封装成Future,并存放到 responseTable map 中,等待消息拉取成功后再处理
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
        this.responseTable.put(opaque, responseFuture);
        final SocketAddress addr = channel.remoteAddress();
        // 客户端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;
                } else {
                    // 拉取成功,设置发送状态失败标志
                    responseFuture.setSendRequestOK(false);
                }
			   // 因为是同步操作,所以处理完后,可以立马移除,这点跟push方式中的异步不一样
                responseTable.remove(opaque);
                // 设置失败原因
                responseFuture.setCause(f.cause());
                // 设置响应为null
                responseFuture.putResponse(null);
                log.warn("send a request command to channel <" + addr + "> failed.");
            }
        });
		// 等待拉取任务的结果
        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
        if (null == responseCommand) {// 超时/异常返回null
            // 这里为啥要判断 isSendRequestOK,才能断定是超时呢,因为默认 isSendRequestOK 就是 true
            if (responseFuture.isSendRequestOK()) {
                throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                                                   responseFuture.getCause());
            } else {
                throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
            }
        }
	    // 返回响应结果
        return responseCommand;
    } finally {
        // 请求完成,从map中移除该请求
        this.responseTable.remove(opaque);
    }
}

至此,PULL方式,就讲完了,是不是很简单,实际上在讲PUSH时,已经把一些公共方法都讲完了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

多栖码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值