消息中间件-RocketMQ
简介
阿里巴巴双十一官方指定消息产品,支撑阿里巴巴集团所有的消息服务,历经十余年高可用与高可靠的严苛考验,是阿里巴巴交易链路的核心产品;
服务可用性 99.95%,Region 化、多可用区、分布式集群化部署,确保服务高可用,即便整个机房不可用仍可正常提供消息服务;
数据可靠性 99.99999999%,同步双写、超三副本数据冗余与快速切换技术确保数据可靠;
使用场景
异步解耦
向上面的假设每次发送请求的处理时间都要50ms,在没有任何优化的情况就要150ms。如果不用消息中间键的话我们可以用多线程的技术,启用两个线程分别运行发送短信和发送邮件的请求,这样我处理的总处理时间就是100ms。
使用消息中间键:
高可用松耦合架构设计
通过上、下游业务系统的松耦合设计,即便下游子系统出现不可用甚至宕机,都不会影响到核心交易系统的正常运转;
削峰填谷
商城项目,往往有这种秒杀活动,限时抢购活动,在某一时刻数据量会剧增。
-
超高流量脉冲处理能力
MQ 超高性能的消息处理能力可以承接流量脉冲而不被击垮,在确保系统可用性同时,因快速有效的请求响应而提升用户的体验;
-
海量消息堆积能力
确保下游业务在安全水位内平滑稳定的运行,避免超高流量的冲击;
-
合理的成本控制
通过削弱填谷可控制下游业务系统的集群规模,从而降低投入成本;
分布式缓存同步/消息分发
假设用户发送请求到a系统,a系统分发到b、c、d系统。比如处理a系统里面的业务,把处理完的数据分发到b、c、d中,如果这时候又有一个e系统该怎么处理?
用消息中间键,把a系统处理完的数据封装到里面去,其他的系统监控消息中间键里面的数据。
-
实时数据更新
通过消息实时推送的方式,让数据实时得以更新;
-
降低页面响应时间
大量并发访问商品数据库,减少页面响应时间
-
满足大规模访问需求
大促众多分会场,多缓存的架构设计,满足对商品变更的大量访问需求;
常见的消息中间键
ActiveMQ
ActiveMQ是Apache出品,比较老的一个开源的消息中间件, 是一个完全支持JMS规范的消息中间件.
API丰富,以前在中小企业应用广泛
MQ衡量的指标:服务性能,数据存储,集群架构
KafKa
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。
RabbitMQ
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
对数据的一致性,稳定性和可靠性要求比较高的场景
RocketMQ
RocketMQ 是阿里巴巴在 2012 年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于 2017 年 9 月 25 日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。
对比
RocketMQ的核心概念
生产者Producer
负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
消费者Consumer
负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。
假如有订单服务和商品服务,订单服务会把消息发送到消息中间里,所以订单服务就是生产者,反之商品服务就就是消费者。
名字服务Name Server
名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。
代理服务器Broker Server
消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
消息内容Message
消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。
消息主题Topic
表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
标签Tag
为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
消息队列MessageQueue
对于每个Topic都可以设置一定数量的消息队列用来进行数据的读取
RocketMQ环境搭建
下载RocketMQ
http://rocketmq.apache.org/
https://github.com/apache/rocketmq
window的安装配置
-
使用rocketmq-4.5.1.zip 解压到指定目录
-
需要配置环境变量ROCKETMQ_HOME
-
修改broker的配置文件
broker.conf
brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = SYNC_MASTER flushDiskType = SYNC_FLUSH enablePropertyFilter=true namesrvAddr=127.0.0.1:9876
-
启动nameserver
运行bin目录下的mqnameserver.cmd
-
启动broker
在bin目录下运行cmd,输入命令
mqbroker.cmd -c ../conf/broker.conf
-
启动管理控制台
在所在目录下创建配置文件
application.properties
rocketmq.config.namesrvAddr=127.0.0.1:9876 server.port=9999
运行
rocketmq-console-ng-1.0.1.jar
jar包,浏览器访问localhost:9999/
jar 包已上传:https://download.csdn.net/download/fine_Ning/12698310
Linux单机环境搭建
1 上传rocketmq-all-4.4.0-bin-release.zip 到/usr/local
2 使用解压命令进行解压
unzip /usr/local/rocketmq-all-4.4.0-bin-release.zip
3 软件重命名
mv /usr/local/rocketmq-all-4.4.0-bin-release/ /usr/local/rocketmq-4.4/
4 修改启动参数配置
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn1g"
vi /usr/local/rocketmq-4.4/bin/runbroker.sh
vi /usr/local/rocketmq-4.4/bin/runserver.sh
5 启动名字服务和代理服务
nohup sh /usr/local/rocketmq-4.4/bin/mqnamesrv &
# -n localhost:9876 指定名称服务的地址, 类似于zk的地址
nohup sh /usr/local/rocketmq-4.4/bin/mqbroker -n localhost:9876 &
6 检验是否启动正常
使用java的内置命令: jps 可以看到BrokerStartup和NamesrvStartup进程
使用Linux命令: netstat-ntlp 可以看到9876的端口和10911的端口
使用ps-ef |grep java
查看启动日志:
tail -100f ~/logs/rocketmqlogs/namesrv.log
tail -100f ~/logs/rocketmqlogs/broker.log
7 发送消息测试
# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.使用安装包的Demo发送消息
sh /usr/local/rocketmq-4.4/bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
8 消费消息测试
# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.使用安装包的Demo发送消息
sh /usr/local/rocketmq-4.4/bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
9 关闭RocketMQ
# 1.关闭NameServer
sh /usr/local/rocketmq-4.4/bin/mqshutdown namesrv
# 2.关闭Broker
sh /usr/local/rocketmq-4.4/bin/mqshutdown broker
核心基础使用
基本入门程序
导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.1</version>
</dependency>
发送消息
public class Producer {
public static void main(String[] args) throws Exception{
//1 创建一个生产者对象, 并且指定一个生产者组
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
//2 设置名字服务的地址
producer.setNamesrvAddr("127.0.0.1:9876");
//3 启动生产者
producer.start();
//4 创建一个消息
Message message = new Message("01-hello", "tagA", "hello,rocketmq".getBytes("utf-8"));
System.out.println(message);
//5 发送消息
producer.send(message);
//6 关闭连接
producer.shutdown();
}
}
消费消息
public class Consumer {
public static void main(String[] args) throws Exception{
//创建一个拉取消息的消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wolfcode-consumer");
//设置名字地址
consumer.setNamesrvAddr("127.0.0.1:9876");
//绑定消息的主题
consumer.subscribe("01-hello", "*");
//消费者监听处理消息方法
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("消费线程:"+Thread.currentThread().getName()+",消息ID:"+msg.getMsgId()+",消息内容:"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
}
}
发送消息方式
-
同步发送消息
生产者往消息中间键发送消息,需要等消息中间键把消息存到磁盘中,返回存储成功以后才可以继续往下执行。
-
异步消息
生产者往消息中间键扔消息,不需要等他存储成功以后才能执行后面的操作
-
一次消息
消息发送到消息中间键当中不关心他的返回结果
消费模式
-
集群模式
MessageModel.CLUSTERING 多个消费者分担一个消费者的压力, 一个消息只会给一个消费者消费
-
广播模式
MessageModel.BROADCASTING 需要对同一个消息进行不同处理的时候, 比如对同一个消息, 需要同时发送短信和发送邮件, 一个消息会发送给所有的消费者
消费方式
- 推送消费
DefaultMQPushConsumer
- 拉取消费
DefaultMQPullConsumer
延时消息
在商城的项目当中,用户下完单以后如果用户不去支付那么订单保留半个小时
延时消息的使用限制
现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18
private String messageDelayLevel = “1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;
消息消费失败会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关,详见代码
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception {
// 实例化一个生产者来产生延时消息
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动生产者
producer.start();
Message message = new Message("06-delay", ("delay message").getBytes());
// 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
message.setDelayTimeLevel(3);
// 发送消息
producer.send(message);
// 关闭生产者
producer.shutdown();
}
}
消息过滤
-
Tag标签过滤
在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wolfcode-consumer"); consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");
-
SQL92过滤
消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。
RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
- 数值比较,比如:>,>=,<,<=,BETWEEN,=;
- 字符比较,比如:=,<>,IN;
- IS NULL 或者 IS NOT NULL;
- 逻辑符号 AND,OR,NOT;
常量支持类型为:
- 数值,比如:123,3.1415;
- 字符,比如:‘abc’,必须用单引号包裹起来;
- NULL,特殊的常量
- 布尔值,TRUE 或 FALSE
只有使用push模式的消费者才能用使用SQL92标准的sql语句
注意: 在使用SQL过滤的时候, 需要在
broker.conf
配置文件中配置参数enablePropertyFilter=true
SpringBoot集成
导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
生产者
配置信息
rocketmq.name-server=127.0.0.1:9876
rocketmq.producer.group=my-group
server.port=8888
实现代码
@RestController
public class HelloController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@RequestMapping("sendMsg")
public String sendMsg(String message,String age) throws Exception{
SendResult sendResult = rocketMQTemplate.syncSend("01-boot-hello", message);
System.out.println(sendResult.getMsgId());
System.out.println(sendResult.getSendStatus());
return "success";
}
}
消费者
配置信息
rocketmq.name-server=127.0.0.1:9876
server.port=8887
实现代码
@Component
@RocketMQMessageListener(
topic = "01-boot-hello",
consumerGroup = "wolfcode-consumer"
)
public class HelloConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
System.out.println("消费消息"+messageExt);
}
}
生产消息类型
-
同步消息
rocketMQTemplate.syncSend("01-boot-hello", message);
-
异步消息
rocketMQTemplate.asyncSend("01-boot-hello", message, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { System.out.println("消息消费成功"); } @Override public void onException(Throwable e) { System.out.println("消息消费失败"); } });
-
一次性消息
rocketMQTemplate.sendOneWay("01-boot-hello", message);
消费模式
@RocketMQMessageListener
注解的配置项配置
- 集群
messageModel = MessageModel.CLUSTERING
- 广播
messageModel = MessageModel.BROADCASTING
延时消息
-
使用原生的Producer对象
DefaultMQProducer producer = rocketMQTemplate.getProducer();
-
使用API
rocketMQTemplate.syncSend("01-boot-hello", MessageBuilder.withPayload(message).build(), 3000, 3);
消息过滤
设置消息标签
在发送的消息Topic:Tag 中间使用冒号隔开
rocketMQTemplate.convertAndSend("01-boot-hello:TagB",message,map);
自定义属性设置
Map<String,Object> map=new HashMap<>();
//用户自定义属性
map.put("age", age);
map.put("name", "hesj");
//也可以设置系统属性
map.put(MessageConst.PROPERTY_KEYS,age);
rocketMQTemplate.convertAndSend("01-boot-hello:TagB",message,map);
消息过滤
在RocketMQMessageListener添加注解
selectorType = SelectorType.TAG,
selectorExpression = "age > 16"