RocketMQ源码解析-Consumer启动(2)

接着上文的Pull消费者启动继续讲。

public void start() throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;

            this.checkConfig();

            this.copySubscription();

            if (this.defaultMQPullConsumer.getMessageModel() == MessageModel.CLUSTERING) {
                this.defaultMQPullConsumer.changeInstanceNameToPID();
            }

            this.mQClientFactory =
                    MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQPullConsumer,
                            this.rpcHook);

            this.rebalanceImpl.setConsumerGroup(this.defaultMQPullConsumer.getConsumerGroup());
            this.rebalanceImpl.setMessageModel(this.defaultMQPullConsumer.getMessageModel());
            this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPullConsumer
                    .getAllocateMessageQueueStrategy());
            this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);

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

            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:
                        this.offsetStore =
                                new RemoteBrokerOffsetStore(this.mQClientFactory,
                                        this.defaultMQPullConsumer.getConsumerGroup());
                        break;
                    default:
                        break;
                }
            }

            this.offsetStore.load();

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

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

 

 

在前文的start()方法中,消费者已经通过checkConfig()和copySubscription()方法已经检查或复制了一部分配置类DefaultMQPullConsumer的属性,接下来会从MQClientMananger中尝试获取mq客户端实例。其实这一步,与生产者中获取客户端的实例一模一样,由于消费者和生产者的配置类都继承了clientConfig类,所以可以调用一个方法实现客户端的获取。自然,两者所实现的客户端其实一模一样,区别在于是否配置了rebalanceImpl等相关的配置。在就客户端而言上,其实消费者生产者的逻辑都是同一套。

 

接下来是对平衡接口实现的一系列配置,rebalanceImpl在DefaultMQClientImpl中一开始就初始化完毕,将这个消费者作为参数传入。

private RebalanceImpl rebalanceImpl = new RebalancePullImpl(this);

 

在pull消费者中,实现的是RebalancePullImpl继承自RebalanceImpl,在之前的copySubscription()方法中可以看到,在消费者配置类里所存放的topic信息在里面都被转化为SubscriptionData,作为键值对存放在rebalanceImpl下的map中。

 

接下来会将消费者的相关属性配置在rebalanceImpl中,比如消费者的consumerGroup(组名),消息模型(广播or集群,这里先默认采用集群),分配消息队列的策略(前文有解释,这里采用默认的平均分配策略),以及消费者客户端实例。

protected static final Logger log = ClientLogger.getLog();
protected final ConcurrentHashMap<MessageQueue, ProcessQueue> processQueueTable =
        new ConcurrentHashMap<MessageQueue, ProcessQueue>(64);
protected final ConcurrentHashMap<String/* topic */, Set<MessageQueue>> topicSubscribeInfoTable =
        new ConcurrentHashMap<String, Set<MessageQueue>>();
protected final ConcurrentHashMap<String /* topic */, SubscriptionData> subscriptionInner =
        new ConcurrentHashMap<String, SubscriptionData>();
protected String consumerGroup;
protected MessageModel messageModel;
protected AllocateMessageQueueStrategy allocateMessageQueueStrategy;
protected MQClientInstance mQClientFactory;

以上是rebalanceImpl的结构,可以看到相关消费者启动的配置在这里都已经配置完毕。

 

接下来,建立pullAPIWrapper,这里调用了构造方法,只是简单的配置,在这里还没有具体的使用到。作为pull消费者,在pull具体的message的时候,正是要调用pullAPIWrapper的相关方法。

 

接下来将会初始化这个消费者的offsetStore,这里会根据选取的是广播模式还是消费者模式,来选取相关的策略。在广播模式下,所有的消费者都会收到所订阅的消息,那么显然,在这个模式下面的所有消费者都会将自己消费消费消息队列的进度保存在自己本地上。而在集群模式下,所有的消费者来平均消费消息,那么相应的,这里的消费进度将会保存在远程。所以,如果采用了广播模式,offset采用的是LocalFileOffsetStroe,对应的,集群模式采用的是,RemoteBrokerOffsetStore。

 

在向消费者客户端实例注册完毕当前的正处于启动过程的消费者之后,将会调用客户端MQClientInstance的start()方法来完成客户端的启动。可以看客户端的start()方法。

public void start() throws MQClientException {
    PackageConflictDetect.detectFastjson();

    synchronized (this) {
        switch (this.serviceState) {
        case CREATE_JUST:
            this.serviceState = ServiceState.START_FAILED;
            // If not specified,looking address from name server
            if (null == this.clientConfig.getNamesrvAddr()) {
                this.clientConfig.setNamesrvAddr(this.mQClientAPIImpl.fetchNameServerAddr());
            }
            // Start request-response channel
            this.mQClientAPIImpl.start();
            // Start various schedule tasks
            this.startScheduledTask();
            // Start pull service
            this.pullMessageService.start();
            // Start rebalance service
            this.rebalanceService.start();
            // Start push service
            this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
            log.info("the client factory [{}] start OK", this.clientId);
            this.serviceState = ServiceState.RUNNING;
            break;
        case RUNNING:
            break;
        case SHUTDOWN_ALREADY:
            break;
        case START_FAILED:
            throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
        default:
            break;
        }
    }
}

 

客户端的启动代码与生产者客户端启动的是同一段代码。

 

在之前,与生产者一样,解析路由消息,完成路由消息的配置,启动netty客户端完成消息的发送,启动定时任务定时更新路由数据,定时发送心跳,调整线程数量大小,在前半部分过程与生产者一模一样,在前文生产者的启动中都已经详细解释过。我想,在pull模式下的消费者,应该更关心RebalanceService的启动。

 

可以看到RebalanceService的run()方法。

 

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

    while (!this.isStoped()) {
        this.waitForRunning(WaitInterval);
        this.mqClientFactory.doRebalance();
    }

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

只要客户端没有被关闭,那么将会一直循环调用客户端的doRebalance()方法。

public void doRebalance() {
    for (String group : this.consumerTable.keySet()) {
        MQConsumerInner impl = this.consumerTable.get(group);
        if (impl != null) {
            try {
                impl.doRebalance();
            }
            catch (Exception e) {
                log.error("doRebalance exception", e);
            }
        }
    }
}

在MQClientInstance的doRebalance方法里,将会循环调用每个consumerGroup下面的消费者的doRebalance()方法,用来试图达到平衡的目的。而doRebalance(0方法直接在DefaultMQPullConsumerImpl里实现。

public void doRebalance() {
    if (this.rebalanceImpl != null) {
        this.rebalanceImpl.doRebalance();
    }
}

 

直接调用了rebalanceImpl的doRebalance()方法。现在可以来仔细看rebalanceImpl的实现。

 

public void doRebalance() {
    Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
    if (subTable != null) {
        for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
            final String topic = entry.getKey();
            try {
                this.rebalanceByTopic(topic);
            } catch (Exception e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("rebalanceByTopic Exception", e);
                }
            }
        }
    }

    this.truncateMessageQueueNotMyTopic();
}

 

首先根据所有在一开始根据topic所生成的订阅信息SubscriptionData都将会被遍历。分别调用reBalanceByTopic()根据topic来依次重新平衡负载。

 

private void rebalanceByTopic(final String topic) {
    switch (messageModel) {
        case BROADCASTING: {
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            if (mqSet != null) {
                boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet);
                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: {
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            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);
                }

                boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet);
                if (changed) {
                    log.info(
                            "rebalanced allocate source. allocateMessageQueueStrategyName={}, group={}, topic={}, mqAllSize={}, cidAllSize={}, mqAll={}, cidAll={}",
                            strategy.getName(), consumerGroup, topic, mqSet.size(), cidAll.size(), mqSet, cidAll);
                    log.info(
                            "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, ConsumerId={}, rebalanceSize={}, rebalanceMqSet={}",
                            strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(),
                            allocateResultSet.size(), mqAll.size(), cidAll.size(), allocateResultSet);

                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }
            }
            break;
        }
        default:
            break;
    }
}

根据广播模式和集群模式都各有做法,先来看集群模式。

集群模式中,先通过topic得到topic对应的messageQueue数据,messageQueue的数据结构如下。

private String topic;
private String brokerName;
private int queueId;

 

保存着topic的Broker名以及具体的队列id。

 

之后根据topic来获取所有订阅这个topic的消费者。

接下来就会根据在之前配置的消息队列分配策略,调用分配策略的allocate()方法,完成消息队列的重新分配。(默认采用的是平均分配策略,具体分配策略的解释在上一篇)。

接下来调用updateProcessQueueTableInRebalance()方法来根据重新平衡的而结果来更新处理队列。

 

private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet) {
    boolean changed = false;

    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()) {
                switch (this.consumeType()) {
                    case CONSUME_ACTIVELY:
                        break;
                    case CONSUME_PASSIVELY:
                        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;
                }
            }
        }
    }

    List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
    for (MessageQueue mq : mqSet) {
        if (!this.processQueueTable.containsKey(mq)) {
            PullRequest pullRequest = new PullRequest();
            pullRequest.setConsumerGroup(consumerGroup);
            pullRequest.setMessageQueue(mq);
            pullRequest.setProcessQueue(new ProcessQueue());

            long nextOffset = this.computePullFromWhere(mq);
            if (nextOffset >= 0) {
                pullRequest.setNextOffset(nextOffset);
                pullRequestList.add(pullRequest);
                changed = true;
                this.processQueueTable.put(mq, pullRequest.getProcessQueue());
                log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
            } else {
                log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
            }
        }
    }

    this.dispatchPullRequest(pullRequestList);

    return changed;
}

 

首先,通过遍历所有消息队列与处理队列的对应关系,如果在新的分配之后,该消息队列已经不再是负责原来topic下的消息传送,那么这一对应关系将会被清除,这一消息队列的数据messageQueue也会被相应的从消费者的存储中remove掉。既然有旧的无用消息队列被清除,那自然有新的消息队列需要建立新的处理队列processQueue与其建立对应关系。在这里将会生成pullRequest来建立新的对应关系。并通过computePullFromWhere()得到下次拉取数据的位置,在RebalancePullImpl中具体实现了这一方法,直接返回0,来确认下一次拉取数据的位置。并将新的处理队列与对应的messageQueue放入map保存。

 

 

如果消息队列在这次rebalance的过程中发生了修改,那么则会调用messageQueueChanged()方法来处理相应的改变。具体实现在RebalancePullImpl中。

 

集群模式的队列rebalance就此结束。

 

在客户端启动完毕之后,PullConsumer也启动完毕。

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMQ是一个开的分布式消息中间件,它具有高吞吐量、高可用性、可伸缩性和可靠性的特点。RocketMQ提供了多种语言的客户端,其中包括了针对Spring Boot的RocketMQ启动器(starter)。 RocketMQ的Spring Boot启动器是一个用于集成RocketMQ和Spring Boot的依赖项。通过添加该启动器,我们可以方便地在Spring Boot项目中使用RocketMQ。 要使用rocketmq-spring-boot-starter,您需要完成以下步骤: 1. 在您的项目的pom.xml文件中添加以下依赖项: ```xml <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>版本号</version> </dependency> ``` 请注意,您需要将“版本号”替换为您想要使用的RocketMQ Spring Boot Starter的版本号。 2. 在您的应用程序的配置文件(例如application.properties或application.yml)中添加RocketMQ的相关配置。您可以根据您的需求配置以下属性: - rocketmq.name-server:RocketMQ的Name Server地址。 - rocketmq.producer.group:生产者组的名称。 - rocketmq.consumer.group:消费者组的名称。 - rocketmq.consumer.topics:要订阅的主题列表。 3. 在您的Spring Boot应用程序中使用RocketMQ的相关注解来发送和接收消息。例如,您可以使用`@RocketMQMessageListener`注解来监听RocketMQ的消息,并使用`@RocketMQTransactionListener`注解来处理事务消息。 通过使用rocketmq-spring-boot-starter,您可以方便地在Spring Boot项目中集成RocketMQ,并使用RocketMQ的各种功能,如发送普通消息、发送和接收顺序消息、发送和接收定时消息等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值