RocketMQ源码(七):consumer的启动

RocketMQ源码(一):NameServer的启动
RocketMQ源码(二):broker的启动(一)
RocketMQ源码(三):broker的启动(二)
RocketMQ源码(四):producer的启动
RocketMQ源码(五):producer发送消息
RocketMQ源码(六):broker接收消息
RocketMQ源码(八):consumer消息拉取(一)
RocketMQ源码(九):consumer消息拉取(二)

consumer

consumer一共有三个实现类

  • DefaultMQPullConsumer(deprecated)
  • DefaultLitePullConsumer
  • DefaultMQPushConsumer
    前两种是通过pull的方式去拉取消息,第三种是push的方式。但是,其本质都是通过pull的方式拉取消息,也就是客户端去broker获取消息。
    因为push的模式下虽然消息的实时性比较好,但是如果要考虑到客户端的消费速度慢于broker的投递速度导致消息在内存中积压,就会使程序变得很复杂。因此一般情况下都是使用pull模式来消费数据。
    DefaultMQPullConsumer在源码中已经被标注为Deprecated,根据注释看到DefaultMQPullConsumer将在2022年移除,因此这里主要是分析DefaultLitePullConsumer的工作原理。
    DefaultLitePullConsumer相比DefaultMQPullConsumer整体上要简便一点,但是缺少了消息重试机制,只能通过seek()方法重置consumerOffset来实现消息的重复消费,而DefaultMQPullConsumer有sendMessageBack。litePull相比push模式以及老的pull方法比较大的一个区别是一个消费者默认可以用20个线程去拉取消息。
public class LitePullConsumerSubscribe {

    public static volatile boolean running = true;

    public static void main(String[] args) throws Exception {
        DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer("lite_pull_consumer_test");
        litePullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        litePullConsumer.subscribe("TopicTest", "*");
        litePullConsumer.setNamesrvAddr("127.0.0.1:9876");
        litePullConsumer.start();
        try {
            while (running) {
                List<MessageExt> messageExts = litePullConsumer.poll();
                System.out.printf("%s%n", messageExts);
            }
        } finally {
            litePullConsumer.shutdown();
        }
    }
}

从org.apache.rocketmq.example.simple.LitePullConsumerSubscribe可以看到如果使用DefaultLitePullConsumer进行消息的消费,接下来就对整个过程中使用到的一些方法进行分析。
首先来认识一下DefaultLitePullConsumer

private final DefaultLitePullConsumerImpl defaultLitePullConsumerImpl;

// 消息消费组
private String consumerGroup;

// 长轮询模式下,consumer请求在broker端最长挂起时间
private long brokerSuspendMaxTimeMillis = 1000 * 20;

// 消息消费者拉取消息最大的超时时间,必须大于 brokerSuspendMaxTimeMillis
private long consumerTimeoutMillisWhenSuspend = 1000 * 30;

// 客户端与 Broker 建立网络连接的最大超时时间
private long consumerPullTimeoutMillis = 1000 * 10;

// 消息组消费模型:集群模式或者广播模式
private MessageModel messageModel = MessageModel.CLUSTERING;

// 消息消费负载队列变更事件
private MessageQueueListener messageQueueListener;

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

// 消息消费队列负载策略
private AllocateMessageQueueStrategy allocateMessageQueueStrategy = new AllocateMessageQueueAveragely();

private boolean unitMode = false;

// 设置是否提交消息消费进度
private boolean autoCommit = true;

// 每一个消费者拉取消息的线程数,相比于push模式很大的区别点
private int pullThreadNums = 20;

private static final long MIN_AUTOCOMMIT_INTERVAL_MILLIS = 1000;

// 自动汇报消息位点的间隔时间
private long autoCommitIntervalMillis = 5 * 1000;

// 一次消息拉取最多返回的消息条数
private int pullBatchSize = 10;

// 单个队列积压的消息条数触发限流的阔值
private long pullThresholdForAll = 10000;

// 单个消息处理队列中最大消息偏移量与最小偏移量的差值触发限流的阔值
private int consumeMaxSpan = 2000;

// 单个队列积压的消息总大小触发限流的阔值
private int pullThresholdForQueue = 1000;

// 单个队列挤压的消息总大小触发限流的阔值
private int pullThresholdSizeForQueue = 100;

// 一次消息拉取默认超时时间
private long pollTimeoutMillis = 1000 * 5;

// topic 路由信息更新频率
private long topicMetadataCheckIntervalMillis = 30 * 1000;

// 初次启动时从什么位置开始消费
private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;

// 如果初次启动时 consumeFromWhere 策略选择为基于时间戳,通过该属性设置定位的时间
private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30));

可以看到的是DefaultLitePullConsumer基本是设置了一些属性,具体的功能实现都是调用的DefaultLitePullConsumerImpl,接下来看看它的一些主要方法

// 启动consumer
@Override
public void start() throws MQClientException {
    setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
    this.defaultLitePullConsumerImpl.start();
}
// 关闭consumer
@Override
public void shutdown() {
    this.defaultLitePullConsumerImpl.shutdown();
}
// 使用subExpression表达式订阅topic,根据负载均衡算法计算分配的队列
@Override
public void subscribe(String topic, String subExpression) throws MQClientException {
    this.defaultLitePullConsumerImpl.subscribe(withNamespace(topic), subExpression);
}
// 强制分配消费队列,跳过负载均衡算法
@Override
public void assign(Collection<MessageQueue> messageQueues) {
    defaultLitePullConsumerImpl.assign(queuesWithNamespace(messageQueues));
}
// 从内存中获取消息
@Override
public List<MessageExt> poll() {
    return defaultLitePullConsumerImpl.poll(this.getPollTimeoutMillis());
}
// 重置消费offset
@Override
public void seek(MessageQueue messageQueue, long offset) throws MQClientException {
    this.defaultLitePullConsumerImpl.seek(queueWithNamespace(messageQueue), offset);
}
// 停止从broker拉取对应MessageQueue的消息
@Override
public void pause(Collection<MessageQueue> messageQueues) {
    this.defaultLitePullConsumerImpl.pause(queuesWithNamespace(messageQueues));
}
// 从NameServer获取topic下所有的messageQueue
@Override
public Collection<MessageQueue> fetchMessageQueues(String topic) throws MQClientException {
    return this.defaultLitePullConsumerImpl.fetchMessageQueues(withNamespace(topic));
}
// 监听topic的messageQueue变化事件
@Override
public void registerTopicMessageQueueChangeListener(String topic,
    TopicMessageQueueChangeListener topicMessageQueueChangeListener) throws MQClientException {
    this.defaultLitePullConsumerImpl.registerTopicMessageQueueChangeListener(withNamespace(topic), topicMessageQueueChangeListener);
}
// 异步提交所有队列的offset
@Override
public void commitSync() {
    this.defaultLitePullConsumerImpl.commitAll();
}
// 设置是否自动提交offset
@Override
public void setAutoCommit(boolean autoCommit) {
    this.autoCommit = autoCommit;
}

对DefaultLitePullConsumer有了基本认识后,还是根据LitePullConsumerSubscribe这个demo类来分析DefaultLitePullConsumer的一些常规操作流程
这里主要分成两部分来看:

  • consumer的启动
  • 消息的拉取
    这里先看看consumer的启动过程,首先看看DefaultLitePullConsumer的实例化方法。
public DefaultLitePullConsumer(final String consumerGroup) {
    this(null, consumerGroup, null);
}

public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook) {
    this.namespace = namespace;
    this.consumerGroup = consumerGroup;
    defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook);
}

public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePullConsumer, final RPCHook rpcHook) {
    this.defaultLitePullConsumer = defaultLitePullConsumer;
    this.rpcHook = rpcHook;
    // 消息拉取线程池
    this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
        this.defaultLitePullConsumer.getPullThreadNums(),
        new ThreadFactoryImpl("PullMsgThread-" + this.defaultLitePullConsumer.getConsumerGroup())
    );
    // 监视topic的messageQueue变化线程
    this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "MonitorMessageQueueChangeThread");
        }
    });
    // 发生异常时拉取消息的延迟时间
    this.pullTimeDelayMillsWhenException = defaultLitePullConsumer.getPullTimeDelayMillsWhenException();
}

接下来是调用DefaultLitePullConsumer的subscribe方法,订阅某个topic的消息

public synchronized void subscribe(String topic, String subExpression) throws MQClientException {
    try {
        if (topic == null || topic.equals("")) {
            throw new IllegalArgumentException("Topic can not be null or empty.");
        }
        // 设置订阅类型
        setSubscriptionType(SubscriptionType.SUBSCRIBE);
        // 创建订阅信息
        SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(defaultLitePullConsumer.getConsumerGroup(),
            topic, subExpression);
        // 保存订阅关系
        this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
        // 注册 messageQueue 的变化监听,触发消息拉取
        this.defaultLitePullConsumer.setMessageQueueListener(new MessageQueueListenerImpl());
        assignedMessageQueue.setRebalanceImpl(this.rebalanceImpl);
        if (serviceState == ServiceState.RUNNING) {
            this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
            // 再次确认是否更新订阅信息
            updateTopicSubscribeInfoWhenSubscriptionChanged();
        }
    } catch (Exception e) {
        throw new MQClientException("subscribe exception", e);
    }
}

SubscriptionType一共有三种

  • NONE 未知
  • SUBSCRIBE 订阅,负载均衡算法分配消费的队列
  • ASSIGN 分配,指定消费队列,跳过负载均衡算法
    首先看下是怎么创建订阅信息的
public static SubscriptionData buildSubscriptionData(final String consumerGroup, String topic,
    String subString) throws Exception {
    // 创建SubscriptionData,保存订阅信息
    SubscriptionData subscriptionData = new SubscriptionData();
    subscriptionData.setTopic(topic);
    subscriptionData.setSubString(subString);
    // 处理订阅表达式
    if (null == subString || subString.equals(SubscriptionData.SUB_ALL) || subString.length() == 0) {
        subscriptionData.setSubString(SubscriptionData.SUB_ALL);
    } else {
        // 根据 || 分割订阅的多个tag
        String[] tags = subString.split("\\|\\|");
        if (tags.length > 0) {
            for (String tag : tags) {
                if (tag.length() > 0) {
                    String trimString = tag.trim();
                    // 保存tag和tag的hashcode
                    if (trimString.length() > 0) {
                        subscriptionData.getTagsSet().add(trimString);
                        subscriptionData.getCodeSet().add(trimString.hashCode());
                    }
                }
            }
        } else {
            throw new Exception("subString split error");
        }
    }

    return subscriptionData;
}

方法比较简单
回到subscribe中,这里定义了一个MessageQueueListenerImpl,这个类就是消息拉取的具体实现,后续会用到
这里还有个判断逻辑是serviceState == ServiceState.RUNNING是,代表着程序先调用了DefaultLitePullConsumer的start方法,然后才调用了subscribe,这个时候就需要向broker发送心跳,并且更新订阅关系。
接下来看看DefaultLitePullConsumer的start方法

public void start() throws MQClientException {
    setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
    this.defaultLitePullConsumerImpl.start();
}

public synchronized void start() throws MQClientException {
    switch (this.serviceState) {
        case CREATE_JUST:
            // 和producer启动一样,先设置状态为 启动失败
            this.serviceState = ServiceState.START_FAILED;
            // 校验一些基础配置
            this.checkConfig();
            // 如果是集群模式且instanceName=DEFAULT,修改instanceName为pid
            if (this.defaultLitePullConsumer.getMessageModel() == MessageModel.CLUSTERING) {
                this.defaultLitePullConsumer.changeInstanceNameToPID();
            }
            // 初始化 MQClientFactory
            initMQClientFactory();
            // 初始化 RebalanceImpl,主要是属性设置
            initRebalanceImpl();
            // 实例化 PullAPIWrapper
            initPullAPIWrapper();
            // 实例化 offsetStore
            initOffsetStore();
            // 同 producer 的启动过程
            mQClientFactory.start();
            // 从 NameServer 获取 messageQueue,和本地的比较
            // 如果变化则通知 TopicMessageQueueChangeListener
            startScheduleTask();

            this.serviceState = ServiceState.RUNNING;

            log.info("the consumer [{}] start OK", this.defaultLitePullConsumer.getConsumerGroup());
            // 用于处理 subscribe 或 assign 方法在 start 前就已经执行
            // 并且保存 topic 的 messageQueue 信息到 messageQueuesForTopic
            operateAfterRunning();

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

这个过程中初始化了一些属性,initMQClientFactory和producer启动中的一样,initPullAPIWrapper是用于注册钩子方法,因此继续向下看initRebalanceImpl方法

private void initRebalanceImpl() {
    this.rebalanceImpl.setConsumerGroup(this.defaultLitePullConsumer.getConsumerGroup());
    this.rebalanceImpl.setMessageModel(this.defaultLitePullConsumer.getMessageModel());
    this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultLitePullConsumer.getAllocateMessageQueueStrategy());
    this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);
}

private void initOffsetStore() throws MQClientException {
    if (this.defaultLitePullConsumer.getOffsetStore() != null) {
        this.offsetStore = this.defaultLitePullConsumer.getOffsetStore();
    } else {
        switch (this.defaultLitePullConsumer.getMessageModel()) {
            case BROADCASTING:
                this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup());
                break;
            case CLUSTERING:
                this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultLitePullConsumer.getConsumerGroup());
                break;
            default:
                break;
        }
        this.defaultLitePullConsumer.setOffsetStore(this.offsetStore);
    }
    // 用于 LocalFileOffsetStore 从本地磁盘加载消费进度
    this.offsetStore.load();
}

初始化了一个rebalanceImpl和offsetStore,接下来的重头戏是mQClientFactory的start方法

public void start() throws MQClientException {
    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.mQClientAPIImpl.fetchNameServerAddr();
                }
                // 开启 netty 服务
                this.mQClientAPIImpl.start();
                // 开启一些定时任务
                this.startScheduledTask();
                // 开启拉取消息服务,PUSH模式的消费者使用,pull模式下线程会一直等待
                this.pullMessageService.start();
                // consumer平衡消费组和消息队列的关系
                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 START_FAILED:
                throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
            default:
                break;
        }
    }
}

startScheduledTask开启的一批定时任务中有一个定时器是

// 持久化 consumer 的消费进度
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

    @Override
    public void run() {
        try {
            MQClientInstance.this.persistAllConsumerOffset();
        } catch (Exception e) {
            log.error("ScheduledTask persistAllConsumerOffset exception", e);
        }
    }
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);

继续分析persistAllConsumerOffset方法

private void persistAllConsumerOffset() {
    // 获取所有的消费者
    Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<String, MQConsumerInner> entry = it.next();
        MQConsumerInner impl = entry.getValue();
        impl.persistConsumerOffset();
    }
}

这里主要看下DefaultLitePullConsumerImpl对persistConsumerOffset的实现

public void persistConsumerOffset() {
    try {
        checkServiceState();
        Set<MessageQueue> mqs = new HashSet<MessageQueue>();
        // 判断当前 consumer 的订阅类型
        // 如果是订阅就取处理中的队列
        if (this.subscriptionType == SubscriptionType.SUBSCRIBE) {
            Set<MessageQueue> allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();
            mqs.addAll(allocateMq);
        }
        // 如果是分配的
        else if (this.subscriptionType == SubscriptionType.ASSIGN) {
            Set<MessageQueue> assignedMessageQueue = this.assignedMessageQueue.getAssignedMessageQueues();
            mqs.addAll(assignedMessageQueue);
        }
        // 持久化队列消费 offset 到远端或者是本地
        this.offsetStore.persistAll(mqs);
    } catch (Exception e) {
        log.error("Persist consumer offset error for group: {} ", this.defaultLitePullConsumer.getConsumerGroup(), e);
    }
}

根据subscriptionType获取到对应的MessageQueue集合后,调用offsetStore.persistAll
集群模式下offsetStore就是RemoteBrokerOffsetStore,所以继续看RemoteBrokerOffsetStore对persistAll的实现

public void persistAll(Set<MessageQueue> mqs) {
    if (null == mqs || mqs.isEmpty())
        return;
    // 持久化消息队列
    final HashSet<MessageQueue> unusedMQ = new HashSet<MessageQueue>();
    // offsetTable中的数据来源是当DefaultLitePullConsumer设置autoCommit为true是,在poll方法中会自动更新offsetTable中的offset
    for (Map.Entry<MessageQueue, AtomicLong> entry : this.offsetTable.entrySet()) {
        MessageQueue mq = entry.getKey();
        AtomicLong offset = entry.getValue();
        if (offset != null) {
            if (mqs.contains(mq)) {
                try {
                    // 把offset更新到broker去
                    this.updateConsumeOffsetToBroker(mq, offset.get());
                    log.info("[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
                        this.groupName,
                        this.mQClientFactory.getClientId(),
                        mq,
                        offset.get());
                } catch (Exception e) {
                    log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e);
                }
            } else {
                unusedMQ.add(mq);
            }
        }
    }
    // 移除不适用的消息队列
    if (!unusedMQ.isEmpty()) {
        for (MessageQueue mq : unusedMQ) {
            this.offsetTable.remove(mq);
            log.info("remove unused mq, {}, {}", mq, this.groupName);
        }
    }
}

这里就是自动提交Offset的相关实现,继续看是如何实现的

private void updateConsumeOffsetToBroker(MessageQueue mq, long offset) throws RemotingException,
    MQBrokerException, InterruptedException, MQClientException {
    updateConsumeOffsetToBroker(mq, offset, true);
}

public void updateConsumeOffsetToBroker(MessageQueue mq, long offset, boolean isOneway) throws RemotingException,
    MQBrokerException, InterruptedException, MQClientException {
    // 查询Broker地址信息
    FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
    // 如果没有查询到,就从NameServer获取topic的路由信息
    if (null == findBrokerResult) {
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
        findBrokerResult = this.mQClientFactory.findBrokerAddressInAdmin(mq.getBrokerName());
    }

    if (findBrokerResult != null) {
        UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader();
        requestHeader.setTopic(mq.getTopic());
        requestHeader.setConsumerGroup(this.groupName);
        requestHeader.setQueueId(mq.getQueueId());
        requestHeader.setCommitOffset(offset);

        if (isOneway) {
            this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffsetOneway(
                findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
        } else {
            this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffset(
                findBrokerResult.getBrokerAddr(), requestHeader, 1000 * 5);
        }
    } else {
        throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
    }
}

public void updateConsumerOffsetOneway(
    final String addr,
    final UpdateConsumerOffsetRequestHeader requestHeader,
    final long timeoutMillis
) throws RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException,
    InterruptedException {
    RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader);

    this.remotingClient.invokeOneway(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr), request, timeoutMillis);
}

可以看到最终是向broker发送了一个RequestCode=UPDATE_CONSUMER_OFFSET的请求
回到MQClientInstance的start方法中,接下来是pullMessageService.start()和push模式的消息拉取相关,暂不分析
然后是rebalanceService.start()

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

    while (!this.isStopped()) {
        // 默认间隔20s执行一次
        this.waitForRunning(waitInterval);
        this.mqClientFactory.doRebalance();
    }

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

// 循环遍历每个消费组获取 MQConsumeInner 对象
// 并执行其 doRebalance 方法
public void doRebalance() {
    for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
        MQConsumerInner impl = entry.getValue();
        if (impl != null) {
            try {
                impl.doRebalance();
            } catch (Throwable e) {
                log.error("doRebalance exception", e);
            }
        }
    }
}

接下来继续看DefaultLitePullConsumerImpl的doRebalance实现

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

// isOrder这里是false
public void doRebalance(final boolean isOrder) {
    // 获取topic的订阅关系
    // 如果没有执行DefaultLitePullConsumer的subscribe方法,也就不会有订阅关系,也就不会负载均衡
    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);
                }
            }
        }
    }
    // 移除 MessageQueue,如果 MesageQueue 的 topic 不在订阅的主题中
    this.truncateMessageQueueNotMyTopic();
}

接下来继续看负载均衡的具体实现

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: {
            // 主题的消息消费队列
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            // 主题的当前消费组的消费者id列表
            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);
            }
            // 主要是对主题的消息队列排序、消费者ID进行排序,然后利用分配算法,计算当前消费者ID(mqClient.clientId) 分配出需要拉取的消息队列
            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, 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);
                    // 通知 MessageQueueListener ,分配的队列信息变更
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }
            }
            break;
        }
        default:
            break;
    }
}

这里主要关注集群模式下的处理方式,主要做了三件事

  • 根据负载均衡算法,获取分配到的队列
  • 判断负责消费的队列信息是否变更
  • 发布消费的队列信息变更事件
    负载均衡算法具体不分析,接下来看看updateProcessQueueTableInRebalance
private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
    final boolean isOrder) {
    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();
        // 只处理 mq 的主题与该主题相关的 ProcessQueue
        if (mq.getTopic().equals(topic)) {
            // 如果 mq 不在当期主题的处理范围内
            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()) {
                    // 如果是 pull 模式
                    case CONSUME_ACTIVELY:
                        break;
                    // 如果是 push 模式
                    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;
                }
            }
        }
    }
    // 增加 不在processQueueTable && 存在于mqSet 里的消息队列。
    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;
            }
            // 在内存中移除 MessageQueue 的 offerset
            this.removeDirtyOffset(mq);
            ProcessQueue pq = new ProcessQueue();
            // 计算下一个拉取偏移量
            long nextOffset = this.computePullFromWhere(mq);
            if (nextOffset >= 0) {
                // 创建一个拉取任务
                ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
                if (pre != null) {
                    log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
                } else {
                    log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
                    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);
            }
        }
    }
    // 立刻执行拉取消息请求
    this.dispatchPullRequest(pullRequestList);

    return changed;
}

值得一提的是computePullFromWhere方法,看下这里是如何得知下一次消费开始的offset

public long computePullFromWhere(MessageQueue mq) {
    // 获取consumeFromWhere配置
    ConsumeFromWhere consumeFromWhere = litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeFromWhere();
    long result = -1;
    switch (consumeFromWhere) {
        case CONSUME_FROM_LAST_OFFSET: {
            long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            } else if (-1 == lastOffset) {
                // 重试队列
                if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { // First start, no offset
                    result = 0L;
                }
                // 否则获取最大的offset
                else {
                    try {
                        result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
                    } catch (MQClientException e) {
                        result = -1;
                    }
                }
            } else {
                result = -1;
            }
            break;
        }
        case CONSUME_FROM_FIRST_OFFSET: {
            long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            } else if (-1 == lastOffset) {
                result = 0L;
            } else {
                result = -1;
            }
            break;
        }
        case CONSUME_FROM_TIMESTAMP: {
            long lastOffset = litePullConsumerImpl.getOffsetStore().readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            } else if (-1 == lastOffset) {
                if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    try {
                        result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
                    } catch (MQClientException e) {
                        result = -1;
                    }
                } else {
                    try {
                        long timestamp = UtilAll.parseDate(this.litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeTimestamp(),
                            UtilAll.YYYYMMDDHHMMSS).getTime();
                        result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp);
                    } catch (MQClientException e) {
                        result = -1;
                    }
                }
            } else {
                result = -1;
            }
            break;
        }
    }
    return result;
}

public long readOffset(final MessageQueue mq, final ReadOffsetType type) {
    if (mq != null) {
        switch (type) {
            // 先从内存中读取,如果内存中不存在,再尝试从远程中读取
            case MEMORY_FIRST_THEN_STORE:
            // 从内存中读取
            case READ_FROM_MEMORY: {
                AtomicLong offset = this.offsetTable.get(mq);
                if (offset != null) {
                    return offset.get();
                } else if (ReadOffsetType.READ_FROM_MEMORY == type) {
                    return -1;
                }
            }
            // 从远程读取然后更新到本地
            case READ_FROM_STORE: {
                try {
                    long brokerOffset = this.fetchConsumeOffsetFromBroker(mq);
                    AtomicLong offset = new AtomicLong(brokerOffset);
                    this.updateOffset(mq, offset.get(), false);
                    return brokerOffset;
                }
                // No offset in broker
                catch (MQBrokerException e) {
                    return -1;
                }
                //Other exceptions
                catch (Exception e) {
                    log.warn("fetchConsumeOffsetFromBroker exception, " + mq, e);
                    return -2;
                }
            }
            default:
                break;
        }
    }

    return -1;
}

可以看到不管是哪种consumeFromWhere配置,都是默认先从本地或broker获取最新的offset消费进度。不一样的是没有获取到消费进度时,每种consumeFromWhere的处理方式。
回到RebalanceImpl的rebalanceByTopic方法,继续看RebalanceLitePullImpl的messageQueueChanged方法

public void messageQueueChanged(String topic, Set<MessageQueue> mqAll, Set<MessageQueue> mqDivided) {
    MessageQueueListener messageQueueListener = this.litePullConsumerImpl.getDefaultLitePullConsumer().getMessageQueueListener();
    if (messageQueueListener != null) {
        try {
            messageQueueListener.messageQueueChanged(topic, mqAll, mqDivided);
        } catch (Throwable e) {
            log.error("messageQueueChanged exception", e);
        }
    }
}

这里和消息拉取相关,后续分析
到此consumer的启动过程就分析完了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,408评论 4 371
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,690评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,036评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,726评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,123评论 3 296
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,037评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,178评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,964评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,703评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,863评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,333评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,658评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,374评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,195评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,988评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,167评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,970评论 2 279