消息中间件-RocketMQ
消息中间件简介
应用场景
https://www.aliyun.com/product/rocketmq?spm=a2c4g.11186623.2.26.1d81a08b7VKvxt
- 异步解耦
- 削峰填谷
- 分布式缓存同步/消息分发
消息中间件
-
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 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。
淘宝内部的交易系统使用了淘宝自主研发的 Notify 消息中间件,使用 MySQL 作为消息存储媒介,可完全水平扩容,为了进一步降低成本,我们认为存储部分可以进一步优化,2011 年初,Linkin开源了 Kafka 这个优秀的消息中间件,淘宝中间件团队在对 Kafka 做过充分 Review 之后, Kafka 无限消息堆积,高效的持久化速度吸引了我们,但是同时发现这个消息系统主要定位于日志传输,对于使用在淘宝交易、订单、充值等场景下还有诸多特性不满足,为此我们重新用 Java 语言编写了 RocketMQ ,定位于非日志的可靠消息传输(日志场景也OK),目前 RocketMQ 在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理, binlog 分发等场景。
消息中间件对比
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
* bin:启动脚本,包括shell脚本和CMD脚本
* conf:实例配置文件 ,包括broker配置文件、logback配置文件等
* lib:依赖jar包,包括Netty、commons-lang、FastJSON等
window的安装配置
-
使用rocketmq-4.5.1.zip 解压到指定目录
-
需要配置环境变量ROCKETMQ_HOME
注意:环境变量不可有空格 ,下图中会报错
然后配置到 PATH 变量中
-
修改broker的配置文件
namesrvAddr=127.0.0.1:9876
-
启动nameserver
在 bin 目录下打开命令窗口
mqnamesrv.cmd
-
启动broker
再次在 bin 目录下打开命令窗口
mqbroker.cmd -c ../conf/broker.conf
-
启动管理控制台
在 \rocketconsle 目录下,运行 jar 包
其中目录下的 application.properties 文件中指明了端口号和 nameserver 的地址
java -jar rocketmq-console-ng-1.0.1.jar
然后访问 http://localhost:9999
Linux 环境搭建
-
上传 rocketmq-all-4.4.0-bin-release.zip 到
/usr/local
-
使用解压命令进行解压
unzip /usr/local/rocketmq-all-4.4.0-bin-release.zip
-
软件重命名
mv /usr/local/rocketmq-all-4.4.0-bin-release/ /usr/local/rocketmq-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
-
启动名字服务和代理服务
nohup sh /usr/local/rocketmq-4.4/bin/mqnamesrv &
这是后台启动,可使用
netstat -ntlp
查看是否有 9876 启动# -n localhost:9876 指定名称服务的地址, 类似于zk的地址
nohup sh /usr/local/rocketmq-4.4/bin/mqbroker -n localhost:9876 &
-
检验是否启动正常
使用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
-
发送消息测试
-
设置环境变量
export NAMESRV_ADDR=localhost:9876
-
使用安装包的Demo发送消息
sh /usr/local/rocketmq-4.4/bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
-
-
消费消息测试
-
设置环境变量
export NAMESRV_ADDR=localhost:9876
-
使用安装包的Demo发送消息
sh /usr/local/rocketmq-4.4/bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
-
-
关闭RocketMQ
-
关闭NameServer
sh /usr/local/rocketmq-4.4/bin/mqshutdown namesrv
-
关闭Broker
sh /usr/local/rocketmq-4.4/bin/mqshutdown broker
监控平台搭建(可忽略)
-
只需要按照 Windows安装里面一样,修改 \rocketconsle 目录下的 application.properties 文件,修改 nameserver 的地址 ,然后 cmd 运行 jar 包
java -jar rocketmq-console-ng-1.0.1.jar
然后访问 http://localhost:9999
1 在服务器wolfcode-01 创建目录
mkdir /usr/local/rocketmq-console/
2 把 rocketmq-console-ng-1.0.1.jar 和application.properties 上传到 /usr/local/rocketmq-console/ 目录
3 启动管理控制台
nohup java -jar rocketmq-console-ng-1.0.1.jar &
4 访问管理控制台
http://192.168.26.129:9999
核心基础使用
基本入门程序
导入依赖
新建 producer-demo、consumer-demo 两个项目, 并都引入以下依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</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"));
//5 发送消息
SendResult send = producer.send(message);
System.out.println(send.getSendStatus()); //获取发送状态
//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();
}
}
发送消息方式
同步消息
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
// 设置NameServer的地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("04-producer-type" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes("utf-8") /* Message body */
);
//发送同步消息到一个Broker
//send方法 默认就是同步发送信息的方式
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.println(JSON.toJSONString(sendResult));
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
异步消息
public class ASyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
// 设置NameServer的地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动Producer实例
producer.start();
CountDownLatch count = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("04-producer-type" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes("utf-8") /* Message body */
);
//发送同步消息到一个Broker
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
count.countDown();
System.out.println("消息发送成功");
System.out.println(JSON.toJSONString(sendResult));
}
@Override
public void onException(Throwable e) {
count.countDown();
System.out.println("消息发送失败"+e.getMessage());
System.out.println("处理失败消息");
}
});
}
count.await();
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
一次性消息
public class OneWayProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
// 设置NameServer的地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("04-producer-type" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes("utf-8") /* Message body */
);
//发送同步消息到一个Broker
producer.sendOneway(msg);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
消费模式
//BROADCASTING:广播模式 会给所有的消费者发一份
//CLUSTERING: 集群模式 只会有一个消费者消费信息 默认值
consumer.setMessageModel(MessageModel.BROADCASTING);
集群模式
MessageModel.CLUSTERING 多个消费者分担一个消费者的压力,一个消息只会给一个消费者消费
广播模式
MessageModel.BROADCASTING 需要对同一个消息进行不同处理的时候,比如对同一个消息, 需要同时发送短信和发送邮件, 一个消息会发送给所有的消费者
消费方式
推送消费
public class PushConsumer {
public static void main(String[] args) throws Exception{
//创建一个拉取消息的消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wolfcode-consumer");
//设置名字地址
consumer.setNamesrvAddr("127.0.0.1:9876");
//指定从哪里开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//绑定消息的主题
consumer.subscribe("03-pull-push", "*");
//消费者监听处理消息方法
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
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();
}
}
拉取消费
public class PullConsumer {
public static void main(String[] args) throws Exception{
//创建一个拉取消息的消费者对象
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("wolfcode-consumer");
//设置名字地址
consumer.setNamesrvAddr("127.0.0.1:9876");
//启动消费者
consumer.start();
PullResult pullResult = consumer.pull(new MessageQueue("03-pull-push", "broker-a", 0), "*", 0, 1);
for (MessageExt messageExt : pullResult.getMsgFoundList()) {
System.out.println("消费线程:"+Thread.currentThread().getName()+",消息ID:"+messageExt.getMsgId()+",消息内容:"+new String(messageExt.getBody()));
}
consumer.shutdown();
}
}
延时消息
比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
延时消息的使用限制
现在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”;
消息消费失败会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关,详见代码SendMessageProcessor.java
生产者
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();
}
}
消费者
public class ScheduledMessageConsumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wolfcode_consumer");
consumer.setNamesrvAddr("127.0.0.1:9876");
// 订阅Topics
consumer.subscribe("06-delay", "*");
// 注册消息监听者
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
for (MessageExt message : messages) {
System.out.println("Receive message[msgId=" + message.getMsgId() + "] " );
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
}
}
消息过滤
在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(“wolfcode-consumer”);
consumer.subscribe(“TOPIC”, “TAGA || TAGB || TAGC”);
消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:
Tag标签过滤
SQL92过滤
基本语法
RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
- 数值比较,比如:>,>=,<,<=,BETWEEN,=;
- 字符比较,比如:=,<>,IN;
- IS NULL 或者 IS NOT NULL;
- 逻辑符号 AND,OR,NOT;
常量支持类型为:
- 数值,比如:123,3.1415;
- 字符,比如:‘abc’,必须用单引号包裹起来;
- NULL,特殊的常量
- 布尔值,TRUE 或 FALSE
只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下:
public void subscribe(finalString topic, final MessageSelector messageSelector)
注意: 在使用SQL过滤的时候, 需要配置参数enablePropertyFilter=true
生产者
public class Producer {
public static void main(String[] args) throws Exception{
//1 创建一个生产者对象, 并且指定一个生产者组
DefaultMQProducer producer = new DefaultMQProducer("wolfcode-producer");
//2 设置名字服务的地址
producer.setNamesrvAddr("192.168.26.129:9876;192.168.26.130:9876");
//3 启动生产者
producer.start();
//4 创建一个消息
Message message = new Message("07-filter", "tagA", "filter msg".getBytes("utf-8"));
message.putUserProperty("a", String.valueOf(4));
//5 发送消息
producer.send(message);
//6 关闭连接
producer.shutdown();
}
}
消费者
public class Consumer {
public static void main(String[] args) throws Exception{
//创建一个拉取消息的消费者对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wolfcode-consumer8");
//设置名字地址
consumer.setNamesrvAddr("192.168.26.129:9876;192.168.26.130:9876");
//指定从哪里开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//绑定消息的主题
// 只有订阅的消息有这个属性a, a >=0 and a <= 3
consumer.subscribe("07-filter", MessageSelector.bySql(" a > 3 and a < 8 "));
//消费者监听处理消息方法
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
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();
}
}
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=9999
实现代码
@RestController
public class HelloController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@RequestMapping("01-hello")
public String sendMsg(String message,String age) throws Exception{
//参数1:目的地;参数2:信息内容
SendResult sendResult = rocketMQTemplate.syncSend("01-boot:", message);
System.out.println(sendResult.getMsgId());
System.out.println(sendResult.getSendStatus());
return "success";
}
}
消费者
配置信息
rocketmq.name-server=127.0.0.1:9876
server.port=9999
实现代码
@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);
}
}
其他常见问题
1 生产消息类型
同步消息
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);
2 消费模式
@RocketMQMessageListener 注解的配置项配置
messageModel = MessageModel.CLUSTERING,
3 延时消息
-
使用原生的Producer对象
DefaultMQProducer producer = rocketMQTemplate.getProducer();
-
使用API
rocketMQTemplate.syncSend("01-boot-hello", MessageBuilder.withPayload(message).build(), 3000, 3);
4 设置消息标签
//在发送的消息Topic:Tag 中间使用冒号隔开
rocketMQTemplate.convertAndSend("01-boot-hello:TagB",message,map);
5 自定义属性设置
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);
过滤设置:
主要, 需要开启broker的支持用户属性配置
enablePropertyFilter=true
6 消息过滤
在 RocketMQMessageListener 添加注解
//在RocketMQMessageListener添加注解
selectorType = SelectorType.TAG,
selectorExpression = "age > 16"