RocketMQ客户端源码分析-DefaultMQPushConsumer

本文深入分析RocketMQ客户端的DefaultMQPushConsumer,包括subscribe、start、rebalanceService和pullMessageService的流程。讲解了广播、集群模式的队列分配策略,以及并发消费和有序消费的实现。在集群模式中,由于相同clientId导致队列分配问题,提醒注意消费者ID的唯一性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

与DefaultMQProducer类似,DefaultMQPushConsumer包含了defaultMQPushConsumerImpl,而defaultMQPushConsumerImpl又包含了MQClientInstance。

使用方式
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("s1");
consumer.setNamesrvAddr("{外网IP}:9876");
consumer.subscribe("mytopic", "mytag");
consumer.registerMessageListener(new MessageListenerConcurrently() {
	@Override
	public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
		for(int i=0; i<msgs.length(); i++){
			MessageExt msg = msgs.get(i);
			System.out.println(msg.getTopic() + " " + msg.getTags() + " " + new String(msg.getBody()));
		}
		return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
	}
});
consumer.start();

我们重点关注下subscribe和start的过程。

subscribe
  • 订阅的时候会构造SubscriptionData,存储在rebalanceImpl.getSubscriptionInner()中
  • SubscriptionData比较简单,包含topic,tag等信息,但是不包含主题的队列信息
start

观察MQClientInstance的start方法,重点关注以下三句

  • this.startScheduledTask();
    此处是开启一些定时任务,我们观察第二个定时任务
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        try {
            MQClientInstance.this.updateTopicRouteInfoFromNameServer();
        } catch (Exception e) {
            log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
        }
    }
}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);

在10ms后更新主题路由信息,这里涉及到生产者生产的主题和订阅者关注的主题,对于这些主题,都会去nameServer获取路由信息(所在队列、broker等信息)。
关于订阅主题的路由信息,最终会存在this.rebalanceImpl.topicSubscribeInfoTable中。

  • this.pullMessageService.start();
    此处是对于订阅的队列,进行拉消息
  • this.rebalanceService.start();
    此处是做负载均衡,将订阅主题的队列分配到各个消费者

由于需等队列分配好后,才能进行拉消息,所以我们先看负载均衡的rebalanceService

rebalanceService

这里会遍历每一个consumer的每一个topic,对于每一个topic,会取出路由信息进行队列的分配,分配的时候会根据messageModel有不同的分配方式。

  • broadcast 广播模式
    广播模式,即消费组中的每个消费者消费每一个消息,所以需要监听每一个队列,无需分配。
    每一个监听请求都封装成PullRequest,放到pullRequestQueue中。
  • clustering 集群模式
    集群模式,即消费组中的每个消费者平均分摊消息。
    具体的分摊算法:
    (1)将队列和消费者都进行排序
    (2)使用配置的策略进行分摊,默认为AllocateMessageQueueAveragely
    平均消费数=队列数/消费者数(如果除不尽有特殊处理)
    按照消费者排序,每个消费者获取连续的一段队列,由于顺序和算法一致,所有消费者计算出来的结果是一致的
    (3)根据分摊结果,更新pullRequestQueue

这里遇过一个问题,两个消费者以集群模式订阅主题(该主题拥有16个队列),但最终只有8个队列被消费。后来发现由于这两个消费者都在同一个机器不同的服务中,根据IP生成了一样的clientId(xxx:xxx:xxx:xxx@RocketMQTemplate)。分摊算法认为存在两个消费者,计算出平均消费数为16/2=8,然后为消费者排序,由于两个消费者clientId一致,被当成同一个消费者,分配了同样的8个队列,导致另外8个队列无法被消费。解决方案:消费者的clientId不能一致,重写clientId生成方法保证唯一性。

pullMessageService

pullMessageService负责从pullRequestQueue中取出消息,然后进行pull操作。
重点关注defaultMQPushConsumerImpl的pullMessage方法:
(1)构造PullCallback
(2)调用核心的拉取方法this.pullAPIWrapper.pullKernelImpl
这里面又是在调用MQClientAPIImpl的方法来通过NettyRemotingClient来与broker进行通讯,并在成功后调用callback方法。
(3)当拉取结果回来后,调用callback方法。callback方法中如果发现有消息,则会把消息放进processQueue中,并且提交消费请求(分为并发消费和有序消费,就是我们一开始注册的MessageListener),最后检查pullInterval来决定是立马继续拉取还是稍后拉取(默认interval为0)

总结

根据以上的源码分析,对消费者有了更深入的了解,我们可以在这几个角度对消费者进行分类

pull和push
  • pull 是自主根据需求拉消息
  • push 是自动推送消息,实际上也是客户端在拉消息,但是是不停的主动拉,拉取的时间间隔通过pullInterval来设置
广播和集群

广播是每个消费者消费所有队列,而集群需要根据一致的算法来分配队列,来达到共同消费的目的。分配队列的动作是在每个客户端中进行。

并发消费和有序消费。

无论是并发还是有序,都是多线程消费,但是对于有序消费,会在消费的时候对messageQueueLock进行上锁,来保证有序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值