9、RocketMQ的Consumer之启动流程

Consumer之启动流程
Producer将消息发送给Broker后Broker做好持久化这是就可以创建消费者来对消息进行消费,今天我们分析如何启动一个消费者代码,原理与启动生产者类似。

DefaultMQPushConsumer类的初始化

public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook,
    AllocateMessageQueueStrategy allocateMessageQueueStrategy) {
    // 消费者组
    this.consumerGroup = consumerGroup;
    // 命名空间
    this.namespace = namespace;
    // 分配策略
    this.allocateMessageQueueStrategy = allocateMessageQueueStrategy;
    // 创建DefaultMQPushConsumerImpl实例
    defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(this, rpcHook);
}

DefaultMQPushConsumerImpl作为DefaultMQPushConsumer具体实现,基本上和之前分析过的Producer类似,该方法初始化注册了钩子函数,注册messageListener消息监听器,赋值一些namesevAddr等参数。

subscribe订阅

Producer创建时可以去指定发送的Topic而消费端只要自己去订阅相关topic,同时也可以根据tag与Sql方式进行消息的过滤。


/**
 * 订阅topic支持消息过滤表达式
 * @param topic 订阅的topic
 * @param subExpression 只支持或(“||”)操作,如果是null或者“*”则表示订阅全部
 */
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 subscriptionData = FilterAPI.buildSubscriptionData(topic, subExpression);
        // 将topic与SubscriptionData的关系维护到RebalanceImpl内部的subscriptionInner
        this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
        if (this.mQClientFactory != null) {
            // 如果mQClientFactory不为null就发送心跳给所有broker
            this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
        }
    } catch (Exception e) {
        throw new MQClientException("subscription exception", e);
    }
}

DefaultMQPushConsumer的start方法

正式开始启动消费者,调用DefaultMQPushConsumer的start启动。
public void start() throws MQClientException {
  // 根据namespace和consumerGroup设置消费者组
    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);
        }
    }
}

DefaultMQPushConsumer的start启动的逻辑主要就是根据namespace和ConsumerGroup去设置消费者组,主要的实现还是在其近视实现类DefaultMQPushConsumerImpl(门面模式)中。


public synchronized void start() throws MQClientException {
    // 根据服务状态实现不同的逻辑,只有当为CREATE_JUST才会正常启动,其他类型的都报错
    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) {
                this.defaultMQPushConsumer.changeInstanceNameToPID();
            }

            this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);
            // 消费者组
            this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
            // 消费者模式(广播/集群)
            this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
            //Consumer负载均衡策略(集群模式才存在负载均衡)
            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()) {
                    // 广播模式(本地的Offset文件)
                    case BROADCASTING:
                        this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                        break;
                    // 集群模式 (Broker里面的offset)
                    case CLUSTERING:
                        this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
                        break;
                    default:
                        break;
                }
                this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
            }
            // 加载消息偏移量
            this.offsetStore.load();
            //Consumer中自行指定的回调函数。
            if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {
                this.consumeOrderly = true;
                // MessageListenerOrderly服务
                this.consumeMessageService =
                    new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
            } else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
                this.consumeOrderly = false;
                // MessageListenerConcurrently服务
                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);
            }
          // 启动CreateMQClientInstance客户端通信实例
            // 包括netty服务,各种定时任务,拉取消息服务,rebalanceService服务。
            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;
    }
    
    // 向NameServer拉取并更新当前消费者订阅的topic路由信息
    this.updateTopicSubscribeInfoWhenSubscriptionChanged();
    // 随机选择broker检查tags
    this.mQClientFactory.checkClientInBroker();
    // 发送心跳给所有Broker
    this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
    // 唤醒负载均衡服务rebalanceService,主动进行一次MessageQueue的重平衡。
    this.mQClientFactory.rebalanceImmediately();
}

相关逻辑实现
检查配置
调用checkConfig()方法,对一系列参数进行校验。


private void checkConfig() throws MQClientException {
      // 检查消费组(不能为空,长度不能大于255,只能包含数字和字母)
        Validators.checkGroup(this.defaultMQPushConsumer.getConsumerGroup());
        if (null == this.defaultMQPushConsumer.getConsumerGroup()) {
            throw new MQClientException(
                "consumerGroup is null"
                    + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
                null);
        }
      // 检查是否是默认值名,是则报错
        if (this.defaultMQPushConsumer.getConsumerGroup().equals(MixAll.DEFAULT_CONSUMER_GROUP)) {
            throw new MQClientException(
                "consumerGroup can not equal "
                    + MixAll.DEFAULT_CONSUMER_GROUP
                    + ", please specify another one."
                    + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
                null);
        }
      // 检查消费模式(广播或集群)
        if (null == this.defaultMQPushConsumer.getMessageModel()) {
            throw new MQClientException(
                "messageModel is null"
                    + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
                null);
        }
      //  ....
}

首先检查消费组不能为空,长度不能大于255,只能包含数字和字母,同时消费组的名字不能和默认消费组(DEFAULT_CONSUMER)名字一致,否则就会抛异常。

检查消费模式只能存在集群或者广播模式,其它的则会抛异常。

检查ConsumeFromWhere是否为空,ConsumeFromWhere表示消费策略,简单理解就是消息从什么地方开始消费,默认CONSUME_FROM_LAST_OFFSET(队列的最后开始消费),还可以指定CONSUME_FROM_FIRST_OFFSET(队列的第一个开始消费)和CONSUME_FROM_TIMESTAMP(指定时间戳)。

校验开始消费时间,集群模式下负载策略allocateMessageQueueStrategy,订阅关系,是否注册消息监听,本地线程数,拉取消息的时间间隔,单次拉取消息的数量以及单次消费的数量。

拷贝订阅关系
就是为集群模式下的消费者配置其对应的重试主题retryTopic并绑定,用于实现消息重试,而广播消息不会做任何消息。

private void copySubscription() throws MQClientException {
    try {
      // 订阅消息进行拷贝存入rebalanceImpl中
        Map<String, String> sub = this.defaultMQPushConsumer.getSubscription();
        if (sub != null) {
            for (final Map.Entry<String, String> entry : sub.entrySet()) {
                final String topic = entry.getKey();
                final String subString = entry.getValue();
                SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subString);
                this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
            }
        }

        if (null == this.messageListenerInner) {
            this.messageListenerInner = this.defaultMQPushConsumer.getMessageListener();
        }
      // 消息类型,如果是集群消息进行绑定进行失败重试,如果是广播消息不做任何操作不重试直接丢弃
        switch (this.defaultMQPushConsumer.getMessageModel()) {
            case BROADCASTING:
                break;
            case CLUSTERING:
                final String retryTopic = MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup());
                SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(retryTopic, SubscriptionData.SUB_ALL);
                this.rebalanceImpl.getSubscriptionInner().put(retryTopic, subscriptionData);
                break;
            default:
                break;
        }
    } catch (Exception e) {
        throw new MQClientException("subscription exception", e);
    }
}

注意:集群消息自动订阅该消费者的重试topic,当消息失败会进行重试,防止消息丢失,广播消息不会做任何操作,当消息失败直接丢弃,消息会产生丢失。

其它操作
调用getOrCreateMQClientInstance方法获取CreateMQClientInstance实例,设置负载均衡服务rebalanceImpl的相关属性,创建拉取消息核心类PullAPIWrapper。

根据不同的消息模式设置不同位置想Offset文件即消息偏移量,广播模式下Offset文件保存在本地,集群模式下Offset文件保存在远程的Broker端,同时调用load去加载偏移量。

指定指定的MessageListener去创建不同的ConsumeMessageService消息服务,其中存在MessageListenerOrderly(顺序消费)与MessageListenerConcurrently(并发消费)两个类。

启动消费者服务this.consumeMessageService.start(),进行服务的注册,启动CreateMQClientInstance客户端通信实例,初始化netty服务,各种定时任务,拉取消息服务,rebalabceService服务等等,与之前的Producer类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

princeAladdin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值