Rocketmq消费分为push和pull两种方式,push为被动消费类型,pull为主动消费类型,push方式最终还是会从broker中pull消息。不同于pull的是,push首先要注册消费监听器,当监听器处触发后才开始消费消息,所以被称为“被动”消费。
 
 具体地,以pushConsumer的测试例子展开介绍,通常使用push消费的过程如下:
public class PushConsumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1");
consumer.subscribe("Jodie_topic_1023", "*");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//wrong time format 2017_0422_221800
consumer.setConsumeTimestamp("20170422221800");
consumer.registerMessageListener(new MessageListenerConcurrently() { @Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
 
上述过程背后设计到的点如下: 
 
I. checkConfig 检查内容:
1.消费组 -- (不能与默认DEFAULT_CONSUMER同名)
2.消费模型 -- (默认CLUSTERING)
3.从何处开始消费 -- (默认CONSUME_FROM_LAST_OFFSET)
4.消费时间戳 -- (消息回溯,默认Default backtracking consumption time Half an hour ago)
5.消费负载均衡策略 -- (默认AllocateMessageQueueAveragely)
6.订阅关系 --(map类型,即可订阅多个topic;key=Topic, value=订阅描述)
7.消费监听 --(必须为orderly or concurrently类型之一)
8.消费消息的线程数量控制 -- (消费线程池最大、最小数量)
9.检查单队列并行消费允许的最大跨度 --(consumeConcurrentlyMaxSpan)
10.检查拉消息本地队列缓存消息最大数 --(pullThresholdForQueue)(processQueue.getMsgCount()记数)
11.检查拉取时间间隔 --(拉消息间隔,由于是长轮询,所以默认为0)
12.检查批量消费的个数 --(一次消费多少条消息)
13.检查批量拉取消息的个数 --(一次最多拉多少条)
 
II. copySubscription:
将订阅信息设置到rebalanceImpl的map中用于负载。另外,如果该消费者的消费模式为集群消费,则会将retry的topic一并放到rebalanceImpl的map中用于负载。
 
III. 设置rebanlance信息
IV. 实例化pull消息的包装类型
 
V. 如果不存在offsetStore对象,实例化offsetStore
广播模式:
public class LocalFileOffsetStore implements OffsetStore {...}
注:load()函数体不为空
集群模式:
public class RemoteBrokerOffsetStore implements OffsetStore {...}
注:load()函数体为空
 
VI. 获取监听器,实例化consumeMessageService服务并启动
ConsumeMessageOrderlyService启动后会对拉取下来的消息进行处理。ConsumeMessageOrderlyService有两种类型:ConsumeMessageOrderlyService和ConsumeMessageConcurrentlyService。
1). 如果消息监听器是orderly类型,则创建ConsumeMessageOrderlyService实例
ConsumeMessageOrderlyService.start()只处理消息模式为CLUSTERING的消息消费。
public void start() {
if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
.messageModel())) {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
ConsumeMessageOrderlyService.this.lockMQPeriodically();
}
}, 1000 * 1, ProcessQueue.RebalanceLockInterval, TimeUnit.MILLISECONDS);
}
}
线程启动后会每隔20s执行lockMQPeriodicallys(),lockMQPeriodicallys()会将消费的队列上锁,然后处理,具体过程,有机会单独成文分析。
 
2). 如果消息监听器是concurrently类型,则创建ConsumeMessageConcurrentlyService实例
ConsumeMessageConcurrentlyService.start()会定时清除过期消息 --> cleanExpireMsg()。
 
VII. 注册消费组
将group和consumer注册到MQClientInstance实例。
与生产者注册生产者组类似,一个客户端进程中一个consumerGroup只能有一个实例。
MQConsumerInner prev = this.consumerTable.putIfAbsent(group, consumer);
if (prev != null) {
log.warn("the consumer group[" + group + "] exist already.");
return false;
}
如果没有注册成功,则关闭消费服务,consumeMessageService.shutdown()。
 
VIII. 启动mQClientFactory及MQClientInstance
1). 获取client实例对象MQClientInstance -- getAndCreateMQClientInstance。一个进程只能产生一个MQClientInstance实例对象, 某个客户端的生产者与消费者共用这个实例对象。
2). 启动客户端实例的个各种服务:
public void start() throws MQClientException {
synchronized (this) {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
// 1.判断NamesrvAddr是否为空,为空去远程http服务拉去地址
if (null == this.clientConfig.getNamesrvAddr()) {
this.clientConfig.setNamesrvAddr(this.mQClientAPIImpl.fetchNameServerAddr());
}
// 2.开启通信服务
this.mQClientAPIImpl.start();
// 3.启动各种定时任务
this.startScheduledTask();
// 4.启动消息拉取服务,循环拉取阻塞队列pullRequestQueue
this.pullMessageService.start();
// 5. 启动负载均衡服务
this.rebalanceService.start();
// 6.启动消息生产服务
this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
log.info("the client factory [{}] start OK", this.clientId);
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
break;
case SHUTDOWN_ALREADY:
break;
case START_FAILED:
throw new MQClientException("The Factory object[" + this.getClientId()
+ "] has been created before, and failed.", null);
default:
break;
}
}
}
分析push消费的过程,需对上述过程第3点、第4点、第5点依次介绍。
第3点、启动各种定时任务过程:
 编号  任务 周期 启动时延 
 获取namesrv地址 每隔2分钟  0.01s
2  更新路由信息 每隔3分钟  001s
3  向所有broker发送心跳包,并清除无效broker   每隔30s  1s
4  持久化消费位置offset 每隔5s  10s
5  调整消费线程池大小 每隔1分钟  1min
注:编号3中,客户端会通过心跳消息,向broker注册消费信息。Broker收到该心跳消息,把它维护在一个叫做ConsumerManager的对象里面,为之后做消费的负载均衡提供数据,负载均衡在消费端做,消费端在负载均衡时首先要从broker那获取这份全局信息。
 
第4点 启动pullMessageService服务
初始化客户端实例时,创建PullMessageService服务对象。
this.pullMessageService = new PullMessageService(this),其中PullMessageService继承于ServiceThread,是一个线程对象。启动消息拉取服务线程后,在线程没有阻塞的情况下会不断地从循环阻塞队列pullRequestQueue拉取PullRequest对象,然后执行this.pullMessage(pullRequest)。
 
那么pullRequestQueue的数据如何put进去的?核心是doRebalance ,负载均衡具体细节可以参考:http://www.cnblogs.com/chenjunjie12321/p/7913323.html

例如当前有N个客户端同时消费一个topic下的消息队列(如上图),当前客户端( clientId = currentCId),经过负载均衡处理后得到分配给当前消费者的消息队列(如上图的qM、qN),之后将这些队列与processQueueTable中的队列进行比对分析,见下面第五点。

 
第5点 RebalancePushImpl 负载均衡,分发pullRequest到pullRequestQueue。
负载均衡处理后得到分配给当前消费者的消息队列,然后将这些队列进行updateProcessQueueTableInRebalance 处理。updateProcessQueueTableInRebalance 的大致逻辑为如下 I、II 两步:
 

 
I. 首先检查当前RebalancePushImpl实例processQueueTable中与mqSet的包含关系
(1)如图中processQueueTable的灰色部分,表示与mqSet集合不互不包含的队列,对这些队列首先设置Dropped为true,然后看这些队列是否可以移除出processQueueTable--removeUnnecessaryMessageQueue,即每隔1s 看是否可以拿到当前队列的消费锁(tryLock()),拿到后返回true, 如果等待1s后仍然拿不到当前队列的消费锁则返回false,如果返回true则从processQueueTable移除对应的Entry<MessageQueue, ProcessQueue>;
 
(2) 如图中processQueueTable的白色部分,表示与mqSet集合的交集队列,对于这些队列,如果是消费类型是pull型,则不用管,如果是push型,看这些队列是否isPullExpired,如果是这些队列首先设置Dropped为true,则可以移除出processQueueTable--removeUnnecessaryMessageQueue。
 
II. 经过 I 处理,processQueueTable更新之后, 将processQueueTable集合与mqSet的的相对补集: processQueueTable(mq) - mqSet 里的消息队列依次封装成pullRequest,然后dispatchPullRequest到pullRequestQueue中。
 
经过上述处理后,待消费的队列放在了pullRequestList中,之后遍历pullRequestList,对遍历的每个队列进行消费,代码如下:
 @Override
public void dispatchPullRequest(List<PullRequest> pullRequestList) {
for (PullRequest pullRequest : pullRequestList) {
this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
}
}

executePullRequestImmediately的逻辑功能:

 public void executePullRequestImmediately(final PullRequest pullRequest) {
try {
this.pullRequestQueue.put(pullRequest);
} catch (InterruptedException e) {
log.error("executePullRequestImmediately pullRequestQueue.put", e);
}
}

总之,最终会将负载均衡得到的队列存放到pullRequestQueue。

 
回过来继续分析第4点,pullMessageService线程涉及到消费的核心过程DefaultMQPushConsumerImpl.pullMessage,pullMessageService线程线程体源码如下:
 @Override
public void run() {
log.info(this.getServiceName() + " service started"); while (!this.isStopped()) {
try {
PullRequest pullRequest = this.pullRequestQueue.take();
if (pullRequest != null) {
this.pullMessage(pullRequest);
}
} catch (InterruptedException e) {
} catch (Exception e) {
log.error("Pull Message Service Run Method exception", e);
}
} log.info(this.getServiceName() + " service end");
}

调用DefaultMQPushConsumerImpl.pullMessage方法:

 private void pullMessage(final PullRequest pullRequest) {
final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
if (consumer != null) {
DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
impl.pullMessage(pullRequest);
} else {
log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);

pullMessage具体体拉流程如下图所示:

 
 

下面对并发消费模型(concurrently)的消费代码进行展示:

class ConsumeRequest implements Runnable ,其线程体方法如下:
@Override
public void run() {
if (this.processQueue.isDropped()) {
log.info("the message queue not be able to consume, because it's dropped. group={} {}", ConsumeMessageConcurrentlyService.this.consumerGroup, this.messageQueue);
return;
} MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
ConsumeConcurrentlyStatus status = null; ConsumeMessageContext consumeMessageContext = null;
if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext = new ConsumeMessageContext();
consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup());
consumeMessageContext.setProps(new HashMap<String, String>());
consumeMessageContext.setMq(messageQueue);
consumeMessageContext.setMsgList(msgs);
consumeMessageContext.setSuccess(false);
ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
} long beginTimestamp = System.currentTimeMillis();
boolean hasException = false;
ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
try {
ConsumeMessageConcurrentlyService.this.resetRetryTopic(msgs);
if (msgs != null && !msgs.isEmpty()) {
for (MessageExt msg : msgs) {
MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis()));
}
}
status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
} catch (Throwable e) {
log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}",
RemotingHelper.exceptionSimpleDesc(e),
ConsumeMessageConcurrentlyService.this.consumerGroup,
msgs,
messageQueue);
hasException = true;
}
long consumeRT = System.currentTimeMillis() - beginTimestamp;
if (null == status) {
if (hasException) {
returnType = ConsumeReturnType.EXCEPTION;
} else {
returnType = ConsumeReturnType.RETURNNULL;
}
} else if (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * 60 * 1000) {
returnType = ConsumeReturnType.TIME_OUT;
} else if (ConsumeConcurrentlyStatus.RECONSUME_LATER == status) {
returnType = ConsumeReturnType.FAILED;
} else if (ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status) {
returnType = ConsumeReturnType.SUCCESS;
} if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name());
} if (null == status) {
log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}",
ConsumeMessageConcurrentlyService.this.consumerGroup,
msgs,
messageQueue);
status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
} if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
consumeMessageContext.setStatus(status.toString());
consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status);
ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);
} ConsumeMessageConcurrentlyService.this.getConsumerStatsManager()
.incConsumeRT(ConsumeMessageConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT); if (!processQueue.isDropped()) {
ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this);
} else {
log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs);
}
}

consumerRequest逻辑:

processConsumeResult -- 对消费结果进行处理:

重试队列发消息逻辑:

生成一个重试队列,重试队列topic =  %RETRY% + consumerGroup的形式。
 
 
 附:
值得注意的是每次消费pullRequest上的一条数据后上更新消费到达的 offset,然后将pullRequest.setNextOffset(offset);
//这里的 this 为一个 DefaultMQPushConsumerImpl 实例对象
final long offset = this.rebalanceImpl.computePullFromWhere(pullRequest.getMessageQueue());
...
pullRequest.setNextOffset(offset);

其中 computePullFromWhere采用的策略有如下三种(另外还有几个已经被弃用的(@Deprecated)):

CONSUME_FROM_LAST_OFFSET(默认): 一个新的消费集群第一次启动从队列的最后位置开始消费。后续再启动接着上次消费的进度开始消费。
CONSUME_FROM_FIRST_OFFSET: 一个新的消费集群第一次启动从队列的最前位置开始消费。后续再启动接着上次消费的进度开始消费。
CONSUME_FROM_TIMESTAMP: 一个新的消费集群第一次启动从指定时间点开始消费。后续再启动接着上次消费的进度开始消费。

DefaultMQPushConsumer 中默认采用 CONSUME_FROM_LAST_OFFSET 这种方式,当然可以根据自己需要修改computePullFromWhere的策略

private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;
IX. updateTopicSubscribeInfoWhenSubscriptionChanged
X. sendHeartbeatToAllBrokerWithLock
XI. rebalanceImmediately
 
(完)

rocketmq--push消费过程的更多相关文章

  1. RocketMQ的push消费方式实现的太聪明了

    大家好,我是三友,我又来了~~ 最近仍然畅游在RocketMQ的源码中,这几天刚好翻到了消费者的源码,发现RocketMQ的对于push消费方式的实现简直太聪明了,所以趁着我脑子里还有点印象的时候,赶 ...

  2. rocketmq消费负载均衡--push消费为例

    本文介绍了DefaultMQPushConsumerImpl消费者,客户端负载均衡相关知识点.本文从DefaultMQPushConsumerImpl启动过程到实现负载均衡,从源代码一步一步分析,共分 ...

  3. 关于RocketMQ消息消费与重平衡的一些问题探讨

    其实最好的学习方式就是互相交流,最近也有跟网友讨论了一些关于 RocketMQ 消息拉取与重平衡的问题,我姑且在这里写下我的一些总结. ## 关于 push 模式下的消息循环拉取问题 之前发表了一篇关 ...

  4. RocketMQ(7)---RocketMQ顺序消费

    RocketMQ顺序消费 如果要保证顺序消费,那么他的核心点就是:生产者有序存储.消费者有序消费. 一.概念 1.什么是无序消息 无序消息 无序消息也指普通的消息,Producer 只管发送消息,Co ...

  5. docker push 实现过程

    这一篇文章分析一下docker push的过程:docker push是将本地的镜像上传到registry service的过程: 根据前几篇文章,可以知道客户端的命令是在api/client/pus ...

  6. 【转】RocketMQ事务消费和顺序消费详解

    RocketMQ事务消费和顺序消费详解 转载说明:该文章纯转载,若有侵权或给原作者造成不便望告知,仅供学习参考. 一.RocketMq有3中消息类型 1.普通消费 2. 顺序消费 3.事务消费 顺序消 ...

  7. 一次 RocketMQ 顺序消费延迟的问题定位

    一次 RocketMQ 顺序消费延迟的问题定位 问题背景与现象 昨晚收到了应用报警,发现线上某个业务消费消息延迟了 54s 多(从消息发送到MQ 到被消费的间隔): 2021-06-30T23:12: ...

  8. 基于PBOC电子钱包的消费过程详解

    智能卡金融行业应用电子钱包的消费交易流程,开发人员可参考 首先终端和卡片有一个共同的密钥叫做消费密钥:PurchKey (针对每种特定的交易,比如,圈存,消费,都有特定的密钥与之对应) 假设Purch ...

  9. RocketMQ 顺序消费只消费一次 坑

    rocketMq实现顺序消费的原理 produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一 ...

随机推荐

  1. 【DUBBO】zookeeper在dubbo中作为注册中心的原理结构

    [一]原理图 [二]原理图解释 流程:1.服务提供者启动时向/dubbo/com.foo.BarService/providers目录下写入URL2.服务消费者启动时订阅/dubbo/com.foo. ...

  2. BZOJ1725,POJ3254 [Usaco2006 Nov]Corn Fields牧场的安排

    题意 Farmer John新买了一块长方形的牧场,这块牧场被划分成M列N行\((1 \leq M \leq 12, 1 \leq N \leq 12)\),每一格都是一块正方形的土地.FJ打算在牧场 ...

  3. PEP

    用python ,  PEP难以绕过.  PEP是什么?   Python Enhancement Proposals , 它集合了python的改进提案. 它不是版本递进的, 有些PEP是应该去读一 ...

  4. 13 Stream Processing Patterns for building Streaming and Realtime Applications

    原文:https://iwringer.wordpress.com/2015/08/03/patterns-for-streaming-realtime-analytics/ Introduction ...

  5. C#机器学习插件 ---- AForge.NET

    目录 简介 主要架构 特点 学习之旅 简介 AForge.NET是一个专门为开发者和研究者基于C#框架设计的,这个框架提供了不同的类库和关于类库的资源,还有很多应用程序例子,包括计算机视觉与人工智能, ...

  6. css之选择器的认识

    css中有大量的选择器,主要用来精准的找到代码中的某一段或者某一个段落,并对其样式进行选择和改变. 首先介绍的第一个选择器是: 1,基本选择器: 直接找到标签对其进行样式修正,不论标签藏多深,或者数量 ...

  7. Cannot read property 'setState' of undefined

    You're using function() in your Promise chain, this will change the scope for this. If you're using ...

  8. cowboy的中间件

    想不到cowboy这样的,居然也有中间件的概念,膜拜作者 创建工程 rebar-creator create-app testCowboy testCowboy_app.erl -module(tes ...

  9. 算法训练 安慰奶牛(节点有权值的MST)

    问题描述 Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路.道路被用来连接N个牧场,牧场被连续地编号为1到N.每一个牧场都是一个奶牛的家.FJ计划除去P条道路中尽可能多的道路, ...

  10. 在单片机上实现UDP

    http://blog.chinaunix.net/uid-18921523-id-260999.html