All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.huaweicloud.sermant.rocketmq.controller.RocketMqPullConsumerController Maven / Gradle / Ivy

/*
 *  Copyright (C) 2023-2023 Huawei Technologies Co., Ltd. All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.huaweicloud.sermant.rocketmq.controller;

import com.huaweicloud.sermant.core.common.LoggerFactory;
import com.huaweicloud.sermant.core.utils.ReflectUtils;
import com.huaweicloud.sermant.rocketmq.cache.RocketMqConsumerCache;
import com.huaweicloud.sermant.rocketmq.wrapper.DefaultLitePullConsumerWrapper;
import com.huaweicloud.sermant.utils.RocketMqWrapperUtils;

import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.impl.consumer.AssignedMessageQueue;
import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl;
import org.apache.rocketmq.client.impl.consumer.RebalanceImpl;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * pull消费者控制类
 *
 * @author daizhenyu
 * @since 2023-12-15
 **/
public class RocketMqPullConsumerController {
    private static final long DELAY_TIME = 1000L;

    private static final int MAXIMUM_RETRY = 5;

    private static final int THREAD_SIZE = 1;

    private static final long THREAD_KEEP_ALIVE_TIME = 60L;

    private static final int THREAD_QUEUE_CAPACITY = 20;

    private static final Logger LOGGER = LoggerFactory.getLogger();

    private static volatile ThreadPoolExecutor executor;

    private RocketMqPullConsumerController() {
    }

    /**
     * 禁止pull消费者消费
     *
     * @param wrapper pull消费者wrapper
     * @param topics 禁止消费的topic
     */
    public static void disablePullConsumption(DefaultLitePullConsumerWrapper wrapper, Set topics) {
        Set subscribedTopic = wrapper.getSubscribedTopics();
        if (subscribedTopic.stream().anyMatch(topics::contains)) {
            suspendPullConsumer(wrapper);
            return;
        }
        resumePullConsumer(wrapper);
    }

    private static void suspendPullConsumer(DefaultLitePullConsumerWrapper wrapper) {
        switch (wrapper.getSubscriptionType()) {
            case SUBSCRIBE:
                suspendSubscriptiveConsumer(wrapper);
                break;
            case ASSIGN:
                suspendAssignedConsumer(wrapper);
                break;
            default:
                break;
        }
    }

    private static void suspendSubscriptiveConsumer(DefaultLitePullConsumerWrapper wrapper) {
        if (wrapper.isProhibition()) {
            LOGGER.log(Level.INFO, "Consumer has prohibited consumption, consumer instance name : {0}, "
                            + "consumer group : {1}, topic : {2}",
                    new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(), wrapper.getSubscribedTopics()});
            return;
        }

        // 退出消费者前主动提交消费的offset,退出消费者组后立刻触发一次重平衡,重新分配队列
        wrapper.getPullConsumerImpl().persistConsumerOffset();
        wrapper.getClientFactory().unregisterConsumer(wrapper.getConsumerGroup());
        doRebalance(wrapper);
        wrapper.setProhibition(true);
    }

    private static void suspendAssignedConsumer(DefaultLitePullConsumerWrapper wrapper) {
        DefaultLitePullConsumerImpl pullConsumerImpl = wrapper.getPullConsumerImpl();
        if (wrapper.getAssignedMessageQueue() == null) {
            Optional assignedMessageQueueOptional = RocketMqWrapperUtils
                    .getAssignedMessageQueue(pullConsumerImpl);
            if (assignedMessageQueueOptional.isPresent()) {
                wrapper.setAssignedMessageQueue(assignedMessageQueueOptional.get());
            }
        }
        AssignedMessageQueue assignedMessageQueue = wrapper.getAssignedMessageQueue();
        assignedMessageQueue.updateAssignedMessageQueue(new ArrayList<>());
        if (wrapper.getPullConsumer().isRunning()) {
            ReflectUtils.invokeMethod(pullConsumerImpl, "updateAssignPullTask",
                    new Class[]{Collection.class}, new Object[]{new ArrayList<>()});
        }
        LOGGER.log(Level.INFO, "Success to prohibit consumption, consumer instance name : {0}, consumer group : {1}, "
                        + "message queue : {2}",
                new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(),
                        wrapper.getMessageQueues()});
    }

    private static void doRebalance(DefaultLitePullConsumerWrapper wrapper) {
        initExecutor();

        // 退出消费者组后立刻清理消费者目前消费的消息队列
        messageQueueChanged(wrapper);

        // 延时五秒后,确认是否退出消费者组,并在此清理消费者目前消费的消息队列,确保禁消费成功
        executor.submit(() -> doDelayRebalance(wrapper));
    }

    private static void doDelayRebalance(DefaultLitePullConsumerWrapper wrapper) {
        // 获取消费者的相关参数
        RebalanceImpl rebalance = wrapper.getRebalanceImpl();
        String instanceName = wrapper.getInstanceName();
        String consumerGroup = wrapper.getConsumerGroup();
        Set subscribedTopics = wrapper.getSubscribedTopics();
        MQClientInstance clientFactory = wrapper.getClientFactory();
        String clientId = clientFactory.getClientId();

        // 每间隔一秒,判断消费者是否退出消费者组,如果退出清空消费者消费的消息队列,最大重试次数为五次
        int retryCount = 0;
        boolean isConsumerGroupExited = false;
        while ((retryCount < MAXIMUM_RETRY) && !isConsumerGroupExited) {
            try {
                Thread.sleep(DELAY_TIME);
            } catch (InterruptedException e) {
                LOGGER.log(Level.SEVERE, "An InterruptedException occurs on the thread, "
                        + "details: {0}", e.getMessage());
            }
            for (String topic : subscribedTopics) {
                List consumerIdList = clientFactory.findConsumerIdList(topic, consumerGroup);
                if (consumerIdList != null && consumerIdList.contains(clientId)) {
                    retryCount++;
                    break;
                }

                messageQueueChanged(rebalance, topic);
                if (!isConsumerGroupExited) {
                    isConsumerGroupExited = true;
                }
            }
        }
        if (isConsumerGroupExited) {
            LOGGER.log(Level.INFO, "Success to prohibit consumption, consumer instance name : {0}, "
                            + "consumer group : {1}, topic : {2}",
                    new Object[]{instanceName, consumerGroup, subscribedTopics});
        } else {
            LOGGER.log(Level.SEVERE, "Consumer exiting the {0} consumer group timeout may cause "
                            + "a failure to reallocate the message queue, "
                            + "consumer instance name : {0}, consumer group : {1}, topic : {2}. "
                            + "Please deliver the configuration again.",
                    new Object[]{instanceName, consumerGroup, subscribedTopics});
            wrapper.setProhibition(false);
            wrapper.getClientFactory().registerConsumer(consumerGroup, wrapper.getPullConsumerImpl());
        }
    }

    private static void messageQueueChanged(DefaultLitePullConsumerWrapper wrapper) {
        RebalanceImpl rebalance = wrapper.getRebalanceImpl();
        ConcurrentMap subTable = rebalance.getSubscriptionInner();
        if (subTable != null) {
            for (Entry entry : subTable.entrySet()) {
                String topic = entry.getKey();
                messageQueueChanged(rebalance, topic);
            }
        }
    }

    private static void messageQueueChanged(RebalanceImpl rebalance, String topic) {
        // 清空消费者的processQunues、assignedMessageQueues和pullTask,从而停止消费
        ReflectUtils.invokeMethod(rebalance, "updateProcessQueueTableInRebalance",
                new Class[]{String.class, Set.class, boolean.class},
                new Object[]{topic, new HashSet(), false});
        Set messageQueuesSet = rebalance.getTopicSubscribeInfoTable().get(topic);
        rebalance.messageQueueChanged(topic, messageQueuesSet, new HashSet<>());
    }

    private static void resumePullConsumer(DefaultLitePullConsumerWrapper wrapper) {
        switch (wrapper.getSubscriptionType()) {
            case SUBSCRIBE:
                resumeSubscriptiveConsumer(wrapper);
                break;
            case ASSIGN:
                resumeAssignedConsumer(wrapper);
                break;
            default:
                break;
        }
    }

    private static void resumeSubscriptiveConsumer(DefaultLitePullConsumerWrapper wrapper) {
        if (!wrapper.isProhibition()) {
            LOGGER.log(Level.INFO, "Consumer has opened consumption, consumer "
                            + "instance name : {0}, consumer group : {1}, topic : {2}",
                    new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(), wrapper.getSubscribedTopics()});
            return;
        }
        String consumerGroup = wrapper.getConsumerGroup();
        DefaultLitePullConsumerImpl pullConsumerImpl = wrapper.getPullConsumerImpl();

        wrapper.getClientFactory().registerConsumer(consumerGroup, pullConsumerImpl);
        pullConsumerImpl.doRebalance();
        wrapper.setProhibition(false);
        LOGGER.log(Level.INFO, "Success to open consumption, "
                        + "consumer instance name : {0}, consumer group : {1}, topic : {2}",
                new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(), wrapper.getSubscribedTopics()});
    }

    private static void resumeAssignedConsumer(DefaultLitePullConsumerWrapper wrapper) {
        wrapper.getPullConsumer().assign(wrapper.getMessageQueues());
        LOGGER.log(Level.INFO, "Success to open consumption, "
                        + "consumer instance name : {0}, consumer group : {1}, message queue : {2}",
                new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(),
                        wrapper.getMessageQueues()});
    }

    /**
     * 添加PullConsumer包装类实例
     *
     * @param pullConsumer pullConsumer实例
     */
    public static void cachePullConsumer(DefaultLitePullConsumer pullConsumer) {
        Optional pullConsumerWrapperOptional = RocketMqWrapperUtils
                .wrapPullConsumer(pullConsumer);
        if (pullConsumerWrapperOptional.isPresent()) {
            RocketMqConsumerCache.PULL_CONSUMERS_CACHE.put(pullConsumer.hashCode(), pullConsumerWrapperOptional.get());
            LOGGER.log(Level.INFO, "Success to cache consumer, "
                            + "consumer instance name : {0}, consumer group : {1}, topic : {2}",
                    new Object[]{pullConsumer.getInstanceName(), pullConsumer.getConsumerGroup(),
                            pullConsumerWrapperOptional.get().getSubscribedTopics()});
            return;
        }
        LOGGER.log(Level.SEVERE, "Fail to cache consumer, consumer instance name : {0}, consumer group : {1}",
                new Object[]{pullConsumer.getInstanceName(), pullConsumer.getConsumerGroup()});
    }

    /**
     * 移除PullConsumer包装类实例
     *
     * @param pullConsumer pullConsumer实例
     */
    public static void removePullConsumer(DefaultLitePullConsumer pullConsumer) {
        int hashCode = pullConsumer.hashCode();
        DefaultLitePullConsumerWrapper wrapper = RocketMqConsumerCache.PULL_CONSUMERS_CACHE
                .get(hashCode);
        if (wrapper != null) {
            RocketMqConsumerCache.PULL_CONSUMERS_CACHE.remove(hashCode);
            LOGGER.log(Level.INFO, "Success to remove consumer, consumer instance name : {0}, consumer group "
                            + ": {1}, topic : {2}",
                    new Object[]{wrapper.getInstanceName(), wrapper.getConsumerGroup(),
                            wrapper.getSubscribedTopics()});
        }
    }

    /**
     * 获取PullConsumer的包装类实例缓存
     *
     * @param pullConsumer pull消费者实例
     * @return PullConsumer包装类实例
     */
    public static DefaultLitePullConsumerWrapper getPullConsumerWrapper(
            DefaultLitePullConsumer pullConsumer) {
        return RocketMqConsumerCache.PULL_CONSUMERS_CACHE.get(pullConsumer.hashCode());
    }

    /**
     * 获取PullConsumer缓存
     *
     * @return PullConsumer缓存
     */
    public static Map getPullConsumerCache() {
        return RocketMqConsumerCache.PULL_CONSUMERS_CACHE;
    }

    private static void initExecutor() {
        if (executor == null) {
            synchronized (RocketMqPushConsumerController.class) {
                if (executor == null) {
                    executor = new ThreadPoolExecutor(THREAD_SIZE, THREAD_SIZE, THREAD_KEEP_ALIVE_TIME,
                            TimeUnit.SECONDS, new ArrayBlockingQueue<>(THREAD_QUEUE_CAPACITY));
                    executor.allowCoreThreadTimeOut(true);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy