rocketmq3.26研究之五DefaultMQPushConsumer

  1. 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
      5 Map<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
  2. 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拉取数据
  3. 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。
  4. RebalancePushImpl

    该类实现了负载均衡,以下属性或方法来自于它自己或其父类RebalanceImpl

    • 重要属性
      1 ConcurrentHashMap<String /* topic */, SubscriptionData> subscriptionInner
      此map保存了topic和该topic对应的订阅tag,默认为*,即订阅topic下所有的消息。
      2 ConcurrentHashMap<String/* topic */, Set<MessageQueue>> topicSubscribeInfoTable
      此map保存了topic<->brokerName<->queueId的对应关系
      3 ConcurrentHashMap<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获取位置。
  5. MessageQueue

    该类存储了topic的路由信息,比如testTopic对应broker-a的queue-0等

    • 重要字段
      1 String topic
      2 String brokerName
      3 int queueId
  6. RemoteBrokerOffsetStore

    该类实现了偏移量的存储和查询

    • 重要方法
      fetchConsumeOffsetFromBroker(MessageQueue mq)
      根据broker name获取broker地址,之后根据topic,group name和queue id获取消费的位置
  7. PullMessageService

    该类从名字上看是拉取消息服务,其实它只是对拉取请求进行了封装,使其队列化

    • 重要字段
      LinkedBlockingQueue<PullRequest> pullRequestQueue
      拉取请求队列
    • 重要方法
      1 run
      不断的从pullRequestQueue中取出请求,并调用消息拉取
      2 pullMessage(PullRequest pullRequest)
      消息拉取方法,根据consumer group查找DefaultMQPushConsumerImpl然后调用其pullMessage方法进行消息拉取
      3 executePullRequestLater(PullRequest pullRequest, long timeDelay内部为一个定时任务,timeDelay毫秒后将pullRequest放到pullRequestQueue中
  8. PullRequest

    拉取请求

    • 重要字段
      1 String consumerGroup
      消费者组名
      2 MessageQueue messageQueue
      消息队列对应关系,主要包括topic<->broker name<->queue id
      3 ProcessQueue processQueue
      队列消费处理对象
      long nextOffset
      下一次请求的偏移量
  9. ProcessQueue

    代表了一个消费的queue,假设consumer C,消费topic T,T的路由为broker-0,broker-1,那么共有两个ProcessQueue对应consumer C。
    - 重要字段
    TreeMap<Long, MessageExt> msgTreeMap用于缓存从broker拉取的消息。

  10. 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预备下一次的消息拉取,注意,此时,便形成了一个循环,循环的拉取消息
  11. PullAPIWrapper

    消息拉取的封装

    • 重要字段
      1 ConcurrentHashMap<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
  12. 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中介绍了,在此不再赘述。
  13. ConsumeMessageConcurrentlyService.ConsumeRequest

    并发消费实现

    • 重要字段
      List msgs;
      ProcessQueue processQueue;
      MessageQueue messageQueue;
    • 重要方法
      run
      此方法为消费消息的地方,分为以下几个步骤:
      1 消费前执行预设的钩子方法,如果有的话。
      2 调用MessageListenerConcurrently的实现类来消费消息,该类需要消费者自己实现。
      3 消费后执行预设的钩子方法,如果有的话。
      4 调用processConsumeResult处理返回结果
  14. 最后,就不附图了,参考rocketmq3.26研究之DefaultMQPushConsumer消费流程来看相应的流程图吧

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值