JavaEE 企业级分布式高级架构师(二十)RocketMQ学习笔记(5)

高级功能

消息查询

介绍

RocketMQ提供了3种消息查询方式:

  • 按照Message Key 查询:消息的key是业务开发在发送消息之前自行指定的,通常会把具有业务含义,区分度高的字段作为消息的key,如用户id,订单id等。
  • 按照Unique Key查询:除了业务开发明确的指定消息中的key,RocketMQ生产者客户端在发送发送消息之前,会自动生成一个UNIQ_KEY,设置到消息的属性中,从逻辑上唯一代表一条消息。
  • 按照Message Id 查询:Message Id 是消息发送后,在Broker端生成的,其包含了Broker的地址,和在CommitLog中的偏移信息,并会将Message Id作为发送结果的一部分进行返回。Message Id中属于精确匹配,可以唯一定位一条消息,不需要使用哈希索引机制,查询效率更高。
  • RocketMQ有意弱化Unique Key与Message Id的区别,对外都称之为Message Id。在通过RocketMQ的命令行工具或管理平台进行查询时,二者可以通用。在根据Unique Key进行查询时,本身是有可能查询到多条消息的,但是查询工具会进行过滤,只会返回一条消息。种种情况导致很多RocketMQ的用户,并未能很好对二者进行区分。
  • 业务开发同学在使用RocketMQ时,应该养成良好的习惯,在发送/消费消息时,将这些信息记录下来,通常是记录到日志文件中,以便在出现问题时进行排查。
// 1、构建消息对象Message
Message msg = new Message(); 
msg.setTopic("TopicA"); 
msg.setKeys("Key1"); 
msg.setBody("message body".getBytes()); 
try{
	// 2、发送消息
	SendResult result = producer.send(msg);
	// 3、打印发送结果
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}
  • 结果中包含Unique Key和Message Id,如下所示:
SendResult [sendStatus=SEND_OK, msgId=0A1427544F4818B4AAC27DD168880000,
offsetMsgId=0A14275400002A9F00000000001F268E, messageQueue=MessageQueue
[topic=TopicTest, brokerName=broker-a, queueId=2], queueOffset=1173]
  • 其中:
    • sendStatus:表示消息发送结果的状态
    • msgId:注意这里的命名虽然是msgId,但实际上其是Unique Key
    • offsetMagId:Broker返回的Message ID 。在后文中,未进行特殊说明的情况下,Message ID总是表示offsetMsgId。
    • messageQueue:消息发送到了哪个的队列。
    • queueOffset:消息在队列中的偏移量,每次发送到一个队列时,offset+1
  • 事实上,用户主动设置的Key以及客户端自动生成的Unique Key,最终都会设置到Message对象的properties属性中,如下图所示

在这里插入图片描述

  • 其中:
    • KEYS:表示用户通过setKeys方法设置的消息key
    • UNIQ_KEY:表示消息发送之前由RocketMQ客户端自动生成的Unique Key。发现了其值与上述打印SendResult结果中的msgId字段的值是一样的,这验证了前面所说的msgId表示的实际上就是Unique Key的说法。
  • 在了解如何主动设置Key,以及如何获取RocketMQ自动生成的Unique Key和Message Id后,就可以利用一些工具来进行查询。

消息查询工具

  • RocketMQ提供了3种方式来根据Message Key、Unique Key、Message Id来查询消息,包括:
    • 命令行工具
    • 管理平台
    • 客户端API
命令行工具
  • RocketMQ自带的mqadmin命令行工具提供了一些子命令,用于查询消息,如下
    在这里插入图片描述
  • 例如,要查询在TopicA中,key为key1的消息:
[root@centos130 rocketmq-all-4.7.1-bin-release]# bin/mqadmin queryMsgByKey -n localhost:9876 -t TopicA -k Key1
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
#Message ID                                        #QID                                  #Offset
C0A80064282518B4AAC28AAB390D0000                      1                                        0
C0A80064290018B4AAC28AB5327D0000                      1                                        1
  • 其中:
    • Message ID:这一列的名字显示有问题,实际上其代表的是Unique Key。
    • QID:表示队列的ID,注意在RocketMQ中唯一定位一个队列时需要topic+brokerName+queueId。这里只显示了queueId,其实并不能知道在哪个Broker上。
    • Offset:消息在在队列中的偏移量。
  • 在查询到Unique Key之后,我们就可以使用另外一个命令:queryMsgByUniqueKey,来查询消息的具体内容
[root@centos130 rocketmq-all-4.7.1-bin-release]# bin/mqadmin queryMsgByUniqueKey -n localhost:9876 -t TopicA -i C0A80064282518B4AAC28AAB390D0000
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
Topic:               TopicA
Tags:                [null]
Keys:                [Key1]
Queue ID:            1
Queue Offset:        0
CommitLog Offset:    0
Reconsume Times:     0
Born Timestamp:      2020-10-27 22:14:37,072
Store Timestamp:     2020-10-27 22:14:37,247
Born Host:           192.168.254.1:59693
Store Host:          192.168.254.130:10931
System Flag:         0
Properties:          {KEYS=Key1, UNIQ_KEY=C0A80064282518B4AAC28AAB390D0000, CLUSTER=rmq-cluster}
Message Body Path:   /tmp/rocketmq/msgbodys/C0A80064282518B4AAC28AAB390D0000
  • 对于消息体的内容,会存储到Message Body Path字段指定到的路径中,可以通过命令查看(仅适用于消息体是字符串):
rocketmq-all-4.7.1-bin-release]# cat /tmp/rocketmq/msgbodys/C0A80064282518B4AAC28AAB390D0000
message body
  • queryMsgByUniqueKey子命令还接收另外两个参数:-g参数用于指定消费者组名称,-d参数指定消费者client id。指定了这两个参数之后,消息将由消费者直接消费,而不是打印在控制台上。
  • 首先,通过consumerStatus命令,查询出消费者组下的client id信息,如:
# 注意:消费者要开启
[root@centos130 rocketmq-all-4.7.1-bin-release]# bin/mqadmin consumerStatus -g consumer-group-1 -n localhost:9876
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
001  192.168.0.100@11295                      V4_7_1               1603811059592/192.168.0.100@11295


Same subscription in the same group of consumer

Rebalance OK
  • 这里显示了消费者组 consumer-group-1 下面只有一个消费者,client id为 192.168.0.100@11295。
  • 接着我们可以在queryMsgByUniqueKey子命令中,添加-g和-d参数,如下所示:
[root@centos130 rocketmq-all-4.7.1-bin-release]# bin/mqadmin queryMsgByUniqueKey -g consumer-group-1 -d 192.168.0.100@11295 -t TopicA -i C0A80064282518B4AAC28AAB390D0000 -n localhost:9876
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
ConsumeMessageDirectlyResult [order=false, autoCommit=true, consumeResult=CR_SUCCESS, remark=null, spentTimeMills=1]
  • 可以看到,这里并没有打印出消息内容,取而代之的是消息消费的结果。
  • 在内部,主要是分为3个步骤来完成让指定消费者来消费这条消息,如下图所示:

在这里插入图片描述

第1步
  • 命令行工具给所有Broker发起QUERY_MESSAGE请求查询消息,因为并不知道UNIQ_KEY这条消息在哪个Broker上,且最多只会返回一条消息,如果超过1条其他会过滤掉;如果查询不到就直接报错。
第2步
  • 根据消息中包含了Store Host信息,也就是消息存储在哪个Broker上,接来下命令行工具会直接给这个Broker发起CONSUME_MESSAGE_DIRECTLY请求,这个请求会携带msgId,group和client id的信息。
第3步
  • Broker接收到这个请求,查询出消息内容后,主动给消费者发送CONSUME_MESSAGE_DIRECTLY通知请求,注意虽然与第2步使用了同一个请求码,但不同的是这个请求中包含了消息体的内容,消费者可直接处理。
  • 注意:这里并不是将消息重新发送到Topic中,否则订阅这个Topic的所有消费者组,都会重新消费这条消息。
管理平台
  • 通过RocketMQ提供的管理平台进来行消息查询。在管理平台的消息一栏,有3个TAB,分别用于:根据Topic时间范围查询、Message Key查询、Message Id查询,下面分别进行介绍:
根据Topic时间范围查询
  • 按 Topic 查询属于范围查询,不推荐使用,因为时间范围内消息很多,不具备区分度。查询时,尽可能设置最为精确的时间区间,以便缩小查询范围,提高速度。最多返回2000条数据。

在这里插入图片描述

Message Key查询
  • 按 Message Key 查询属于模糊查询,仅适用于没有记录 Message ID 但是设置了具有区分度的Message Key的情况。 目前,根据Message Key查询,有一个很大局限性:不能指定时间范围,且最多返回64条数据。如果用户指定的key重复率比较高的话,就有可能搜不到。

在这里插入图片描述

Message Id查询
  • 按 Message ID 查询属于精确查询,速度快,精确匹配,只会返回一条结果,推荐使用。在这里,传入Unique Key,offsetMsgId都可以。

在这里插入图片描述

客户端API
  • 除了通过命令行工具和管理平台,还可以通过客户端API的方式来进行查询,这其实是最本质的方式,命令行工具和管理平台的查询功能都是基于此实现。
  • 在org.apache.rocketmq.client.MQAdmin接口中,定义了以下几个方法用于消息查询:
public interface MQAdmin {
	// ...
	/**
     * Query message according to message id
     *
     * @param offsetMsgId message id
     * @return message
     */
    MessageExt viewMessage(final String offsetMsgId) throws RemotingException, MQBrokerException,
        InterruptedException, MQClientException;
    /**
     * Query messages
     *
     * @param topic message topic
     * @param key message key index word
     * @param maxNum max message number
     * @param begin from when
     * @param end to when
     * @return Instance of QueryResult
     */
    QueryResult queryMessage(final String topic, final String key, final int maxNum, final long begin,
        final long end) throws MQClientException, InterruptedException;

    /**
     * @return The {@code MessageExt} of given msgId
     */
    MessageExt viewMessage(String topic,
        String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException;
}
  • 我们常用的DefaultMQProducer、DefaultMQPushConsumer等,都实现了此接口,因此都具备消息查询的能力。
  • 对于命令行工具,底层实际上是基于MQAdminExt接口的实现来完成的。
  • 相同的查询功能在在多处实现是不是太麻烦了?事实上,这只是对外暴露的接口,在内部,实际上都是基于MQAdminImpl这个类来完成的。
viewMessage方法
  • 两种viewMessage方法重载形式,都只会返回单条消息。下面以生产者搜索为例,讲解如何使用API进行查询:

在这里插入图片描述

  • 如果我们把offsetMsgId当做方法参数传入,也可以查询到相同的结果。这是因为,在方法内部实际上是分两步进行查询的:
    • 先把参数当做offsetMsgId,即Message Id进行查询。
    • 如果失败,再尝试当做Unique Key进行查询。
  • 源码如下所示:DefaultMQProducer#viewMessage(String,String)

在这里插入图片描述

  • 前面提到,Unique Key只是从逻辑上代表一条消息,实际上在Broker端可能存储了多条,因此在当做Unique Key进行查询时,会进行过滤,只取其中一条。
  • 源码如下所示:MQAdminImpl#queryMessageByUniqKey

在这里插入图片描述

  • 我们也可以通过另外只接收一个参数的viewMessage方法进行查询,但是需要注意的是,参数只能是offsetMsgId,不能是Unique Key。

实现原理

  • Unqiue 和 & Message Key都需要利⽤ RocketMQ 的哈希索引机制来完成消息查询, Message Id是在Broker端生成的,其包含了Broker地址和commit Log offset信息,可以精确匹配⼀条消息,查询消息更好。
  • 下⾯分别介绍 Unqiue Key & Message Id的⽣成和作⽤
Unique Key生成
  • Unique Key是⽣产者发送消息之前,由RocketMQ 客户端⾃动⽣成的,具体来说,RocketMQ发送消息之前,最终都要通过以下⽅法:
# DefaultMQProducerImpl#sendKernelImpl
private SendResult sendKernelImpl(final Message msg,
        final MessageQueue mq,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final TopicPublishInfo topicPublishInfo,
        final long timeout) // /省略异常声明 {
    // ...
	try {
		// 如果不是批量消息,则⽣成Unique Key
		if (!(msg instanceof MessageBatch)) {
			MessageClientIDSetter.setUniqID(msg);
		}
	}
	// ...
}
  • 如上所示,如果不是批量消息,会通过MessageClientIDSetter 的 setUniqID ⽅法为消息设置Unique key,该⽅法实现如下所示:
public static void setUniqID(final Message msg) {
    // Unique Key为空的情况下,才进⾏设置
    if (msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX) == null) {
         msg.putProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, createUniqID());
    }
}
  • 如果消息的Unique Key属性为null,就通过createUniqID()⽅法为消息创建⼀个新的Unique Key,并设置到消息属性中。
Unique Key作用
  • 了解Unique Key的作⽤对于我们理解消息重复的原因有很⼤的帮助。RocketMQ并不保证消息投递过程中的Exactly Once语义,即消息只会被精确消费⼀次,需要消费者⾃⼰做幂等。⽽通常导致消息重复消费的原因,主要包括:
    • ⽣产者发送时消息重复
    • 消费者Rebalance时消息重复
  • 导致⽣产者发送重复消息的原因可能是:⼀条消息已被成功发送到服务端并完成持久化,由于⽹络超时此时出现了⽹络闪断或者客户端宕机,导致服务端对客户端应答失败,此时⽣产者将再次尝试发送消息。
  • 在重试发送时,sendKernelImpl会被重复调⽤,意味着setUniqID⽅法会被重复调⽤,不过由于setUniqID⽅法实现中进⾏判空处理,因此重复设置Unique Key。在这种情况下,消费者后续会收到两条内容相同并且 Unique Key 也相同的消息(offsetMsgId不同,因为对Broker来说存储了多次)。

那么消费者如何判断,消费重复是因为重复发送还是Rebalance导致的重复消费呢?
在这里插入图片描述

  • 消费者实现MessageListener接⼝监听到的消息类型是MessageExt,可以将其强制转换为MessageClientExt,之后调⽤getMsgId⽅法获取Unique Key,调⽤getOffsetMsgId获得Message Id。如果多消息的Unique Key相同,但是offsetMsgId不同,则有可能是因为重复发送导致。
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
	for (MessageExt msg:msgs){
		MessageClientExt mct = (MessageClientExt)msg;
		String uniqueKey = mct.getMsgId();
		String messageId = mct.getOffsetMsgId();
	}
	System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
	return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
 }
批量模式下的Unique Key
  • DefaultMQProducer提供了批量发送消息的接⼝:
public SendResult send(Collection<Message> msgs)
  • 在内部,这批消息⾸先会被构建成⼀个MessageBatch对象。在前⾯sendKernelImpl⽅法中我们也看到了,对于MessageBatch对象,并不会设置Unique Key。这是因为在将批量消息转换成MessageBatch时,已经设置过了。
  • 可能会误以为⼀个批量消息中每条消息Unique Key是相同的,其实不然,每条消息Unique Key都不同。
  • 示例:
	public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer-group-1");
        producer.setNamesrvAddr("192.168.254.130:9876");
        producer.start();

        // 构建批量消息
        List<Message> msgs = new ArrayList<>();
        msgs.add(new Message("TopicB", "message1".getBytes()));
        msgs.add(new Message("TopicB", "message2".getBytes()));

        SendResult result = producer.send(msgs);
        System.out.println(result);
        producer.shutdown();
    }
  • 输出结果:
SendResult [sendStatus=SEND_OK, msgId=C0A8006462E618B4AAC28F8BBF930000,C0A8006462E618B4AAC28F8BBF930001, offsetMsgId=C0A8FE8200002AB3000000000000021E,C0A8FE8200002AB300000000000002BB, messageQueue=MessageQueue [topic=TopicB, brokerName=broker-b, queueId=1], queueOffset=0]
  • 可以看到,此时输出的msgId(即Unique Key)和offsetMsgId都会包含多个值。客户端给批量消息中每条消息设置不同的Unqiue Key,可以参考DefaultMQProducer#batch方法源码:

在这里插入图片描述

Message Id组成
  • SendResult中的offsetMsgId,即常规意义上我们所说的Message Id是在Broker端生成的,用于唯一标识一条消息,在根据Message Id查询的情况下,最多只能查询到一条消息。Message Id总共 16 字节,包含消息存储主机地址,消息 Commit Log offset。如下图所示:

在这里插入图片描述

  • RocketMQ内部通过一个MessageId对象进行表示:
public class MessageId {
    private SocketAddress address;
    private long offset;
    public MessageId(SocketAddress address, long offset) {
        this.address = address;
        this.offset = offset;
    }
    // setter/getter
}
  • 并提供了一个MessageDecoder对象来创建或者解码MessageId:

在这里插入图片描述

  • Broker端在顺序存储消息时,首先会通过createMessageId方法创建msgId。
  • 源码如下所示:CommitLog.DefaultAppendMessageCallback#doAppend

在这里插入图片描述

  • 而客户端在根据msgId向Broker查询消息时,首先会将通过MessageDecoder的decodeMessageId方法,之后直接向这个broker进行查询指定位置的消息。
  • 参见MQAdminImpl#viewMessage:

在这里插入图片描述

  • 由于根据Message Id进行查询,实际上是直接从特定Broker的CommitLog中的指定位置进行查询的,属于精确匹配,并不像用户设置的key,或者Unique Key那么样,需要使用到哈希索引机制,因此效率很高。

总结

  • RocketMQ提供了3种消息查询方式:Message Key、Unique Key、Message Id。
  • RocketMQ提供了3种消息查询工具:命令行、管理平台、客户端API,且支持将查询到让特定/所有消费者组重新消费。
  • RocketMQ有意对用户屏蔽Unique Key & Message Id区别,很多地方二者可以通用。
  • Message Key & Unique Key 需要使用到哈希索引机制,有额外的索引维护成本。
  • Message Id由Broker和commit log offset组成,属于精确匹配,查询效率更好。

Rebalance

简介

  • Rebalance(再均衡)机制指的是:将一个Topic下的多个队列(或称之为分区),在同一个消费者组(consumer group)下的多个消费者实例(consumer instance)之间进行重新分配
  • Rebalance机制本意是为了提升消息的并行处理能力。例如,一个Topic下5个队列,在只有1个消费者的情况下,那么这个消费者将负责处理这5个队列的消息。如果此时我们增加一个消费者,那么可以给其中一个消费者分配2个队列,给另一个分配3个队列,从而提升消息的并行处理能力。如下图:

在这里插入图片描述

  • 但是Rebalance机制也存在明显的限制与危害
Rebalance限制
  • 由于一个队列最多分配给一个消费者,因此当某个消费者组下的消费者实例数量大于队列的数量时,多余的消费者实例将分配不到任何队列。
  • 除了以上限制,更加严重的是,在发生Rebalance时,存在着一些危害,如下所述:
Rebalance危害
  • 消费暂停:考虑在只有Consumer 1的情况下,其负责消费所有5个队列;在新增Consumer 2,触发Rebalance时,需要分配2个队列给其消费。那么Consumer 1就需要停止这2个队列的消费,等到这两个队列分配给Consumer 2后,这两个队列才能继续被消费。
  • 消费突增:由于rebalance可能导致重复消费,如果需要重复消费的消息过多;或者因为rebalance暂停时间过⻓,导致积压了部分消息。那么都有可能导致在rebalance结束之后瞬间可能需要消费很多消息。
  • 重复消费:Consumer 2 在消费分配给自己的2个队列时,必须接着从Consumer 1之前已经消费到的offset继续开始消费。然而默认情况下,offset是异步提交的,如consumer 1当前消费到offset为10,但是异步提交给broker的offset为8;那么如果consumer 2从8的offset开始消费,那么就会有2条消息重复。也就是说,Consumer 2 并不会等待Consumer1提交完offset后,再进行Rebalance,因此提交间隔越⻓,可能造成的重复消费就越多。

在这里插入图片描述

  • 也就是说,Consumer 2 并不会等待Consumer1提交完offset后,再进行Rebalance,因此 提交间隔越⻓,可能造成的重复消费就越多。

协调机制

  • 从本质上来说,触发Rebalance的根本因素无非是两个:
    • 订阅Topic的队列数量变化
    • 消费者组信息变化
  • 导致二者发生变化的典型场景如下所示:
类别典型场景
队列信息变化broker宕机,broker升级等运维操作,队列扩容/缩容
消费者组信息变化日常发布过程中的停止与启动,消费者异常宕机,网络异常导致消费者与Broker断开连接,主动进行消费者数量扩容/缩容主动进行消费者数量扩容/缩容,Topic订阅信息发生变化
  • 在这里,队列信息和消费者组信息称之为Rebalance元数据,Broker负责维护这些元数据,并在二者信息发生变化时,以某种通知机制告诉消费者组下所有实例,需要进行Rebalance。从这个⻆度来说,Broker在Rebalance过程中,是一个协调者的⻆色。
  • 在Broker内部,通过元数据管理器维护了Rebalance元数据信息,如下图所示:

在这里插入图片描述

  • 这些管理器,内部实现都是一个Map。其中:
  • 队列信息:由 TopicConfigManager 维护。Map 的key是Topic名称,Value是TopicConfig。Broker通过实时的或者周期性的上报自己的Topic配置信息给NameServer,在NameServer组装成Topic的完整路由信息。消费者定时向NameServer定时拉取最新路由信息,以实现间接通知,当发现队列信息变化,触发Rebalance。
  • 消费者组信息:由 ConsumerManager 、 ConsumerOffsetManager 、 SubscriptionGroupManager 三者共同维护。
public class ConsumerManager {
    // Map 的key是Group名称,Value是ConsumerGroupInfo
    // ConsumerManager维护了消费者组订阅信息,以及消费者组下当前的消费者实例信息,当消费者组
    // 的订阅信息或者实例发生变化,Broker都会**主动**给所有消费者实例发送通知,触发Rebalance
    private final ConcurrentMap<String/* Group */, ConsumerGroupInfo> consumerTable =
        new ConcurrentHashMap<String, ConsumerGroupInfo>(1024);
}
public class ConsumerOffsetManager extends ConfigManager {
    // 在Rebalance时,消费者需要从ConsumerOffsetManager查询应该从那个位置继续开始消费
    private ConcurrentMap<String/* topic@group */, ConcurrentMap<Integer, Long>> offsetTable =
        new ConcurrentHashMap<String, ConcurrentMap<Integer, Long>>(512);
}
public class SubscriptionGroupManager extends ConfigManager {
    // 主要是维护消费者组的一些附加信息,方便运维。
    private final ConcurrentMap<String, SubscriptionGroupConfig> subscriptionGroupTable =
        new ConcurrentHashMap<String, SubscriptionGroupConfig>(1024);
}
队列信息变化
  • 队列信息通过Broker内的TopicConfigManager来维护,每个Broker都会将自己的信息上报给NameServer,由NameServer组装成完整的Topic路由信息。
  • 通常情况下,一个Topic下的队列数量不会频繁的变化,但是如果遇到,Topic队列数量扩/缩容,broker日常运维时的停止/启动或者broker异常宕机,也有可能导致队列数量发生变化。
bin/mqadmin topicRoute -n localhost:9876 -t TopicB

在这里插入图片描述

  • 在RocketMQ中,Topic的路由信息实际上是动态变化的。不论是停止/启动/扩容导致的所有变化最终都会上报给NameServer。客户端可以给NameServer发送请求,来获得某个Topic的完整路由信息。如果发现队列信息发生变化,则触发Reabalance。
消费者组信息变化
  • Rebalance的另外一个条件:消费者组信息,由ConsumerManager、ConsumerOffsetManager、SubscriptionGroupManager三个组件共同维护。
ConsumerManager
  • ConsumerManager是最重要的一个消费者组元数据管理器,其维护了某个消费者组的订阅信息,以及所有消费者实例的详细信息,并在发生变化时提供通知机制。
  • 数据添加:客户端通过发送HEART_BEAT请求给Broker,将自己添加到ConsumerManager中维护的某个消费者组中。需要注意的是,每个Consumer都会向所有的Broker进行心跳,因此每个 Broker 都维护了所有消费者的信息。
  • 数据删除:客户端正常停止时,发送UNREGISTER_CLIENT请求,将自己从ConsumerManager移 除;此外在发生网络异常时,Broker也会主动将消费者从ConsumerManager中移除。
  • 数据查询:消费者可以向任意一个Broker发送GET_CONSUMER_LIST_BY_GROUP请求,来获得 一个消费者组下的所有消费者实例信息。
  • 我们可以通过mqadmin命令行工具的consumerConnection子命令,来查看ConsumerManager中某个消费者的信息,如:
bin/mqadmin consumerConnection -g consumer-group-1 -n localhost:9876

在这里插入图片描述

  • 消费者组实例信息:展示了groupA下当前有2个消费者,以及对应的详细信息,包括:消费者id、消费者ip/port、消费者语言、消费者版本。
  • 消费者组订阅信息:包括订阅的Topic、过滤条件、消费模式,以及从什么位置开始消费等。
  • 这二者不论哪个信息发生变化,Broker都会主动通知这个消费者组下的所有实例进行Rebalance。这个消费者组下的所有实例在收到通知后,各自进行Rebalance:

在这里插入图片描述

  • Broker是通知每个消费者各自Rebalance,即每个消费者自己给自己重新分配队列,而不是Broker将分配好的结果告知Consumer
ConsumerOffsetManager
  • 事实上,通过ConsumerManager已经可以获得Rebalance时需要的消费者所有必要信息。但是还有一点,Rebalance时,如果某个队列重新分配给了某个消费者,那么必须接着从上一个消费者的位置继续开始消费,这就是ConsumerOffsetManager的作用。
  • 消费者可以给Broker发送UPDATE_CONSUMER_OFFSET请求,来更新消费者组对于某个Topic的消费进度。发送QUERY_CONSUMER_OFFSET指令,来从ConsumerOffsetManager中查询消费进度。
  • 通过mqadmin命令行工具的consumerProgress子命令,来可以看到Topic每个队列的消费进度,如:
bin/mqadmin consumerProgress -g consumer-group-1 -n localhost:9876

在这里插入图片描述

SubscriptionGroupManager
  • 订阅组配置管理器,内部针对每个消费者组维护一个SubscriptionGroupConfig。主要是为了针对消费者组进行一些运维操作。
Consumer Rebalance机制
  • Broker在Rebalance过程中起的是协调者的作用,但是Rebalance的细节,却是在Consumer端完成的。
Rebalance触发时机
  • 前面我们提到Broker会主动通知消费者进行Rebalance,但是从消费者的角度来看,整个生命过程的各个阶段,都有可能触发Rebalance,而不仅仅是收到通知后才进行Rebalance。
  • 具体来说,Consumer在启动/运行时/停止时,都有可能触发Rebalance,如下图所示:

在这里插入图片描述

  • 在启动时,消费者会立即向所有Broker发送一次发送心跳(HEART_BEAT)请求,Broker则会将消费者添加由ConsumerManager维护的某个消费者组中。然后这个Consumer自己会立即触发一次Rebalance。
  • 在运行时,消费者接收到Broker通知会立即触发Rebalance,同时为了避免通知丢失,会周期性触发Rebalance。
  • 当停止时,消费者向所有Broker发送取消注册客户端(UNREGISTER_CLIENT)命令,Broker将消费者从ConsumerManager中移除,并通知其他Consumer进行Rebalance。
开课吧-javaEE企业级分布式高级架构师是一门专注于培养企业级应用开发的高级技术课程。该课程旨在帮助学员全面掌握Java EE企业级开发的技能和知识,培养他们成为具备分布式应用系统设计和架构能力的高级架构师。 在这门课程中,学员将学习Java EE的核心概念和技术,包括Servlet、JSP、JDBC、EJB、JNDI等。同时,学员还将深入学习分布式应用开发的相关技术,如Web服务、消息队列、分布式缓存、负载均衡等。除此之外,课程还将涉及如何使用流行的Java EE开发框架(如Spring、Hibernate等)进行企业应用开发,并介绍分布式系统的设计原则和最佳实践。 通过学习这门课程,学员将能够了解分布式应用架构的基本原理,并具备设计和构建分布式应用系统的能力。他们将熟练掌握Java EE平台的各种技术和工具,能够灵活运用它们开发高性能、可扩展性强的企业级应用系统。此外,通过课程中的实战项目,学员还将锻炼解决实际问题和项目管理的能力。 作为一门高级架构师的课程,它将帮助学员进一步提升自己的职业发展。毕业后,学员可以在企业中担任分布式应用的架构师、系统设计师、技术经理等角色,负责企业级应用系统的设计和开发。此外,他们还可以选择独立开发,提供技术咨询和解决方案。 总之,开课吧-javaEE企业级分布式高级架构师是一门非常有价值的课程,它将帮助学员掌握Java EE企业级开发的核心技术和分布式应用架构的设计原理,培养他们成为具备高级架构师能力的软件开发专业人士。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值