以下仅分析消费者部分,公共代码在生产者和namesrv中已分析;
1)创建DefaultMQPushConsumer实例
入参为消费者组名,其中会创建DefaultMQPushConsumerImpl实例,其构造方法没做啥。有一个实例变量进行了自定义初始化,即new RebalancePushImpl,其中又会调用RebalanceImpl的构造方法,此时会初始化三个重要的实例变量;
1.1)processQueueTable
chm类型的,键值对为<MessageQueue,ProcessQueue>;
1.2)topicSubscribeInfoTable
chm类型的,键值对为<String,Set>;
1.3)subscriptionInner;
chm类型的,键值对为<String,SubscriptionData>;
另外在DefaultMQPushConsumer的构造方法中也会实例化AllocateMessageQueueAveragely作为默认负载策略;DefaultMQPushConsumer类继承了ClientConfig类,实际上与配置有关;DefaultMQPushConsumer类充当了门面角色,采用了门面设计模式;
2)设置默认消费起点CONSUME_FROM_FIRST_OFFSET
暂无补充;
3)订阅topic和设置过滤
暂无补充;
4)注册监听处理拉取到的消息
调用了DefaultMQPushConsumer#registerMessageListener方法,入参为MessageListenerConcurrently实例或MessageListenerOrderly实例;接着会重写consumeMessage方法,拿到消息进行业务处理;
5)consume.start
启动哪些:客户端netty,定时任务,一些线程等;这里很多与生产者逻辑一致,不赘述,重点分析消费者特有部分,和生产者相同的共有的逻辑省略;
5.1)checkConfig和判断serviceState状态
checkConfig方法中消费者组不能为null;组名不能为默认值;消费模式不能为null;consumeFromWhere不能为null;启动时间不能为null;分配策略不能为null;订阅信息不能为null;messageListenner不能为null,要么是顺序消费,要么是并发消费,否则抛异常;消费服务的线程数介于1到1000之间,否则抛异常等等;以上为null的均抛相应异常;
状态判断和生产者的逻辑一致;
5.2)copySubscription()
根据topic和tag构建订阅信息实例SubscriptionData实例,其中含有分别存储tag和tag.hashCode的set集合;最后将<topic,subscriptionData>键值对放入subscriptionInner集合中;
5.3)创建MQClientInstance实例
若消费者和生产者所处机器ip的不同,则此时创建实例和生产者是不同实例;逻辑一样;这是个公共类;在创建客户端实例时,会执行以下三个与消费者相关的实例化操作:
5.3.1)实例化PullMessageService
会初始化pullRequestQueue队列,是LinkedBlockingQueue类型;初始化调度任务线程;
5.3.2)实例化RebalanceService
并未实例化什么重要的;
5.3.3)实例化负责消息回退的内部发送者DefaultMQProducer
构造方法入参是CLIENT_INNER_PRODUCER_GROUP,表示生产者组名;当执行该内部生产者实例的start方法时,入参传入的是false,当检测到是生产者组名是CLIENT_INNER_PRODUCER_GROUP时,就会不新建新的客户端实例;另外检测到入参为false,则不会执行mQClientInstance的start方法;
5.4)实例化PullAPIWrapper
该类相当于生产者中的MQClientAPIImpl,专门用于处理网络发送接收相关;该类封装了MQClientInstance,而MQClientInstance中又封装了MQClientAPIImpl,所以PullAPIWrapper具备网络发送接收的功能;另外有一个实例变量pullFromWhichNodeTable被直接初始化为chm了,key表示mq,value表示brokerId,表示下次该mq从broker的主节点还是从节点拉取数据;
5.5)实例化RemoteBrokerOffsetStore
默认集群模式,会实例化该类;
5.6)实例化消费服务并启动
这里是并发消费所以实例化ConsumeMessageConcurrentlyService,若是顺序消费,则实例化ConsumeMessageOrderlyService;并启动该服务;
延迟15分钟,每15分钟执行一次清理过期消息任务,清理条件是消费者本地如果某条消息15分钟还未被消费,则需要回退该消息;从ProcessQueue中的TreeMap中的第一个元素开始循环(最多16次),调用回退方法,第二个入参是延迟级别为3;这里还有会判断若回退期间消息并没有被消费,才将该条消息从TreeMap中移除掉;向MQClientInstance实例中的消费者集合consumerTable中注册,即向该Map中存入消费者group和当前DefaultMQPushConsumerImpl实例;
5.7)将消费者实例注册进消费者报表
这里用到了观察者模式,向consumerTable表中添加group,DefaultMQPushConsumerImpl生产者实例,分别作为key和value,意味着一个消费者组只对应一个消费者实例;注意消费者实例和客户端实例不是一回事;
5.8)MQClientInstance#start方法启动客户端实例
这一步和生产者逻辑一致;重点分析与消费者相关的部分;如定时任务持久化消费进度,启动拉取消息线程,启动重平衡线程;
5.8.1)更新topicSubscriptionInfoTable
客户端每30秒更新topic路由信息的定时任务中,此时会更新RebalanceImpl中消费者订阅主题的队列信息topicSubscriptionInfoTable,key为topic,value为set;此处和5.9节中的逻辑一致;
5.8.2)客户端每隔30秒向所有broker发送心跳
包含注册信息如clientId等,其中在broker端收到心跳后会执行ConsumerManager#registerConsumer方法,会将clientId等信息存储到ConsumerManager的consumerTable中,在执行doRebalance时,会遍历当前消费者组下的所有clientId;
5.8.3)持久化消费进度
消费者每隔5秒持久化一次消费进度即offset,将内存中的偏移量存储在消费记录文件中;
5.8.4)pullMessageService.start()
开启一个消费者拉取消息线程,从pullRequestQueue.take()中拿到请求,pullMessage(pullRequest)将请求发往broker,从broker中拉取需要消费的消息,broker返回消息给消费者,消费者本地会执行回调方法pullCallBack,根据返回状态,选择不同操作,若拉取消息成功,则执行监听方法,执行监听方法之后,又会返回结果,若拉取消息失败,则将该失败消息再次发往broker,准备再次消费。拉取消息消费的具体细节在后边小节中专门展开分析;
5.8.5)rebalanceService.start()
开启重平衡组件,会立即进入超时20s的countDownLatch阻塞,被唤醒或自动醒来后,会执行doRebalance方法,接着会执行dispatchPullRequest方法,目的是构建pullRequest,并放入pullRequestQueue;负载均衡具体细节在后边小节中专门展开分析;
5.9)更新主题订阅信息topicSubscriptionInfoTable
首先遍历5.2节中的subscriptionInner表,根据当前消费者订阅的topic,从nameserver拿到对应的最新的路由数据TopicRouteData实例,与本地比较,不同则更新本地topicRouteData缓存;
接着更新brokerAddrTable;
接着将topicRouteData转为topicPublishInfo,遍历topicRouteData中的List,queueData中写队列数为多少,就创建多少个messageQueue,且编号从0开始,编号代表的就是queueId;新的queueData,编号又从0开始;本质是找到当前topic分布在哪些broker的哪些queueId上,最后将当前topic对应的messageQueue加到list集合中;
接着将topicRouteData转为subscriptionInfo,遍历topicRouteData中的List,queueData中读队列数为多少,就创建多少个messageQueue,且编号从0开始,编号代表的就是queueId;新的queueData,编号又从0开始;,最后将当前topic对应的messageQueue加到set集合中,最后更新topicSubscriptionInfoTable表;
最后调用countDownLatch的signal方法,立即唤醒重平衡rebalanceService线程;
消费总览
可以分为9大部分
1)启动前;
2)启动;
3)分配messageQueue给消费者;
4)pullMessageService线程拿到pullRequest;
5)broker端返回消息,执行callback回调函数;
6)长轮询机制;
7)消息失败重试;
8)延迟消息
9)实时唤醒重平衡线程;