1.认识MQ
1.1.什么是MQ
····MQ全称为Message Queue,即消息队列 ,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生 产、存储、消费全过程的软件系统,遵循FIFO原则。
1.2.为什么用MQ
····并发量高时,当服务器线程耗尽,后续请求会等待变慢,再加上高并发请求就会导致后续请求越来越慢,请求长时间等待,导致大量请求超时。对于一些慢操作并且不需要同步处理的业务,可以放入消息队列,消费者在消息队列里取任务,异步执行。
1.3.MQ使用场景
限流削峰:
MQ可以将系统的超量请求暂存其中,以便系统后期可以慢慢进行处理;
异步/解耦:
上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高;
而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ层;
数据收集:
分布式系统的数据量很大,对于一些需要收集的数据,比如需要记录的日志、服务运行状态等数据,可以批量采集汇总;
大数据处理:
与三方平台数据交互,请求了大量数据回来要进行处理,由于数据较多处理不过来,那么就可以放入MQ,再创建一些消费者进行数据处理即可。
····如果是体量不大的小项目,并发量也不高,使用MQ反而会降低效率;
····如果对数据一致性要求高,也不能用MQ。
1.4.常见的MQ
ActiveMQ:
Java语言开发一款MQ产品,早期很多公司与项目中都在使用,现在的社区活跃度很低;
RabbitMQ:
使用ErLang语言开发的一款MQ产品,吞吐量较Kafka与RocketMQ要低,且由于其不是 Java语言开发,所以公司内部对其实现定制化开发难度较大;
kafka:
使用Scala/Java语言开发,高吞吐率,常用于大数据领域的实 时计算、日志采集等场景;
RocketMQ:
使用Java语言开发的一款MQ产品,经过数年阿里双11的考验,性能与稳定性非常高。没有遵循任何常见的MQ协议,而是使用自研协议。
1.5.MQ常见协议
AMQP协议:
AMQP是一套公开的消息队列协议,最早在2003年被提出;
从协议层定义消息通信数据的标准格式, 为的就是解决MQ市场上协议不统一的问题;
基于此协议的客户端与消息中间件可传递 消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制;
JMS协议:
JMS是Java消息服务,java提供的一套消息服务API标准,目的是为所有的java应用程序提供统一的消息通信的标准;
jms是java语言专属的消 息服务标准,它是在api层定义标准,并且只能用于java应用;
MQTT协议:
MQTT,Message Queuing Telemetry Transport(消息队列遥测传输),是IBM开发的一个即时通讯协 议,是一种二进制协议;
用于服务器和低功耗IoT(`物联网`)设备间的通信。
2.RocketMQ介绍
本篇文章记录RocketMQ的简单使用
2.1.RocketMQ是什么
RocketMQ是一个统一消息引擎、轻量级数据处理平台;
RocketMQ是⼀款阿⾥巴巴开源的消息中间件,双十一承载了万亿级消息的流转;
2016年11⽉,阿⾥巴巴向 Apache 软件基⾦会捐赠 RocketMQ,成为 Apache 孵化项⽬;
2017年9⽉,Apache 宣布 RocketMQ孵化成为 Apache顶级项⽬(TLP )成为国内⾸个互联⽹中间件在 Apache 上的顶级项⽬。
2.2.RocketMQ特性
-
支持集群模型、负载均衡、水平扩展能力
-
亿级别消息堆积能力
-
采用零拷贝的原理,顺序写盘,随机读
-
底层通信框架采用Netty NIO
-
NameServer代替Zookeeper,实现服务寻址和服务协调
-
消息失败重试机制、消息可查询
-
强调集群无单点,可扩展,任意一点高可用,水平可扩展
-
经过多次双十一的考验
3.RocketMQ的安装
····下载:http://rocketmq.apache.org;
····解压后配置环境变量:ROCKETMQ_HOME:E:\javasoftware\rocketmq-all-4.2.0-bin-release;(配置一个HOME即可,不用配置到bin目录下)。
3.1.启动MQ
····cmd命令框执行进入至MQ文件夹\bin
下,然后执行 start mqnamesrv.cmd
,启动NameServer。
保留弹出的提示框,别关掉。
这里是启动了mq的nameserver,相当于我们使用微服务的nacos注册中心(只是这样理解);
然后将MQ即Broker和可视化界面都注册到namesever上。
3.2.启动Broker
····进入至MQ文件夹\bin
下,修改Bean目录下的 runbroker.cmd
中JVM占用内存大小。
····根据自己的情况设置;
····cmd执行start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
,启动Broker。
····Broker启动成功(RocketMQ启动成功);
3.3.可视化管理插件
····下载:https://github.com/apache/rocketmq-externals/releases;
····解压后,修改配置:src/main/resource/application.properties ,这里需要指向Name Server 的地址和端口。
····回到安装目录(pom.xml所在目录),执行:
mvn clean package -Dmaven.test.skip=true
,然后会在target目录生成打包后的jar文件。
····启动:进入 target 目录,执行 java -jar rocketmq-console-ng-1.0.0.jar
, 访问 http://localhost:8080
3.4.创建cmd文件
创建cmd文件,将启动步骤整合
start bin\mqnamesrv.cmd
start bin\mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
java -jar rocketmq-console-ng-1.0.0.jar
将可视化的jar包放在了rocketmq的包下。
4.RocketMQ原理
4.1.结构
https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md
····RocketMQ主要由 Producer、Broker、Consumer、NameServer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器。
····为了实现高可用,Broker本身是主备架构,Master负责写请求和读请求,Slave负责数据备份和分担读请求。这样的好处是提高了吞吐量同时防止数据丢失。
Producer:消息发布的角色,支持分布式集群方式部署;
Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。
-----------------------------------------------------------------------------------------
Consumer:消息消费的角色,支持分布式集群方式部署;
支持以push推,pull拉两种模式对消息进行消费;
同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制;
-----------------------------------------------------------------------------------------
Broker:Broker主要负责消息的存储、投递和查询以及服务高可用保证;
-----------------------------------------------------------------------------------------
NameServer:NameServer是一个Broker与Topic路由的注册中心支持Broker的动态注册与发现;
Broker管理:
NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据;
提供心跳检测机制,检查Broker是否还存活;
路由信息管理:
每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息;
然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。
4.2.RocketMQ入门
https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md
4.2.1.导入依赖
需要和安装的MQ版本一致
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
4.2.2.生产者
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("producer-hello");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message message = new Message("topic-order", "tags-order-flow", "发送了一个订单物流消息".getBytes("utf-8"));
SendResult result = producer.send(message);
System.out.println(result);
}
}
4.2.3.消费者
public interface Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-order");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic-order", "tags-order-flow");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(new java.util.function.Consumer<MessageExt>() {
@Override
public void accept(MessageExt messageExt) {
String msg = new String(messageExt.getBody(), StandardCharsets.UTF_8);
System.out.println(msg);
}
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
4.2.4.工作原理图
-
启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
-
Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
-
收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
-
Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
-
Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
一个Topic可以对应多个消费者 ,一个Queue只能对应一个组中的一个消费者
为了防止消息紊乱,一个Consumer Group 中的Consumer都是订阅相同Topic下的Queue
消息拉取:
-
Pull:拉取式,需要消费者间隔一定时间就去遍历关联的Queue,实时性差但是便于应用控制消息的拉取
-
Push:默认推送式,封装了Queue的遍历,实时性强,但是对系统资源占用比较多。
消费模式:
-
广播模式:
同一个Consumer Group 下的所有Consumer都会受到同一个Topic的所有消息。同一个消息可能会被消费多次
。 -
集群模式(默认):同一个Gonsumer Group 下的Consumer平分同一个Topic下的消息。
同一个消息只是被消费一次
。
队列分配算法:
Queue是如何分配给Consumer的,这对应了四种算法:平均分配策略,环形平均策略,一致性Hash策略,同机房策略。
-
平均分配【默认】:根据 qeueuCount / consumerCount 作为每个消费者平均分配数量,如果多出来的queue就再依次逐个分配给Consumer。
-
环形平均策略:根据消费者的顺序,一个一个的分配Queue即可类似于发扑克牌。
-
一致性Hash策略 : 该算法将Consumer的Hash值作为节点放到Hash环上,然后将Queue的hash值也放入Hash环上,通过顺时针进行就近分配。
-
同机房策略:该算法会根据queue的部署机房位置和consumer的位置,过滤出当前consumer相同机房的queue。然后按照平均分配策略或环形平均策略对同机房queue进行分配。如果没有同机房queue,则按照平均分配策略或环形平均策略对所有queue进行分配。
····平均分配性能比较高,一致性Hash性能不高,但是能减少Rebalance,如果Consumer数量变动频繁可以使用一致性Hash。
Offset管理
RockertMQ通过Offset来维护Consumer的消费进度,比如:消费者从哪个位置开始持续消费消息的?这里有三个枚举来指定从什么位置消费
CONSUME_FROM_LAST_OFFSET:从queue的最后一条消息开始消费
CONSUME_FROM_FIRST_OFFSET:从queue的第一条消息开始消费
CONSUME_FROM_TIMESTAMP:从某个时间戳位置的消息开始消费
。
消费者消费结束之后,会向Consumer会提交其消费进度offset给Broker。Offset信息的存储分为本地 Offset管理 和远程Offset管理
- 远程Offset管理:Brocker通过 store/config/consumerOffset.json 文件以JSON方式来存储offset相关数据以json的形式:适用于集群模式
- 本地Offset管理:offset相关数据以json的形式持久化到Consumer本地磁盘文件中,路径为当前用户主目录下的.rocketmq_offsets/ c l i e n t I