RocketMQ的Consumer源码分析
这一节我们分析RocketMQ的Consumer的启动流程以及消息处理过程。下图是RocketMQ启动流程、Rebalance、以及消息拉取处理的较为完整的流程图。

##Consumer的启动流程
Consumer的启动主要会经过以下步骤:
- 校验Consumer一系列的配置,包括消费组、消费者配置等信息
- 校验消费组是否非空、是否超长、是否含有非法字符、是否系统占用等
- 校验一系列配置信息是否设置,如消费模式、消费位置、消息监听器等
- 校验消费者线程大小、消费者拉取消息的大小、批次大小等
- 复制订阅信息到RebalanceImpl,copySubscription()会把当前Topic的订阅信息放到RebalanceImpl的map中
- 如果是集群消费模式,使用PID设置实例名称
- 创建MQClientInstance,会优先从缓存中获取,如果缓存中有则直接返回,这也意味着相同的clientId会共享MQClientInstance
- 为RebalanceImpl设置所需要的属性,如消费组、消费模式、Queue分配策略等
- 初始化一个PullAPIWrapper,这个PullAPIWrapper是会为后面从Broker拉取消息服务
- 执行offsetStore.load(),广播模式下会选择从本地加载消费进度,集群模式默认什么也不做
- 根据顺序/并发消费方式初始化对应的消息消费服务
- 启动消息消费服务,这里会开启一个延时任务定期执行
- 对于并发消费方式,会定期清理过期消息,默认每15分钟执行一次
- 对于顺序消费方式且为集群消费模式时,会定期将消费的Queue锁定,默认每20秒执行一次
- 向MQClientInstance注册Consumer,相同的consumerGroup共享DefaultMQPushConsumer
- 启动MQClientInstance,这一步在初始化过程是Consumer启动的关键
- 如果没有指定NameServer地址,则会尝试从配置中心拉取,我们通常会在启动Producer时指定,没有指定的场景通常用在多集群的业务中,方便统一进行管理
- 启动MQClientAPIImpl,这里会完成NettyClient的初始化,为网络通信做准备
- 开启一系列的定时任务
- 如果没有指定NameServer地址,则定期从配置中心拉取
- 定期更新Topic的路由信息,这里会随机选择一个NameServer进行通信,获取topic对应的路由信息并更新到本地的内存中
- 定期清理下线的Broker,并且给所有的Broker发送心跳,默认每30秒执行一次
- 定期持久化消费进度,默认每5秒执行一次
- 定期调整消费者线程大小
- 启动PullMessageService,开始处理PullRequest,PullMessageService是一个循环执行的线程
- 从pullRequestQueue队列中获取一个PullRequest,如果没有获取到则会阻塞直到从队列中获取到数据为止
- 根据消费组获取到对应的MQConsumerInner实例,PullRequest中会有对应的consumerGroup信息
- 最终调用DefaultMQPushConsumerImpl.pullMessage()方法,完成消息的拉取,具体过程在下面详细分析
- 启动RebalanceService,开始执行rebalance,RebalanceService是一个循环执行的线程,限时等待被唤醒
- 随后调用mqClientFactory.doRebalance(),在MQClientInstance中,会遍历所有的消费组,执行Rebalance操作
- 最终调用到RebalanceImpl.doRebalance()方法,具体过程在下面详细分析
- 初始化内置的Producer
- 设置Consumer状态为RUNNING,刚启动时Consumer状态是START_FAILED
- 从NameServer获取订阅的Topic路由信息并更新本地缓存
- 从路由信息中随机找一个Broker,检查Broker是否支持消费者端对该Topic的订阅方式
- 向所有的Broker发送心跳,因为需要和所有的Broker通信
- 唤醒RebalanceService服务,立即进行一次Rebalance
Consumer的Rebalance流程
Consumer的rebalance流程是通过RebalanceService线程来执行的,当RebalanceService线程被唤醒后,执行以下逻辑
-
Consumer的rebalance会调用RebalanceImpl.doRebalance()方法,该方法会对订阅的所有topic依此进行rebalanceByTopic()
public void doRebalance(final boolean isOrder) { Map<String, SubscriptionData> subTable = this.getSubscriptionInner(); if (subTable != null) { for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) { final String topic = entry.getKey(); try { this.rebalanceByTopic(topic, isOrder); } catch (Throwable e) { if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { log.warn("rebalanceByTopic Exception", e); } } } } this.truncateMessageQueueNotMyTopic(); }
-
如果当前topic是广播消费模式,则消费者可以消费所有有效的Message Queue
-
如果当前topic是集群消费模式,则按照分配策略分配queue
-
获取该Topic对应的所有mqSet
-
根据consume group获取订阅了该topic的消费者信息(随机选择一个Broker发送网络请求获取)
-
按照queue的分配策略(默认为平均分配策略,AllocateMessageQueueAveragely)取得queue的分配结果,目前支持以下分配策略
AllocateMessageQueueAveragely:平均分配,将该topic下所有Queue按照brokerId和queueId从小到大排序,按照Consumer数量平均分为若干份,每个Consumer分配一份。 AllocateMessageQueueAveragelyByCircle:环形平均分配,和平均分配策略类似,只是每个Consumer分配到的queueId不是连续的。 AllocateMessageQueueByConfig:通过配置分配,用户启动时可以指定消费哪些Queue AllocateMessageQueueConsistentHash:一致性哈希,使用一致性hash算法来分配Queue,用户需自定义虚拟节点的数量。 AllocateMessageQueueByMachineRoom:按照机房分配,将queue按照Broker划分为几个machine room,不同的Consumer只消费某几个特定Broker上的消息。 AllocateMachineRoomNearby:机房就近分配,与按照机房分配类似,只是在AllocateMessageQueueByMachineRoom的基础上,会优先为Consumer分配在同一个machine room的Broker上的queue。
-
-
根据分配的mqSet更新