【不同类型的消费者】

DefaultMQPushConsumer

由系统控制读取操作,收到消息后自动调用传入的处理方法来处理。

DefaultMQPullConsumer

读取操作中的大部分功能由使用者自动控制。

【DefaultMQPushConsumer的使用】

[特点]

1.系统收到消息后自动调用处理方法来处理消息,自动保存Offset。

2.加入的新的DefaultMQPushConsumer会自动做负载均衡。

public class QuickStart {
/**
* DefaultMQPushConsumer需要配置三个参数
* 1.Consumer的GroupName
* 2.NameServer的地址和端口号
* 3.Topic的名称
*/

public static void main(String[] args) throws InterruptedException, MQClientException {
DefaultMQPushConsumer Consumer = new DefaultMQPushConsumer("unique_group_name_1"); //1.GroupName
Consumer.setNamesrvAddr("127.0.0.1:9876;127.0.0.2:9876"); //2.NameServer的地址和端口号
Consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
Consumer.setMessageModel(MessageModel.BROADCASTING);
Consumer.subscribe("TopicTest", "*"); //3.Topic的名称
Consumer.registerMessageListener(new MessageListenerConcurrently() {
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();
}
}

[ 注意1:关于GroupName ]

RocketMQ支持两种消息模式:Clustering(集群消费) 和 Broadcasting(广播消费)。

1.Clustering模式 (即P2P模式)

同一个ConsumerGroup(即相同的GroupName)里的每个Consumer只消费订阅消息的一部分,同一个ConsumerGroup里所有的Consumer消费的内容合起来才是所订阅的Topic内容的整体,从而达到负载均衡的目的。

2.Broading模式(即发布-订阅模式)

同一个ConsumerGroup里的每个Consumer都能消费到所订阅的Topic的消息,就是一个消息会被多次分发,被多个Consumer消费。

[ 注意2:关于NameServer配置 ]

NameServer的地址和端口号,可以填写多个,用 ";" 隔开,达到消除单点故障的目的。如"ip1:port;ip2:"

[ 注意3:Topic的配置 ]

Topic的名称用来标识消息类型,需要填创建。如果不需要消费某个Topic下的所有消息,可以通过指定消息的Tag进行消息过滤,比如:

//表示这个Consumer只消费"TopicTest"下的带有tag1、tag2、tag3的消息
Consumer.subscribe("TopicTest", "tag1||tag2||tag3");

Tag是发消息时设置的标签,在填写Tag参数的位置,用null或"*"表示要消费这个Topic的所有消息。

【DefaultMQPushConsumer的处理流程】

DefaultMQPushConsumer的主要功能实现在DefaultMQPushConsumerImpl中,消息处理在pullMessage的PullCallBack中。

在PullCallBack中有个Switch语句,根据Broker返回的消息类型做对应的处理。

DefaultMQPushConsumer的源码中有很多PullRequest语句,比如

DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest)

为什么Push的Consumer中会出现PullRequest相关的代码呢?

通过长轮询的方式达到Push效果的方法,长轮询的方式既有Pull的优点,也有Push方式的实时性。

[ 补充——Push方式和Pull方式的区别 ]

1.Push方式

过程:Server收到消息后,主动把消息推送给Client端。

优点:实时性高。

缺点:

加大了Server端的工作量,会影响Server的性能。

Client端处理能力各不相同,Client的状态不收Server控制,如果Client端不能及时处理Server推送过来的消息,会造成各种潜在的问题。

2.Pull方式

过程:Client端循环地从Server端拉取消息,主动权在Client手里,自己拉取到一定量的消息后,处理完成之后继续取。

缺点:

循环拉取的消息间隔时间不好设定,间隔太短就处于忙等状态,浪费资源;间隔太长,消息不能被及时处理。

[ 长轮询的方式 ]

长轮询的方式通过Client端和Server端的配合,达到了既有Pull方式的优点,也能达到保证实时性的目的。

[ 长轮询的发送Pull消息的代码片段 ]

拼接PullMessageRequestHeader,然后作为消息参数发送。

PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
requestHeader.setConsumerGroup(this.ConsumerGroup);
requestHeader.setTopic(mq.getTopic());
requestHeader.setQueueId(mq.getQueueid());
requestHeader.setQueueOffset(Offset);
requestHeader.setMaxMsgNums(maxNums);
requestHeader.setSysFlag(sysFlaginner);
requestHeader.setCommitOffset(commitOffset);
//设置了Broker的最长阻塞时间,默认15秒,Broker没有消息时才阻塞,有消息会立刻返回。
requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
requestHeader.setSubscription(subExpression);
requestHeader.setSubVersion(subVersion);
requestHeader.setExpressionType(expressionType);
PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
brokerAddr, requestHeader, timeoutMillis, communicationMode, pullCallback);

[ 长轮询的Broker服务端代码 ]

//如果队列里没有消息
if (this.brokerController.getBrokerConfig().isLongPollingEnable()) {
this.waitForRunning( * ); //等待5S
} else {
this.waitForRunning(this.brokerController.getBrokerConfig().
getShortPollingTimeMills());
long beginLockTimestamp = this.systemClock.now();
this.checkHoldRequest();
long costTime = this.systemClock.now() - beginLockTimestamp;
if (costTime > * ) {
Log.info("[NOTIFYME] check hold request cost {} ms.", costTime);
}
}

  从Broker的源码中看,服务端收到新消息请求时,如果队列里没有消息,并不急于返回,而是循环不断的查看状态,每次waitForRunning一段时间(默认5S),然后再check。默认情况下当Broker一直没有新消息,第三次check的时候,等待时间超过了RequestHeader里面的SuspendTimeoutMillis,就会返回空结果。

  在等待的过程中,Broker收到了新的消息后会直接调用notifyMessageAriving方法返回的请求结果。

[ 长轮询小结 ]

长轮询的核心是,Broker端HOLD住客户端发送过来的请求一小段时间,在这个时间里有新的消息到达,就利用现有的连接立即返回给Consumer。

长轮询的主动权还是掌握在Consumer手中,Broker即使有大量的消息积压,也不会主动推送给Consumer。

局限性:

在HOLD住Consumer请求的时候需要占用资源,它适合用在消息队列这种客户端连接可控的场景。

【DefaultMQPush的流量控制】

PushConsumer的核心还是Pull方式,所以采用这种方式的客户端能根据自身的处理速度调整获取消息的操作速度。

PushConsumer有一个线程池,消息处理逻辑在各个线程里同时执行,线程池定义如下:

this.consumeExecutor = new ThreadPoolExecutor(
this.defaultMQPushConsumer.getConsumeThreadMin(),
this.defaultMQPushConsumer.getConsumeThreadMax(),
* ,
TimeUnit.MILLISECONDS,
this.consumeRequestQueue,
new ThreadFactoryimpl("ConsumeMessageThread")
);

  Pull拉的消息,如果直接提交到线程池,很难监控和控制,比如当前消息堆积数量、消息是否重复执行、如何延迟处理某些消息。这些问题,都用一个快照类ProcessQueue来解决,在PushConsumer运行的时候,每个Message Queue都有个对应的ProcessQueue对象,保存这个Message Queue消息处理状态的快照。

[ ProcessQueue对象 ]

主要组成:一个TreeMap + 一个读写锁。

[ PushConsumer的流量控制 ]

有了ProcessQueue对象,流量的控制就方便多了。

PushConsumer会判断下面三个数据:

1.获取但还未处理的消息个数;

2.消息的总大小;

3.Offset的跨度;

任何一个值超过设定的大小就会隔一段时间再拉取,从而达到流量控制的目的。

【 DefaultMQPullConsumer 】

使用DefaultMQPullConsumer像使用DefaultMQPushConsumer一样需要设置各种参数,写处理消息的方法等。

[ PullConsumer示例代码 ]

public class PullConsumer {
private static final Map<MessageQueue, Long> OFFSE_TABLE = new HashMap<>(); public static void main(String[] args) throws MQClientException {
DefaultMQPullConsumer Consumer = new DefaultMQPullConsumer("group_name_A");
Consumer.start();
Set<MessageQueue> mqs = Consumer.fetchSubscribeMessageQueues("TopicTest");
/** 1.获取MessageQueue,并遍历 **/
for (MessageQueue mq : mqs) {
/** 2.维护Offsetstore **/
long Offset = Consumer.fetchConsumeOffset(mq, true);
System.out.printf("Consume from the Queue:" + mq + "%n");
SINGLE_MQ:
while (true) {
try {
PullResult pullResult = Consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), );
System.out.printf("%s%n", pullResult);
putMessageQueueOffset(mq, pullResult.getNextBeginOffset());
/** 3.根据不同的消息状态做不同的处理 **/
switch (pullResult.getPullStatus()) {
case FOUND:
break;
case NO_MATCHED_MSG:
break;
case NO_NEW_MSG:
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
Consumer.shutdown();
} }
} private static long getMessageQueueOffset(MessageQueue mq){
Long Offset = OFFSE_TABLE.get(mq);
if(Offset!=null){
return Offset;
}
return ;
} private static void putMessageQueueOffset(MessageQueue mq,long Offset){
OFFSE_TABLE.put(mq,Offset);
}
}

[ PullConsumer注意点 ]

示例代码是逐个读取某个Topic下的所有MessageQueue的内容,主要做了三件事:

1.获取MessageQueue,并遍历

一个Topic包含多个MessageQueue。

如果这个Consumer需要获取这个Topic下的所有消息,就要遍历所有的MessageQueue。如果有特殊情况,也可以选择某些特定的MessageQueue来读取消息。

2.维护Offsetstore

从一个MessageQueue里拉取消息时,要传入Offset参数,随着不断的读取消息,Offset不断怎张。此时需要由用户负责把Offset存储下来,可以根据具体情况存到内存、磁盘或者数据中。

3.根据不同的消息状态做不同的处理

拉取消息的请求发出后,会返回下面4种状态码:

/**
* 拉取消息状态码
*/
public enum PullStatus {
/**
* Founded
*/
FOUND,
/**
* No new message can be pull
*/
NO_NEW_MSG,
/**
* Filtering results can not match
*/
NO_MATCHED_MSG,
/**
* Illegal offset,may be too big or too small
*/

OFFSET_ILLEGAL

}

比较重要的是这2个状态码:

FOUND   获取到消息;

NO_NEW_MESSAGE 没有新的消息;

【Consumer的启动、关闭流程】

[1.PullConsumer]

PullConsumer的主动权很高,可以根据实际需要暂停、停止、启动消费者。

[ 注意 ]

PullConsumer的重点是Offset的保存,需要再代码中异常处理部分增加这样的处理:把Offset写入磁盘,记住每个MessageQueue的Offset,才能保证消息消费的准确性。

[ 2.PushConsumer ]

DefaultMQPushConsumer的退出:

调用shutdown()方法,以便释放资源,保存Offset等。(这个调用要加到Consumer所在应用的处理逻辑中)

PushConsumer启动:

PushConsumer启动的时候,会做各种配置的检查,然后连接NameServer获取Topic信息,启动时如遇到异常,如无法连接NameServer,程序依然可以正常启动不报错(日志里会有Warn信息)。

【为什么DefaultPushMQConsumer在无法连接NameServer时不报错?】

和分布式系统的设计有关,RocketMQ集群可以有多个NameServer、Broker,某个机器出异常后整体服务依然可用。

所以DefaultMQPushConsumer被设计成当发现某个连接异常时,不立即退出,而是不断尝试重新连接。

【如果要在DefaultMQPushConsumer启动的时候,及时暴露配置问题(及时报错),如何处理?】

可以在Consumer.start()语句后调用:Consumer.fetchSubscribeMessageQueues("TopicName"),这时如果配置信息不准确,或者当前服务不可用,会报MQClientException异常。

RocketMQ读书笔记3——消费者的更多相关文章

  1. RocketMQ读书笔记7——吞吐量优先的场景

    [Broker端进行消息过滤] 在Broker端进行消息过滤,可以减少无效消息发送到Consumer,少占用网络宽带从而提高吞吐量. [过滤方式1——通过Tag过滤] [ 关于Tag和Key ] 对一 ...

  2. RocketMQ读书笔记6——可靠性优先的使用场景

    [顺序消息] 顺序消费是指消息的产生顺序和消费顺序相同. 比如订单的生成.付款.发货,这三个消息必须按顺序处理才可以. [顺序消息的分类] 全局顺序消息和部分顺序消息. 上面订单的例子,其实是部分顺序 ...

  3. RocketMQ读书笔记5——消息队列的核心机制

    [Broker简述] Broker是RocketMQ的核心,大部分“重量级”的工作都是由Broker完成的,包括: 1.接受Producer发过来的消息: 2.处理Consumer的消费信息请求: 3 ...

  4. RocketMQ读书笔记4——NameServer(MQ的协调者)

    [NameServer简述] 对于一个消息队列集群来说,系统由很多机器组成,每个机器的角色.IP地址都不相同,而且这些信息是变动的(如在某些情况下,会有新的Producer或Consumer加入). ...

  5. RocketMQ读书笔记2——生产者

    [生产者的不同写入策略] 生产者向消息队列里写入数据,不同的业务需要生产者采用不同的写入策略: 同步发送.异步发送.延迟发送.发送事务消息等. [DefaultMQProduce示例] public ...

  6. RocketMQ读书笔记1——简述

    [消息队列的功能介绍] 分布式消息队列可以提供应用解耦.流量削峰.消息分发.保证最终一致性.方便动态扩容等功能. [MQ使用场景1——应用解耦] 复杂的系统如电商系统,会存在多个子系统,如订单系统.库 ...

  7. <<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步(1)

    <<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步 并发问题是所有问题的基础,也是操作系统设计的基础.并发包括很多设计问题,其中有进程间通信,资源共享与竞争,多个 ...

  8. Linux 之父自传《just for fun》读书笔记

    一次偶然的机会,看到了阮一峰老师关于这本书的介绍,当时我就觉得这本书相当有趣. 在没有读这本书之前,我觉得 linus 作为发明 Linux 系统的人,应该是一个比较严肃的人,就像我的老师一样.但事实 ...

  9. 《玩转Django2.0》读书笔记-Django建站基础

    <玩转Django2.0>读书笔记-Django建站基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.网站的定义及组成 网站(Website)是指在因特网上根据一 ...

随机推荐

  1. FPGA基础学习(6) -- 原语

    目录 1. IBUF和IBUFDS(IO) 2. IDDR(Input/Output Functions) 3. IBUFG和IBUFGDS(IO) 原语,即primitive.不同的厂商,原语不同: ...

  2. [大数据入门]实战练习 安装Cloudera-Hadoop集群

    实验环境规划   Hostname IP OS Roles Machine 0 elephant 192.168.124.131     Machine 1 tiger 192.168.124.132 ...

  3. (转)基于CentOS 7安装Zabbix 3.4和Zabbix4.0

    原文:https://blog.csdn.net/leshami/article/details/78708049 CentOS 7环境下Zabbix4.0的安装和配置实例-----------htt ...

  4. AngularJs学习笔记--unit-testing

    原版地址:http://docs.angularjs.org/guide/dev_guide.unit-testing javascript是一门动态类型语言,这给她带来了很强的表现能力,但同时也使编 ...

  5. 生产html测试报告

    批量执行完用例后,生成的测试报告是文本形式的,不够直观,为了更好的展示测试报告,最好是生成 HTML 格式的.unittest 里面是不能生成 html 格式报告的,需要导入一个第三方的模块:HTML ...

  6. android 小工具:pc 上用 curl 命令打开手机浏览器,浏览指定网址

    测试 API 时或其它情况经常需要在手机浏览器中输入 url 一长串的 url 输起来真是麻烦 AirDroid 很强大也不用数据线,但有时老断开连接,不是很爽.发到手机 qq 吧还得手动粘贴 所以自 ...

  7. robots 小记

    简介 网站所有者使用/robots.txt文件向网站机器人提供有关其网站的说明;这称为 Robots Exclusion Protocol.它的工作原理是这样的:robot 想要访问一个网站URL,比 ...

  8. 以cmd命令行方式执行php文件时,传递参数

    1. php自带的两个参数$argc, $argv: 1.1. $argv : (后面的v是variables的意思),传递进来的参数会以数组的方式保持在这个变量里 1.2. $argc : (后面的 ...

  9. 设计模式之第7章-外观模式(Java实现)

    设计模式之第7章-外观模式(Java实现) “鱼哥,知道怎么把大象装进冰箱里面么?”(作者按:这么简单的问题还想考我,早了几百年吧.)“把大象装进冰箱里,一共需要三步:第一步,把冰箱门打开:第二步,把 ...

  10. 九度oj 1437 To Fill or Not to Fill 2012年浙江大学计算机及软件工程研究生机试真题

    题目1437:To Fill or Not to Fill 时间限制:1 秒 内存限制:128 兆 特殊判题:否 提交:1488 解决:345 题目描述: With highways availabl ...