DefaultMQPushConsumer
rocketmq客户端消费者实现,从名字上已经可以看出其消息获取方式为broker往消费端推送数据,其内部实现了流控,消费位置上报等等。
- 重要字段
1 String consumerGroup 消费者组名,必须设置
需要注意的是,多个消费者如果具有同样的组名,那么这些消费者必须只消费同一个topic,具体原因参见rocketmq问题汇总-一个consumerGroup只对应一个topic
2 MessageModel messageModel
消费的方式,分为两种:
2.1 BROADCASTING 广播模式,即所有的消费者可以消费同样的消息
2.2 CLUSTERING 集群模式,即所有的消费者平均来消费一组消息
3 ConsumeFromWhere consumeFromWhere
消费者从那个位置消费,分别为:
3.1 CONSUME_FROM_LAST_OFFSET:第一次启动从队列最后位置消费,后续再启动接着上次消费的进度开始消费
3.2 CONSUME_FROM_FIRST_OFFSET:第一次启动从队列初始位置消费,后续再启动接着上次消费的进度开始消费
3.3 CONSUME_FROM_TIMESTAMP:第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费
以上所说的第一次启动是指从来没有消费过的消费者,如果该消费者消费过,那么会在broker端记录该消费者的消费位置,如果该消费者挂了再启动,那么自动从上次消费的进度开始,见RemoteBrokerOffsetStore
4 AllocateMessageQueueStrategy allocateMessageQueueStrategy
消息分配策略,用于集群模式下,消息平均分配给所有客户端
默认实现为AllocateMessageQueueAveragely
5Map<topic, sub expression>
subscription // topic对应的订阅tag
6 MessageListener messageListener //客户端消费消息的实现类
7 OffsetStore offsetStore //offset存储实现,分为本地存储或远程存储
8 int consumeThreadMin = 20 //线程池自动调整,参见MQClientInstance调整客户端消费线程池
9 int consumeThreadMax = 64//线程池自动调整,参见MQClientInstance调整客户端消费线程池
10 long adjustThreadPoolNumsThreshold = 100000
11 int consumeConcurrentlyMaxSpan = 2000//单队列并行消费最大跨度,用于流控
12 int pullThresholdForQueue = 1000 // 一个queue最大消费的消息个数,用于流控
13 long pullInterval = 0 //消息拉取时间间隔,默认为0,即拉完一次立马拉第二次,单位毫秒
14 consumeMessageBatchMaxSize = 1//并发消费时,一次消费消息的数量,默认为1,假如修改为50,此时若有100条消息,那么会创建两个线程,每个线程分配50条消息。
15 pullBatchSize = 32 //消息拉取一次的数量
16 boolean postSubscriptionWhenPull = false
17 boolean unitMode = false
18 DefaultMQPushConsumerImpl defaultMQPushConsumerImpl
消费者实现类,所有的功能都委托给DefaultMQPushConsumerImpl来实现 - 重要方法
1 subscribe(String topic, String subExpression)
订阅某个topic,subExpression传*为订阅该topic所有消息
2 registerMessageListener(MessageListenerConcurrently messageListener)
注册消息回调,如果需要顺序消费,需要注册MessageListenerOrderly的实现
3 start
启动消息消费,参照DefaultMQPushConsumerImpl.start
- 重要字段
DefaultMQPushConsumerImpl
消费者具体实现类
- 重要字段
1 RebalancePushImpl rebalanceImpl负载均衡实现类
2 MQClientInstance mQClientFactory 由于在介绍DefaultMQProducer时已经介绍过MQClientInstance的作用,故在此不再解释,如有需要可以点击链接跳过去查看
3 PullAPIWrapper pullAPIWrapper
拉取消息的封装 - 重要方法
1 start
该方法实现了consumer的启动,并开始消费消息,主要包括以下几个方面:
1.1 校验设置参数是否合法
1.2 将订阅关系(即topic<->*)复制到RebalancePushImpl,包括%RETRY% topic
1.3 设置instanceName为jvm pid
1.4 初始化MQClientInstance实例(这个实例和rocketmq3.26研究之四DefaultMQProducer中的MQClientInstance是一样的),也就是说rocketmq的producer和consumer都持有MQClientInstance的实例,来实现其功能。
1.5 为RebalancePushImpl设置参数,包括消费者组名,消费模式(集群or广播)
1.6 初始化PullAPIWrapper
1.7 初始化OffsetStore,集群消费默认为RemoteBrokerOffsetStore
1.8 初始化ConsumeMessageService并启动,根据顺序消费和并发消费初始化不同的实例,集群消费为ConsumeMessageConcurrentlyService,顺序消费为ConsumeMessageOrderlyService(这个不再介绍)
1.9 注册自己,保证一个consumer group只对应一个DefaultMQPushConsumerImpl的实例
1.10 启动MQClientInstance参见start方法
1.11 updateTopicSubscribeInfoWhenSubscriptionChanged
循环所有topic,调用MQClientInstance的updateTopicRouteInfoFromNameServer,来更新topic的路由信息和订阅信息
1.12 给所有的broker发送心跳
1.13 进行rebalance
2 pullMessage(final PullRequest pullRequest)
消息拉取方法,此方法是最关键的方法,其执行步骤如下:
2.1 获取ProcessQueue对象
2.2 判断这个队列是否已经停止了,参见updateProcessQueueTableInRebalance
2.3 设置ProcessQueue的最近拉取时间
2.4 从ProcessQueue获取其内含有的消息个数,默认大于1000,则进行流控,所谓的流控即延时执行消息拉取,调用executePullRequestLater实现。
2.5 若是集群消费模式,从ProcessQueue获取offset跨度,默认大于2000,则进行流控。这个限制貌似和2.4一样,因为这里的offset跨度也就是消费的位置,其实也就是消息个数,但是这里是指并行消费,即可能存在多个消费者消费同一个queue?(这块还不太清楚),那么此时就会offset跨度>消息数了,那个这个限制实际是限制了并行消费一个queue时的最大消息数,也就是跨度。
2.6 从RebalanceImpl获取订阅关系,参考subscriptionInner
2.7 构造PullCallback的匿名内部实现类,进行回调,请参考DefaultMQPushConsumerImpl.PullCallback
2.8 调用PullAPIWrapper.pullKernelImpl拉取数据
- 重要字段
AllocateMessageQueueAveragely
集群消费时用到该类,该类为消费者分配queue,其是AllocateMessageQueueStrategy的默认实现
- 重要方法
List<
MessageQueue>
allocate(
String consumerGroup, //消费者属组,此方法中只用于输出日志用
String currentCID, //cid,默认为ip@pid,该参数的作用参考rocketmq问题汇总-instanceName参数何时该设置?
List<
MessageQueue>
mqAll,//topic<->broker name<->queue id关系对象
List cidAll)// 同一consumer group下的所有的cid列表
该方法实现了为消费者平均分配queue。其首先需要根据所有的消费者数和所有queue的量计算出平均每个消费者需要消费多少个queue,再根据当前消费者在消费者组的位置(即currentCID在cidAll的位置),分配相应的queue。
- 重要方法
RebalancePushImpl
该类实现了负载均衡,以下属性或方法来自于它自己或其父类RebalanceImpl
- 重要属性
1ConcurrentHashMap<String /* topic */, SubscriptionData> subscriptionInner
此map保存了topic和该topic对应的订阅tag,默认为*,即订阅topic下所有的消息。
2ConcurrentHashMap<String/* topic */, Set<MessageQueue>> topicSubscribeInfoTable
此map保存了topic<->brokerName<->queueId的对应关系
3ConcurrentHashMap<MessageQueue, ProcessQueue> processQueueTable
此map保存了MessageQueue的处理队列,具体来说就是某一个消费者,消费某个boker的某个queue的处理队列。 - 重要方法
1 doRebalance
遍历所有订阅的topic,进行rebalanceByTopic
2 rebalanceByTopic
根据广播或集群模式进行rebalance,以下对集群消费模式说一下:
2.1 首先根据topic获取到Set<
MessageQueue>
2.2 其次根据topic和consumer group获取到所有的consumer的cid列表(调用MQClientInstance.findConsumerIdList实现)
2.3 调用AllocateMessageQueueAveragely.allocate来重新获取相应的MessageQueue列表
2.4 根据上步返回的结果,调用updateProcessQueueTableInRebalance
3 updateProcessQueueTableInRebalance
这个方法功能如下:
3.1更新processQueueTable
具体的为查找processQueueTable中无用的ProcessQueue,标记为停止,移除,查找上步中的MessageQueue列表新的对应关系,创建新的ProcessQueue并添加进去,并生成新的PullRequest,此时有个关键的字段,即PullRequest.nextOffset,从computePullFromWhere计算得出,这个字段关系着这个请求下次拉取数据的位置。
3.2 针对新的PullRequest调用PullMessageService.executePullRequestImmediately进行消息拉取
4 computePullFromWhere(MessageQueue mq)
计算一个queue该从那个位置开始拉取数据,具体实现如下(以下分析基于集群消费模式):
4.1 首先需要确定消费的位置,即ConsumeFromWhere
4.2 获取其OffsetStore,集群模式默认为RemoteBrokerOffsetStore
4.3 如果消费位置为CONSUME_FROM_LAST_OFFSET,那么首先需要调用RemoteBrokerOffsetStore.fetchConsumeOffsetFromBroker获取消费位置(该消费位置在broker端的存储请参考ConsumerOffsetManager.queryOffset),如果获取不到,则获取此队列的最大消费位置。
4.4 如果消费位置为CONSUME_FROM_FIRST_OFFSET,那么首先需要调用RemoteBrokerOffsetStore.fetchConsumeOffsetFromBroker获取消费位置,如果获取不到,则返回0。
4.5 如果消费位置为CONSUME_FROM_TIMESTAMP,那么首先需要调用RemoteBrokerOffsetStore.fetchConsumeOffsetFromBroker获取消费位置,如果获取不到,则调用MQAdminImpl.searchOffset获取位置。
- 重要属性
MessageQueue
该类存储了topic的路由信息,比如testTopic对应broker-a的queue-0等
- 重要字段
1 String topic
2 String brokerName
3 int queueId
- 重要字段
RemoteBrokerOffsetStore
该类实现了偏移量的存储和查询
- 重要方法
fetchConsumeOffsetFromBroker(MessageQueue mq)
根据broker name获取broker地址,之后根据topic,group name和queue id获取消费的位置
- 重要方法
PullMessageService
该类从名字上看是拉取消息服务,其实它只是对拉取请求进行了封装,使其队列化
- 重要字段
LinkedBlockingQueue<PullRequest> pullRequestQueue
拉取请求队列 - 重要方法
1 run
不断的从pullRequestQueue中取出请求,并调用消息拉取
2 pullMessage(PullRequest pullRequest)
消息拉取方法,根据consumer group查找DefaultMQPushConsumerImpl然后调用其pullMessage方法进行消息拉取
3 executePullRequestLater(PullRequest pullRequest, long timeDelay内部为一个定时任务,timeDelay毫秒后将pullRequest放到pullRequestQueue中
- 重要字段
PullRequest
拉取请求
- 重要字段
1 String consumerGroup
消费者组名
2 MessageQueue messageQueue
消息队列对应关系,主要包括topic<->broker name<->queue id
3 ProcessQueue processQueue
队列消费处理对象
long nextOffset
下一次请求的偏移量
- 重要字段
- ProcessQueue
代表了一个消费的queue,假设consumer C,消费topic T,T的路由为broker-0,broker-1,那么共有两个ProcessQueue对应consumer C。
- 重要字段
TreeMap<Long, MessageExt> msgTreeMap
用于缓存从broker拉取的消息。 - DefaultMQPushConsumerImpl.PullCallback
其内部有两个重要方法
1 onException
发生异常时延时重试,即调用executePullRequestLater
2 onSuccess
成功返回结果后,处理步骤如下:
2.1 调用processPullResult进行解码
2.2 针对的消息的状态NO_NEW_MSG,NO_MATCHED_MSG,OFFSET_ILLEGAL,FOUND,只介绍正常情况即FOUND,其余情况不做分析。
针对FOUND,首先将消息列表存入ProcessQueue,之后调用ConsumeMessageService.submitConsumeRequest进行消费。
2.3 将PullRequest存入PullMessageService的queue预备下一次的消息拉取,注意,此时,便形成了一个循环,循环的拉取消息
PullAPIWrapper
消息拉取的封装
- 重要字段
1ConcurrentHashMap<MessageQueue, AtomicLong/* brokerId */> pullFromWhichNodeTable
存储了mq对应的brokerid,即从broker master拉取消息,还是从broker slave拉取消息。 - 重要方法
1 PullResult pullKernelImpl(
MessageQueue mq,// 1 mq
String subExpression,// 2 订阅的tag,默认为*
final long subVersion,// 3 订阅的时间
final long offset,// 4 拉取消息的位置,参照更新processQueueTable
final int maxNums,// 5 拉取消息的数量,默认为32
final int sysFlag,// 6 一些标志
final long commitOffset,// 7 提交的offset
final long brokerSuspendMaxTimeMillis,// 8 broker无响应最大时间,默认15秒
final long timeoutMillis,// 9 客户端超时时间,默认20秒
final CommunicationMode communicationMode,// 10 默认为CommunicationMode.ASYNC
final PullCallback pullCallback// 参见PullCallback
1.1 查找brokerid,调用recalculatePullFromWhichNode,为了看是否应该从slave拉取消息,主要为了防止master压力过大或挂掉的情况
1.2 从MQClientInstance.brokerAddrTable获取broker地址
1.3 调用MQClientAPIImpl.pullMessage,进行与broker交互,由于在介绍DefaultMQProducer已经大概说过了,在此就不啰嗦了,需要啰嗦一下的是返回结果后,会回调PullCallback
2 recalculatePullFromWhichNode(MessageQueue mq)
从pullFromWhichNodeTable获取brokerid,不存在返回0,即master
3 processPullResult
处理pullKernelImpl返回的结果,主要进行消息的解码和更新pullFromWhichNodeTable
- 重要字段
ConsumeMessageConcurrentlyService
- 重要方法
1 submitConsumeRequest(List msgs, …)
根据消费者设置的consumeMessageBatchMaxSize来决定提交多少个任务来消费,如果msgs.size<=consumeMessageBatchMaxSize,那么提交一个任务就够了,如果msgs.size>consumeMessageBatchMaxSize,那么需要提交msgs.size%consumeMessageBatchMaxSize个任务,这里的任务实现类为ConsumeRequest
2 processConsumeResult
根据返回的状态,将集群模式消费失败的消息,重新发送到broker,注意:需要消费者实现MessageListenerConcurrently.consumeMessage(final List msgs, ConsumeConcurrentlyContext context)方法时,假设一共100个消息,消费了10个,消费第11个时发生异常,需要设置context.setAckIndex(11),然后返回,这样第11至100个消息会被重新发送至broker,等待下一次消费。
然后从ProcessQueue中移除消费过的消息。
最后更新最新的offset至RemoteBrokerOffsetStore。另外,关于客户端将offset同步至broker的实现在MQClientInstance更新客户端offset中介绍了,在此不再赘述。
- 重要方法
ConsumeMessageConcurrentlyService.ConsumeRequest
并发消费实现
- 重要字段
List msgs;
ProcessQueue processQueue;
MessageQueue messageQueue; - 重要方法
run
此方法为消费消息的地方,分为以下几个步骤:
1 消费前执行预设的钩子方法,如果有的话。
2 调用MessageListenerConcurrently的实现类来消费消息,该类需要消费者自己实现。
3 消费后执行预设的钩子方法,如果有的话。
4 调用processConsumeResult处理返回结果
- 重要字段
最后,就不附图了,参考rocketmq3.26研究之DefaultMQPushConsumer消费流程来看相应的流程图吧
rocketmq3.26研究之五DefaultMQPushConsumer
最新推荐文章于 2024-08-10 11:03:56 发布