一.简介
从这一期开始,我们对RocketMQ进行源码分析,我们知道,RocketMQ消费分为两种模式,pulll和push, pull即消费者客户端主要向服务端拉取消息,push模式为服务端主动将消息推到客户端,这次我们从源码的角度分析下push模式的实现
二.问题分析
先简单分析下,什么为推? 我们知道设计模式中有一个模式叫监听器模式,实际上很多框架的实现都是使用了这一模式,spring的事件,redis的pub/sub,包括mq的push,但是仔细想想,何为推,服务端主动推给客户端,怎么个推法,这里一般有两种实现,一种是采用回调的形式,服务端有事件源之后,启动一个线程,遍历所有所有的客户端列表,然后依次调用其钩子函数,函数实现可以是本地的,也可以是远程的,这是一种;但这种方式需要服务端缓存所有的客户端列表,然后依次调用,另外,还有个问题,就是每一次事件源发布,就需要调一次,这在某种场景下对资源的浪费很大;所以,还有一个方式是利用长轮询的拉来模拟推,实现类似于推的效果,RocketMQ采用的就是这种方式; 其实,就推这个事件而言,是不容易实现的,这里举个不恰当的例子,就好比小时候老师发作业,拉就是每个同学上讲台拿自己的本子,推就是老师把做每个作业本一个个发到同学手里面,哪个效率高,肯定是前者,因为,每个同学只要记住讲台的地址就可以了,但第二种老师需要记住班里每个同学的座位。其中的差别大家可以细想一下,所以RocketMQ采用的也是拉来模拟推。
三.源码分析
我们来看下push模式下的具体实现,首先从消费者的启动开始,一般consumer的启动代码如下:
consumer = new DefaultMQPushConsumer("demo");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.subscribe("demo_topic", "*");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
设置 nameServer地址,topic,tag, 监听处理函数,以及一些其他属性,然后start()启动consumer;
点开start,继续往下看,是一个具体实现类的DefaultMQPushConsumerImpl的star(),继续进去,是真正的启动细节,
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(),
this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());
this.serviceState = ServiceState.START_FAILED;
this