RocketMQ源码解析——Consumer

目录:

了解消息消费者——Consumer:

消息消费以组的模式开展,一个消费组内可以包含多个消费者,每个消费者可以订阅多个主题,消费组之间有集群(负载均衡)模式和广播模式两种消费模式:

  • 集群(负载均衡)模式:主题下的同一条消息,只允许其中一个消息消费者进行消费
  • 广播模式:主题下的同一条消息,将被集群内所有的消费者消费一次

消息消费者和服务器之间的消息传递也有两种模式,推送模式、拉取模式:

  • 推送模式:当消息到达服务器之后,服务器推送消息给消息消费者(所谓的推送模式实际上是基于拉取模式实现的,在以下的源码分析会讲解到)
  • 拉取模式:当消息到达服务器之后,消息消费者主动向服务器拉取消息

常用的消息消费模式是推送模式,而推送模式中最核心的类是DefaultMQPushConsumer:

在这里插入图片描述
从图上可以了解到在DefaultMQPushConsumer之上还实现了两个核心父类接口:MQPushConsumer、MQConsumer。为了更深入了解DefaultMQPushConsumer这个类,先来看下它的父类接口有哪些重要的方法:

/**
* 获取监听指定主题的消息队列
*/
Set<MessageQueue> fetchSubscribeMessageQueues(final String topic) throws MQClientException;

/**
* 注册并发事件监听器
*/
void registerMessageListener(final MessageListenerConcurrently messageListener);

/**
* 注册顺序事件监听器
*/
void registerMessageListener(final MessageListenerOrderly messageListener);

/**
* 基于主题订阅消息,并使用表达式过滤消息
*/
void subscribe(final String topic, final String subExpression) throws MQClientException;

/**
* 基于主题订阅消息,并指定队列选择器过滤消息
*/
void subscribe(final String topic, final MessageSelector selector) throws MQClientException;

/**
* 取消指定主题下的订阅者
*/
void unsubscribe(final String topic);

以上便是消息消费者的核心方法,接下来更进一步了解DefaultMQPushConsumer这个实现类有哪些重要属性:

protected final transient DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; // 消息推送功能实现类

private String consumerGroup; // 消费者组

private MessageModel messageModel = MessageModel.CLUSTERING; // 消费模式(默认是集群模式)

private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; // 消费开始偏移量

private AllocateMessageQueueStrategy allocateMessageQueueStrategy; // 集群模式下的消息队列负载策略

private Map<String /* topic */, String /* sub expression */> subscription = new HashMap<String, String>(); // 主题与消息过滤表达式映射表

private MessageListener messageListener; // 消息监听器

private OffsetStore offsetStore; // 消息消费进度存储器

private int consumeThreadMin = 20; // 消息消费者最小线程数量

private int consumeThreadMax = 20; // 消息消费者最大线程数量

private int consumeConcurrentlyMaxSpan = 2000; // 并发消息消费时处理队列最大跨度

private long pullInterval = 0; // 拉取消息任务间隔时间

private int consumeMessageBatchMaxSize = 1; // 每次最多消费消息条数

private int pullBatchSize = 32; // 拉取消息条数

private boolean postSubscriptionWhenPull = false; // 是否每次拉取消息都订阅消息(默认为false)

private boolean unitMode = false; // 是否以组为单位进行订阅消息(默认为false)

private int maxReconsumeTimes = -1; // 消息消费最大重试次数(默认为-1,意味着可以重试最多16次)

private long consumeTimeout = 15; // 消息消费超时时间

private long awaitTerminationMillisWhenShutdown = 0; // 关闭消费者前等待时间(考虑到可能还有消息正在消费,默认为不等待)

源码分析:

通过上一节了解了消费者的重点方法和属性,这一节就开始针对源码进行分析
首先找到消费者的启动入口:

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4"); // 创建基于推送模式的消费者,并指定组名
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // 设置消费开始偏移量
        consumer.subscribe("TopicTest", "*"); // 订阅指定主题下,tag符合匹配表达式条件的消息
        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;
            }
        });
        consumer.start(); // 启动消费者
        System.out.printf("Consumer Started.%n");
    }
}
  • 订阅消息:
public void subscribe(String topic, String subExpression) throws MQClientException {
    this.defaultMQPushConsumerImpl.subscribe(withNamespace(topic), subExpression);
}
public void subscribe(String topic, String subExpression) throws MQClientException {
    try {
        SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression); // 基于主题和tag匹配表达式构建订阅数据对象
        this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); // 保存订阅数据对象
        if (this.mQClientFactory != null) {
            this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
        }
    } catch (Exception e) {
        throw new MQClientException("subscription exception", e);
    }
}
  • 注册监听器:
public void registerMessageListener(MessageListenerConcurrently messageListener) {
    this.messageListener = messageListener;
    this.defaultMQPushConsumerImpl.registerMessageListener(messageListener); // 将监听器注册到消费者实现基础类中
}
public void registerMessageListener(MessageListener messageListener) {
    this.messageListenerInner = messageListener;
}
  • 启动消费者:

为了能够更直观的理解以下源码,首先熟悉下消费者整体的启动流程,如下图所示:

在这里插入图片描述
基于上图的流程图示,再了解具体的源码实现:

public void start() throws MQClientException {
    setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); // 设置消费组
    this.defaultMQPushConsumerImpl.start(); // 启动消费者实现基础类
    if (null != traceDispatcher) {
        try {
            traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
        } catch (MQClientException e) {
            log.warn("trace dispatcher start failed ", e);
        }
    }
}
public synchronized void start() throws MQClientException {
	// 根据服务状态执行相应的逻辑
    switch (this.serviceState) {
        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; // 将状态先标识为启动失败
            this.checkConfig(); // 检查配置
            this.copySubscription(); // 复制(构建)订阅数据
            if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) { // 若消费模式为集群模式,则将消费者客户端实例名称为进程ID
                this.defaultMQPushConsumer.changeInstanceNameToPID();
            }
			
            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook); // 构建消费者客户端对象

			// 构建rebalance对象
            this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
            this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
            this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());
            this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);

            this.pullAPIWrapper = new PullAPIWrapper(
                mQClientFactory,
                this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());
            this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);

            if (this.defaultMQPushConsumer.getOffsetStore() != null) {
                this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();
            } else {
                switch (this.defaultMQPushConsumer.getMessageModel()) {
                    case BROADCASTING: // 广播模式,将消费进度保存在本地
                        this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                        break;
                    case CLUSTERING: // 集群模式,将消费进度保存在远端Broker
                        this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                        break;
                    default:
                        break;
                }
                this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
            }
            this.offsetStore.load();

            if (this.getMessageListenerInner() instanceof MessageListenerOrderly) { // 监听器类型为顺序监听器
            	// 创建顺序消息消费服务
                this.consumeOrderly = true;
                this.consumeMessageService = new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
            } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) { // 监听器类型为并发监听器
            	// 创建并发消息消费服务
                this.consumeOrderly = false;
                this.consumeMessageService = new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
            }

            this.consumeMessageService.start(); // 启动消息消费服务

            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);
            }

            mQClientFactory.start(); // 启动消费者客户端
            
            log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup());
            
            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;
    }

    this.updateTopicSubscribeInfoWhenSubscriptionChanged();
    this.mQClientFactory.checkClientInBroker();
    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
    this.mQClientFactory.rebalanceImmediately();
}
  • 启动消息消费服务:

对于消息消费服务来说,有两个类型:顺序服务、并发服务。因为并发服务类型更常用,因此以下源码都是基于并发服务的源码进行分析

public void start() {
    this.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { // 启动计划任务
        @Override
        public void run() {
            cleanExpireMsg(); // 清理过期消息
        }
    }, this.defaultMQPushConsumer.getConsumeTimeout(), this.defaultMQPushConsumer.getConsumeTimeout(), TimeUnit.MINUTES);
}
  • 注册消费者实例:
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;
}
  • 启动消费者客户端:
public void start() throws MQClientException {
    synchronized (this) {
    	// 根据服务状态执行相应的逻辑
        switch (this.serviceState) {
            case CREATE_JUST: // 已创建,但未启动(初始状态)
                this.serviceState = ServiceState.START_FAILED; // 将服务状态先标识为启动失败
                if (null == this.clientConfig.getNamesrvAddr()) { // 获取NameServer地址,若本地缓存为空,则从远程获取
                    this.mQClientAPIImpl.fetchNameServerAddr();
                }
                this.mQClientAPIImpl.start(); // 开启客户端和服务器端连接通道
                this.startScheduledTask(); // 启动数据更新计划任务(NameServer地址、路由信息、向Broker发送心跳信息,等等)
                this.pullMessageService.start(); // 启动拉取消息服务
                this.rebalanceService.start(); // 启动创建消息拉取请求服务
                this.defaultMQProducer.getDefaultMQProducerImpl().start(false); // 启动生产者推送服务
                log.info("the client factory [{}] start OK", this.clientId);
                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;
        }
    }
}

通过以上源码,可以看到在消费者客户端中启动了很多服务,但是重点关注的服务应该是拉取消息服务。在本文开头说到,所以的消息推送模式也是基于消息拉取模式实现的,其中关键就在此处

  • 启动消息拉取服务:

在这里插入图片描述
所谓的PullMessageService其实是一个线程类,因此关注它的线程任务run方法:

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

     while (!this.isStopped()) {
         try {
             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");
 }
private void pullMessage(final PullRequest pullRequest) {
    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);
    }
}
  • 消息拉取流程:

在这里插入图片描述

public void pullMessage(final PullRequest pullRequest) {
    final ProcessQueue processQueue = pullRequest.getProcessQueue(); // 从拉取消息请求中获取信息处理队列
    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, pullTimeDelayMillsWhenException);
        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(); // 获取最大待处理消息数量
    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;
    }

    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.isPreviouslyLocked()) {
                long offset = -1L;
                try {
                    offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue());
                } catch (MQClientException e) {
                    this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                    log.error("Failed to compute pull offset, pullResult: {}", pullRequest, e);
                    return;
                }
                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.setPreviouslyLocked(true);
                pullRequest.setNextOffset(offset);
            }
        } else {
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            log.info("pull message later because not locked in broker, {}", pullRequest);
            return;
        }
    }

    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();

    PullCallback pullCallback = new PullCallback() { // 定义拉取消息回调函数(在此暂不展开讲解,在本文之后应用的地方再细说)
        @Override
        public void onSuccess(PullResult pullResult) {
            ...
        }
    };

    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, // 异步
            pullCallback
        ); // 与服务端交互,拉取获取消息
    } catch (Exception e) {
        log.error("pullKernelImpl exception", e);
        this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
    }
}
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信息,本地缓存为空,则更新远程NameServer路由信息后,再重新获取
    FindBrokerResult findBrokerResult =
        this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
            this.recalculatePullFromWhichNode(mq), false);
    if (null == findBrokerResult) {
        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;

        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(); // 获取Broker地址
        if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
            brokerAddr = computePullFromWhichFilterServer(mq.getTopic(), brokerAddr);
        }

        PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
            brokerAddr,
            requestHeader,
            timeoutMillis,
            communicationMode,
            pullCallback); // 发送异步请求,拉取消息,返回拉取结果

        return pullResult;
    }

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

根据传入的参数,决定消费者客户端和服务器端的交互方式,此处是异步:

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: // 重点关注异步
            this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
            return null;
        case SYNC:
            return this.pullMessageSync(addr, request, timeoutMillis);
        default:
            assert false;
            break;
    }

    return null;
}
private void pullMessageAsync(
    final String addr,
    final RemotingCommand request,
    final long timeoutMillis,
    final PullCallback pullCallback
) throws RemotingException, InterruptedException {
	// 发起异步请求,从Broker中获取消息,并在回调函数中处理响应结果
    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()));
                }
            }
        }
    });
}

到此,终于看到拉取消息的请求发送出去了,那么接受请求方Broker,又是如何处理这个请求的呢?返回的响应结果当中又包含了什么呢?

  • Broker接收处理请求:

在这里插入图片描述
找到Broker处理拉取消息请求的方法入口:

private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException {
	...
	// 创建消息过滤器
    MessageFilter messageFilter;
    if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) {
        messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData, this.brokerController.getConsumerFilterManager());
    } else {
        messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData, this.brokerController.getConsumerFilterManager());
    }

	// 根据请求头的组名、主题、队列id、队列偏移量、最大获取消息数量,以及消息过滤器条件获取消息
    final GetMessageResult getMessageResult = this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
            requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);
    if (getMessageResult != null) {
    	// 封装响应结果
        response.setRemark(getMessageResult.getStatus().name());
        responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());
        responseHeader.setMinOffset(getMessageResult.getMinOffset());
        responseHeader.setMaxOffset(getMessageResult.getMaxOffset());

        if (getMessageResult.isSuggestPullingFromSlave()) {
            responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
        } else {
            responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
        }

        switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) {
            case ASYNC_MASTER:
            case SYNC_MASTER:
                break;
            case SLAVE:
                if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
                    response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                    responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
                }
                break;
        }

        if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
            // consume too slow ,redirect to another machine
            if (getMessageResult.isSuggestPullingFromSlave()) {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
            }
            // consume ok
            else {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
            }
        } else {
            responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
        }

        switch (getMessageResult.getStatus()) {
            case FOUND: // 消息获取成功
                response.setCode(ResponseCode.SUCCESS); 
                break;
            case MESSAGE_WAS_REMOVING: // 消息存在下一个commitLog文件中,需要重试拉取消息
                response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); 
                break;
            case NO_MATCHED_LOGIC_QUEUE: // 未找到队列
            case NO_MESSAGE_IN_QUEUE: // 队列中未包消息
                if (0 != requestHeader.getQueueOffset()) {
                    response.setCode(ResponseCode.PULL_OFFSET_MOVED);
                    // XXX: warn and notify me
                    log.info("the broker store no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}",
                        requestHeader.getQueueOffset(),
                        getMessageResult.getNextBeginOffset(),
                        requestHeader.getTopic(),
                        requestHeader.getQueueId(),
                        requestHeader.getConsumerGroup()
                    );
                } else {
                    response.setCode(ResponseCode.PULL_NOT_FOUND);
                }
                break;
            case NO_MATCHED_MESSAGE: // 未找到消息,需要重试拉取消息
                response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY); 
                break;
            case OFFSET_FOUND_NULL: // 根据指定偏移量找到的消息为空
                response.setCode(ResponseCode.PULL_NOT_FOUND);
                break;
            case OFFSET_OVERFLOW_BADLY: // 偏移量越界
                response.setCode(ResponseCode.PULL_OFFSET_MOVED);
                // XXX: warn and notify me
                log.info("the request offset: {} over flow badly, broker max offset: {}, consumer: {}",
                    requestHeader.getQueueOffset(), getMessageResult.getMaxOffset(), channel.remoteAddress());
                break;
            case OFFSET_OVERFLOW_ONE: // 偏移量在队列中没找到
                response.setCode(ResponseCode.PULL_NOT_FOUND);
                break;
            case OFFSET_TOO_SMALL: // 偏移量不在队列中
                response.setCode(ResponseCode.PULL_OFFSET_MOVED);
                log.info("the request offset too small. group={}, topic={}, requestOffset={}, brokerMinOffset={}, clientIp={}",
                    requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueOffset(),
                    getMessageResult.getMinOffset(), channel.remoteAddress());
                break;
            default:
                assert false;
                break;
        }
	}
	...
	// 若commitLog标记为可用,并且当前操作的Broker是主结点,则更新消费进度
    boolean storeOffsetEnable = brokerAllowSuspend;
    storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag;
    storeOffsetEnable = storeOffsetEnable && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
    if (storeOffsetEnable) {
        this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),
            requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());
    }
    return response;
}

以上便是Broke处理消息拉取请求,并返回响应的核心逻辑
那么接下来,继续回到消费者这边看看,又是如何处理这个响应结果的

  • 处理响应结果:
private PullResult processPullResponse(final RemotingCommand response, final String addr) throws MQBrokerException, RemotingCommandException {
	// 根据响应结果返回码决定拉取消息状态
    PullStatus pullStatus = PullStatus.NO_NEW_MSG;
    switch (response.getCode()) {
        case ResponseCode.SUCCESS: // 消息获取成功
            pullStatus = PullStatus.FOUND;
            break;
        case ResponseCode.PULL_NOT_FOUND: // 消息获取失败
            pullStatus = PullStatus.NO_NEW_MSG; 
            break;
        case ResponseCode.PULL_RETRY_IMMEDIATELY: // 未找到匹配的消息,需要重试
            pullStatus = PullStatus.NO_MATCHED_MSG;
            break;
        case ResponseCode.PULL_OFFSET_MOVED: // 指定拉取消息的偏移量有误
            pullStatus = PullStatus.OFFSET_ILLEGAL;
            break;
        default:
            throw new MQBrokerException(response.getCode(), response.getRemark(), addr);
    }

	// 封装并返回拉取结果信息
    PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);
    return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),
        responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
}
  • 回调函数处理成功 / 失败结果:
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()); // 将拉取到的消息存入processQueue
                        DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
                            pullResult.getMsgFoundList(),
                            processQueue,
                            pullRequest.getMessageQueue(),
                            dispatchToConsume); // 将processQueue提交到消费消息服务

						// 如果pullInterval > 0,则等待pullInterval毫秒后再执行下一次拉取消息操作;否则,直接执行,拉取消息
                        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;
               	...
                default:
                    break;
            }
        }
    }

	/**
	* 处理异常结果
	*/
    @Override
    public void onException(Throwable e) { 
        if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
            log.warn("execute the pull request exception", e);
        }

        DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
    }
};
  • 将processQueue提交到消费消息服务:
public void submitConsumeRequest(
    final List<MessageExt> msgs,
    final ProcessQueue processQueue,
    final MessageQueue messageQueue,
    final boolean dispatchToConsume) {
    final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize(); // 获取单次消费消息最大大小
    if (msgs.size() <= consumeBatchSize) { // 消息大小 <= 单次消费消息最大大小,允许直接消费
        ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue); // 根据消息内容、处理队列、消息队列封装成消费请求对象(实际上是一个线程类,提交给线程池异步处理,消费消息)
        try {
            this.consumeExecutor.submit(consumeRequest); // 将消费请求提交到线程池进行处理
        } catch (RejectedExecutionException e) {
            this.submitConsumeRequestLater(consumeRequest);
        }
    } else { // 否则,需要对消息进行切割,再分批消费
        for (int total = 0; total < msgs.size(); ) {
            List<MessageExt> msgThis = new ArrayList<MessageExt>(consumeBatchSize);
            for (int i = 0; i < consumeBatchSize; i++, total++) {
                if (total < msgs.size()) {
                    msgThis.add(msgs.get(total));
                } else {
                    break;
                }
            }

            ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
            
            try {
                this.consumeExecutor.submit(consumeRequest);
            } catch (RejectedExecutionException e) {
                for (; total < msgs.size(); total++) {
                    msgThis.add(msgs.get(total));
                }
                this.submitConsumeRequestLater(consumeRequest);
            }
        }
    }
}
  • 提交消费线程:

将消费线程提交到线程池后,池中分配对应的资源和时间执行线程中任务。因此,回到消费线程类ConsumeRequest本身,看下它的线程任务run方法中具体内容是什么

public void run() {
    if (this.processQueue.isDropped()) {
        log.info("the message queue not be able to consume, because it's dropped. group={} {}", ConsumeMessageConcurrentlyService.this.consumerGroup, this.messageQueue);
        return;
    }

    MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener; // 获取消费并发服务的监听器(本文开头有讲解到注册监听器)
    ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
    ConsumeConcurrentlyStatus status = null;
    defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup());

	// 若有注册到钩子函数,则执行钩子函数
    ConsumeMessageContext consumeMessageContext = null;
    if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
        consumeMessageContext = new ConsumeMessageContext();
        consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace());
        consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup());
        consumeMessageContext.setProps(new HashMap<String, String>());
        consumeMessageContext.setMq(messageQueue);
        consumeMessageContext.setMsgList(msgs);
        consumeMessageContext.setSuccess(false);
        ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
    }

    long beginTimestamp = System.currentTimeMillis();
    boolean hasException = false;
    ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
    try {
    	// 为消息设置消费开始时间戳
        if (msgs != null && !msgs.isEmpty()) {
            for (MessageExt msg : msgs) {
                MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis()));
            }
        }
        status = listener.consumeMessage(Collections.unmodifiableList(msgs), context); // 调用监听器的consumeMessage方法,传入消息以及消息上下文,执行自定义的消费逻辑(回到本开头注册并发监听器的consumeMessage方法)
    } catch (Throwable e) {
        log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", RemotingHelper.exceptionSimpleDesc(e), ConsumeMessageConcurrentlyService.this.consumerGroup, msgs, messageQueue);
        hasException = true;
    }
	...
}

到此,消息消费者的核心源码解析结束

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值