实验(一)

实验现象

准备

 创建的topicA、topicB,每个都是16个消息队列;

 创建了同一个消费者组:ntm-hxy-group

 默认消息模式:负载均衡

消费者一(clientId1)

先启动一个消费者一:clientId:192.168.31.182@10962#386194072446460

public static void main(String[] args) throws Exception {
    String nameAddress = "192.168.11.180:9876;192.168.11.181:9876;192.168.11.182:9876";
    DefaultMQPushConsumer concurrentlyPushConsumer2 =
            createConcurrentlyPushConsumer("ntm-hxy-group", nameAddress, "topicA", "topicB");
}

 

这个clientId1 订阅了topicA、topicB,属于组ntm-hxy-group

 控制台界面:

这里代表订阅的clientId的数量

 

 上图订阅关系:client1订阅了两个topic,自动添加了一个重试的topic

该消费者组ntm-hxy-group消费队列的情况如下:

这是重试topic,只有一个消息队列,由clientId1消费

 

 

 topicA所有16个队列,都由clientId消费

 

 topicB 所有16个消息队列都由clientId1 消费

 由此可见:两个topic都被这一个clientId1占用了,还有一个重试topic也是被该clinetId1占用

 

 再启动一个消费者(clientId2)

在启动一个clientId2,同一个group,只订阅topicA。clientId:192.168.31.182@11084#386532403737756

控制台界面:

 

 

同一个消费者组group,clientId变为了两个 

 

 topic只有topicA了,也就是最新订阅信息,但是clientId还有两个。

此时,该消费者组ntm-hxy-group消费队列的情况如下:

 

 如上图,重试消息队列消费对象没有变,因为重试topic是服务端(broker)基于group产生的,所以一直和第一个启动的clientId绑定的,也就是重试队列会一直由第一个clientId消费,不会由于心跳引起订阅的改变而改变。

这时刚启动第二个的时候,只有topicA了,并且消息队列的消费对象被负载均衡给了两个clientId

也就是每个clientId可以消费topicA一半的消息,但是此时topicB丢失了,也就是发送给topicB的消息会被堆积,不能被消费了。

默认每30秒发送心跳:

 

 下次clientId1发送心跳时,topicB又订阅上了,此时的情况如下:

 

 这个重试group跟心跳无关,还是clientId1消费没变。

 

topicA还是由两个clientId进行负载均衡

但是新增了订阅topicB,如下:

 

 原因:虽然client1发送心跳时,订阅信息刷新了,topicA和topicB都订阅了,但是消费队列绑定关系是根据group来进行替换的,因为负载均衡时关键两点:

 

①根据topic获取MesssageQueue列表

在 RebalanceImpl 维护了一份 map 结构的本地缓存 topicSubscribeInfoTable,以 topic 维度保存了对应的 MesssageQueue列表;

②根据topic、group获取在线的消费者终端列表(即在线的clientId)

findConsumerIdList 方法接受两个参数:Topic 主题和 ConsumerGroup 消费组

底层通过发送 RequestCode.GET_CONSUMER_LIST_BY_GROUP 请求码的 RemotingCommand 到 Broker 查询在线消费者列表,拿到结果后反序列化

所以对tpicB进行负载均衡时,根据topicB在本地缓存找到了16个队列(即mqSet),然后根据groupName去查找在线的clientId,找到了两个(clientId:192.168.31.182@10962#386194072446460、clientId:192.168.31.182@11084#386532403737756)即cidAll的值,之后拿mqSet、cidAll进行默认的平均负载均衡策略进行负载均衡时,逻辑如下:

 

 这个方法默认负载均衡策略具体实现如下:

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
    List<String> cidAll) {
  
    if (currentCID == null || currentCID.length() < 1) {
        throw new IllegalArgumentException("currentCID is empty");
    }
    if (mqAll == null || mqAll.isEmpty()) {
        throw new IllegalArgumentException("mqAll is null or mqAll empty");
    }
    if (cidAll == null || cidAll.isEmpty()) {
        throw new IllegalArgumentException("cidAll is null or cidAll empty");
    }

    List<MessageQueue> result = new ArrayList<MessageQueue>();
    if (!cidAll.contains(currentCID)) {
        log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}",
            consumerGroup,
            currentCID,
            cidAll);
        return result;
    }

    int index = cidAll.indexOf(currentCID);
    int mod = mqAll.size() % cidAll.size();
    int averageSize =
        mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
            + 1 : mqAll.size() / cidAll.size());
    int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
    int range = Math.min(averageSize, mqAll.size() - startIndex);
    for (int i = 0; i < range; i++) {
        result.add(mqAll.get((startIndex + i) % mqAll.size()));
    }
    return result;
}

所以就是16个队列,被两个clientId平均分配了,clientId1先启动,所以分配topicB前八个消息队列,而clientId2应该分配topicB后八个消息队列的,但是发现topicB并没有被clientId2消费端订阅,所以topicB的后八个消息队列的消费端就丢失了,也就是后八个队列的消息会堆积,不会有消费者消费。

这是通过控制台看到的现象,至于为什么明明topicB分配给了clientId2的消息队列,却由于clientId2没有订阅topicB的原因而导致丢失了消费端的绑定?这个原因在下一个实验通过代码解释。

 

 

 

 

 
posted @ 2022-04-18 14:42  迷走神经  阅读(53)  评论(0编辑  收藏  举报