RocketMQ最佳实践之千问千答

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/FENGQIYUNRAN/article/details/102597150

Q1:

同一个ConsumerGroup组的所有consumer能均衡消费同一个topic的所有queue吗?

A1:

queue的总数 = master机器个数 * 指定topic的queue个数
如果某个topic有4个queue,在有两台master机器场景下, 存在6个consumer,那么第一个、第二个Consumer消费2个队列,其余consumer消费1个队列。
如果Consumer 超过队列数量,那么多余的Consumer将不能消费消息。

具体在表格上体现如下:

总队列数量 Consumer数量 Reblance结果
4 6 C1 - C2:2 C3 - C6:1
3 6 C1 - C6:1
2 6 C1 - C4:1 C5 - C6:0

得出结果,可以实现queue均匀消费。

Q2:

多个不相同的ConsumerGroup组的所有consumer消费同一个topic的消息的消费情况?

A2:

以两组为例,两组ConsumerGroup的consumer实例均消费了同一个topic的所有消息,包括消息的顺序、msgId、queueId等等都是完全一致的

Q3:

如何正确实现一个ConsumerGroup同时消费多个topic,比如同时消费3个topic?

A3:

首先,consumer的维度是topic,如果需要一个消费组消费这三个topic,每新建一个consumer实例,这个consumer实例应该用这个消费组同时订阅三个topic;如果你新建两个consumer实例,这个两个consumer实例都应该用这个消费组同时订阅三个topic。记得每个jvm进程,最好保证建一个consumer实例。

Q4:

如何正确实现一个Jvm进程启动多个mq实例?

A4:

只需要在获取mq实例的时候,设置不同InstanceName即可获取不同的多个mq实例
具体原理如下:
在发送消息的时候加上断点,查看product对象,发现namesrvAddr是对的,不同的mq实例发送消息时namesrvAddr是不同的。
无可奈何之下,查看了下rocketmq的源码,发现在mq执行start时,会去取一个MQClientInstance实例
在这里插入图片描述
再进去就是:
在这里插入图片描述
它会去factoryTable(一个ConcurrentMap)取MQClientInstance,取的时候用的key时clientId,然后查看clientId时从clientConfig.buildMQClientId()方法获取的,进入clientConfig.buildMQClientId()方法:
在这里插入图片描述
发现clientId是由clientIp和InstanceName组成的,如果InstanceName没有设置,每次返回都是同一个clinetId,导致mq每次拿到的MQClientInstance都是同一个。

Q5:

Topic、Tag、GroupName的设计初衷是什么?

A5:

Topic是生产者在发送消息和消费者在拉取消息的类别。Topic与生产者和消费者之间的关系非常松散。具体来说,一个Topic可能有0个,一个或多个生产者向它发送消息;相反,一个生产者可以发送不同类型Topic的消息。类似的,消费者组可以订阅一个或多个主题,只要该组的实例保持其订阅一致即可

Topic可以理解为第一级消息类型,类比于书的标题。在应用系统中,一个Topic标识为一类消息类型,比如交易信息。
在Producer中使用Topic:

Message msg = new Message("TopicTest" /* Topic */,
                    "TagA",("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));

在Consumer中订阅Topic:

consumer.subscribe("TopicTest", "*");

Tag 标签,是子主题,为用户提供了额外的灵活性。有了标签,来自同一业务模块的具有不同目的的消息可以具有相同的主题和不同的标记。标签有助于保持代码的清晰和连贯,同时标签也方便RocketMQ提供的查询功能。

Tag可以理解为第二级消息类型,类比于书的目录,方便检索使用消息。在应用系统中,一个Tag标识为一类消息中的二级分类,比如交易信息下的交易创建、交易完成。

在Producer中使用Tag:

 Message msg = new Message("TopicTest",
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));

在Consumer中订阅Tag:

consumer.subscribe("TopicTest", "TagA||TagB");// * 代表订阅Topic下的所有消息

GroupName代表具有相同角色的生产者组合或消费者组合,称为生产者组或消费者组。

作用是在集群HA的情况下,一个生产者down之后,本地事务回滚后,可以继续联系该组下的另外一个生产者实例,不至于导致业务走不下去。在消费者组中,可以实现消息消费的负载均衡和消息容错目标。
另外,有了GroupName,在集群下,动态扩展容量很方便。只需要在新加的机器中,配置相同的GroupName。启动后,就立即能加入到所在的群组中,参与消息生产或消费。

在Producer中使用GroupName:

DefaultMQProducer producer = new DefaultMQProducer("group_name_1");// 使用GroupName来初始化Producer,如果不指定,就会使用默认的名字:DEFAULT_PRODUCER

在Consumer中使用GroupName:

  DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_name_1");// 使用GroupName来初始化Consumer,如果不指定,就会使用默认的名字:DEFAULT_CONSUMER

Q6:

假设现在有一个producer和两个consumer,分别为consumer1和consumer2,consumer1和consumer2依据topic的个数初始化DefaultMQPushConsumer。分析下如下场景的消费情况?

A6:

  1. consumer1和consumer2配置相同的单个consumerGroup,单个topic和tags。 此种场景下consumer1与consumer2能正常消费到producer发送的所有消息,并遵循broker端的lb策略,consumer1与consumer2消费到的消息之和等于producer发送的消息数目
  2. consumer1和consumer2配置相同的单个consumerGroup,多个topic和tags。 此种场景下,若客户端初始化DefaultMQPushConsumer依据topic的数量进行初始化,则consumer1与consumer2与broker建立心跳链接时会相互覆盖相同consumerGroup下的消费topic信息,这样会导致部分消息消费不了。详见RecketMQ-同一个订阅组下有多个Topic,消息能发送到Topic中,但无法被监听到
    若想保证消费同一consumerGroup下的多个topic和tag的消息,客户端在初始化时需要依据consumerGroup的数量只初始化一个DefaultMQPushConsumer实例,之后在设置consumer订阅的topic和tag时,多次设置topic和tag的组合,即
    this.consumer.subscribe(topicA, tagsA);        
    this.consumer.subscribe(topicB, tagsB);
    ...
    // DefaultMQPushConsumerImpl中关于subscribe源码实现        
     public void subscribe(String topic, String subExpression)throws MQClientException {        
            try {
                    SubscriptionData subscriptionData =                         FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(), topic, subExpression);
                    this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
                    if (this.mQClientFactory !=null) {
                        this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
                    }
            }catch (Exception var4) {
                    throw new MQClientException("subscription exception", var4);
            }
    }

由DefaultMQPushConsumerImpl中对subscribe的实现源码可知,不同topic和对应的tag以key-value的形式存在于subsCriptionInner中,该对象的类型为ConcurrentHashMap,也就意味着如果重复注册相同topic,会覆盖之前关于相同topic的订阅,这点在使用单个DefaultMQPushConsumer要注意。
3. consumer1和consumer2配置相同的多个consumerGroup,topic和tags与consumerGroup对应。 此种情况相当于多个情况1的实现,消费者consumer1和consumer2可以正确消费所有消息。
4. consumer1和consumer2配置相同的单个consumerGroup,单个topic和多个tag。 此种情况要说一下为啥RocketMQ对consumerGroup和topic都不追加s定义,只对tag用复数词修饰。这是因为tags表示的意思为一个或多个tag的意思,RocketMQ自身会根据tags字符串中的分隔符“|”来订阅不同的tag。实际上在我们定义topic和tags时也就默认了一对多的情况,并不用针对多个tag设置多个相同名字的topic,这样反而会出现情况2中的tag覆盖,丢失部分消息。
所以此种情况下与1情况相同,consumer1和consumer2能正确消费所有消息。
5. consumer1和consumer2配置不同的多个consumerGroup,相同的单个topic和tags。此种情况用于consumer1和consumer2需要同时消费所有producer发出的消息。consumer1和consumer2消费的消息之和等于producer生产的消息的2倍,所有发出的消息都会同时出现在consumer1和consumer2的消费队列中

参考

Rocketmq集群consumer消费(同组及不同组)
Rocketmq同项目多个生产者多个消费者问题)
RocketMQ中Topic、Tag、GroupName的设计初衷)
RocketMQ消费者配置消费场景解析)

猜你喜欢

转载自blog.csdn.net/FENGQIYUNRAN/article/details/102597150
今日推荐