22. Rocketmq集成SpringBoot
22.1 搭建rocketmq-producer(消息生产者)
22.1.1 创建项目,完整的pom.xml
_<?_xml version="1.0" encoding="UTF-8"_?>_
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 [https://maven.apache.org/xsd/maven-4.0.0.xsd"](https://maven.apache.org/xsd/maven-4.0.0.xsd%22)
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
_<!-- lookup parent from repository --> _
</parent>
<groupId>com.powernode</groupId>
<artifactId>01-rocketmq-producer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rocketmq-producer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>** **
**_<!-- rocketmq的依赖 --> _**
**<dependency> **
**<groupId>org.apache.rocketmq</groupId> **
**<artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.0.2</version> **
**</dependency> **
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
22.1.2 修改配置文件application.yml
spring:
application:
name: rocketmq-producer
rocketmq:
name-server: 127.0.0.1:9876 # rocketMq的nameServer地址
producer:
group: powernode-group # 生产者组别
send-message-timeout: 3000 # 消息发送的超时时间
retry-times-when-send-async-failed: 2 # 异步消息发送失败重试次数
max-message-size: 4194304 # 消息的最大长度
22.1.3 我们在测试类里面测试发送消息
往powernode主题里面发送一个简单的字符串消息
/** * 注入rocketMQTemplate,我们使用它来操作mq */
@Autowiredprivate RocketMQTemplate rocketMQTemplate;
/** * 测试发送简单的消息 * * @throws Exception */
@Testpublic void testSimpleMsg() throws Exception {
// 往powernode的主题里面发送一个简单的字符串消息
SendResult sendResult = rocketMQTemplate.syncSend("powernode", "我是一个简单的消息");
// 拿到消息的发送状态
System._out_.println(sendResult.getSendStatus());
// 拿到消息的id
System._out_.println(sendResult.getMsgId());
}
运行后查看控制台
22.1.4 查看rocketMq的控制台
查看消息细节
22.2 搭建rocketmq-consumer(消息消费者)
22.2.1 创建项目,完整的pom.xml
_<?_xml version="1.0" encoding="UTF-8"_?>_
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 [https://maven.apache.org/xsd/maven-4.0.0.xsd"](https://maven.apache.org/xsd/maven-4.0.0.xsd%22)
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> ** **
**<version>2.6.3</version>**
<relativePath/>
_<!-- lookup parent from repository --> _
</parent>
<groupId>com.powernode</groupId>
<artifactId>02-rocketmq-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rocketmq-consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> ** **
**_<!-- rocketmq的依赖 --> _**
**_ _<dependency> **
**<groupId>org.apache.rocketmq</groupId> **
**<artifactId>rocketmq-spring-boot-starter</artifactId>**
** <version>2.0.2</version> **
**</dependency>**
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
22.2.2 修改配置文件application.yml
| spring:
application:
name: rocketmq-consumerrocketmq:
name-server: 127.0.0.1:9876
| — |
22.2.3 添加一个监听的类SimpleMsgListener
消费者要消费消息,就添加一个监听
package com.powernode.listener;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
_/** * 创建一个简单消息的监听 * _
_1.类上添加注解@Component和@RocketMQMessageListener *
@RocketMQMessageListener(topic = "powernode", consumerGroup = "powernode-group") *
topic指定消费的主题,consumerGroup指定消费组,一个主题可以有多个消费者组,一个消息可以被多个不同的组的消费者都消费 * _
_2.实现RocketMQListener接口,**注意泛型的使用,可以为具体的类型,如果想拿到消息 * 的其他参数可以写成MessageExt** */_
@Component@RocketMQMessageListener(topic = "powernode", consumerGroup = "powernode-group",messageModel = MessageModel._CLUSTERING_)public class SimpleMsgListener implements RocketMQListener<String> {
_/** * 消费消息的方法 * * @param message */ _
@Override public void onMessage(String message) {
System._out_.println(message);
}
}
22.2.4 启动rocketmq-consumer
查看控制台,发现我们已经监听到消息了
23. RocketMQ发送对象消息和集合消息
我们接着在上面项目里面做
23.1 发送对象消息
主要是监听的时候泛型中写对象的类型即可
23.1.1 修改rocketmq-producer添加一个Order类
package com.powernode.domain;
import lombok.AllArgsConstructor;import lombok.Data;
import lombok.NoArgsConstructor; import java.util.Date;
/** * 订单对象 */
@Data@AllArgsConstructor@NoArgsConstructorpublic class Order {
/** * 订单号 */
private String orderId;
/** * 订单名称 */
private String orderName;
/** * 订单价格 */
private Double price;
/** * 订单号创建时间 */
private Date createTime;
/** * 订单描述 */
private String desc;
}
23.1.2 修改rocketmq-producer添加一个单元测试
_/** * 测试发送对象消息 * * @throws Exception */_
@Testpublic void testObjectMsg() throws Exception {
Order order = new Order();
order.setOrderId(UUID._randomUUID_().toString());
order.setOrderName("我的订单");
order.setPrice(998D);
order.setCreateTime(new Date());
order.setDesc("加急配送");
_// 往powernode-obj主题发送一个订单对象 _
rocketMQTemplate.syncSend("powernode-obj", order);
}
23.1.3 发送此消息
23.1.4 修改rocketmq-consumer也添加一个Order类(拷贝过来)
23.1.5 修改rocketmq-consumer添加一个ObjMsgListener
package com.powernode.listener; import com.powernode.domain.Order;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/** * 创建一个对象消息的监听
* 1.类上添加注解@Component和@RocketMQMessageListener
* * 2.实现RocketMQListener接口,注意泛型的使用 */
@Component@RocketMQMessageListener(topic = "powernode-obj", consumerGroup = "powernode-obj-group")
public class ObjMsgListener implements RocketMQListener<**Order**> {
/** * 消费消息的方法 * * @param message */
@Override public void onMessage(**Order **message) {
System._out_.println(message);
}}
23.1.6 重启rocketmq-consumer后查看控制台
对象消息已经监听到了
23.2 发送集合消息
和对象消息同理,创建一个Order的集合,发送出去,监听方注意修改泛型中的类型为Object即可,这里就不做重复演示了
24. RocketMQ集成SpringBoot发送不同消息模式
24.1 发送同步消息
理解为:消息由消费者发送到broker后,会得到一个确认,是具有可靠性的
这种可靠性同步地发送方式使用的比较广泛,比如:重要的消息通知,短信通知等。
我们在上面的快速入门中演示的消息,就是同步消息,即
rocketMQTemplate.syncSend()
rocketMQTemplate.send()
rocketMQTemplate.convertAndSend()
这三种发送消息的方法,底层都是调用syncSend,发送的是同步消息
24.2 发送异步消息
rocketMQTemplate.asyncSend()
24.2.1 修改rocketmq-producer添加一个单元测试
/** * 测试异步发送消息 * * @throws Exception */
@Testpublic void testAsyncSend() throws Exception {
// 发送异步消息,发送完以后会有一个异步通知
rocketMQTemplate.**asyncSend**("powernode", "发送一个异步消息", new SendCallback() {
/** * 成功的回调 * @param sendResult */
@Override public void onSuccess(SendResult sendResult) {
System._out_.println("发送成功");
}
/** * 失败的回调 * @param throwable */
@Override public void onException(Throwable throwable) {
System._out_.println("发送失败");
} });
// 测试一下异步的效果
System._out_.println("谁先执行");
// 挂起jvm 不让方法结束
System._in_.read();
}
24.2.2 运行查看控制台效果
谁先发送打印在前面
24.3 发送单向消息
这种方式主要用在不关心发送结果的场景,这种方式吞吐量很大,但是存在消息丢失的风险,例如日志信息的发送
24.3.1 修改rocketmq-producer添加一个单元测试
/** * 测试单向消息 * * @throws Exception */
@Testpublic void testOnWay() throws Exception {
// 发送单向消息,没有返回值和结果 rocketMQTemplate.**sendOneWay**("powernode", "这是一个单向消息");
}
24.4 发送延迟消息
24.4.1 修改rocketmq-producer添加一个单元测试
/** * 测试延迟消息 * * @throws Exception */
@Testpublic void testDelay() throws Exception {
// 构建消息对象
Message<String> message = MessageBuilder._withPayload_("我是一个延迟消息").build();
// 发送一个延时消息,延迟等级为4级,也就是30s后被监听消费
SendResult sendResult = rocketMQTemplate.syncSend("powernode", message, 2000, 4);
System._out_.println(sendResult.getSendStatus());
}
24.4.2 运行后,查看消费者端,过了30s才被消费
这里注意的是RocketMQ不支持任意时间的延时
只支持以下几个固定的延时等级,等级1就对应1s,以此类推,最高支持2h延迟
private String messageDelayLevel = “1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;
24.5 发送顺序消息
24.5.1 修改Order表添加一个顺序字段
/** * 订单的流程顺序 */private Integer seq; |
---|
24.5.2 修改rocketmq-producer添加一个单元测试
/** * 测试顺序消费 * mq会根据hash的值来存放到一个队列里面去 * * @throws Exception */
@Testpublic void testOrderly() throws Exception {
List<Order> orders = Arrays._asList_(
new Order(UUID._randomUUID_().toString().substring(0, 5), "张三的下订单", null, null, null, 1),
new Order(UUID._randomUUID_().toString().substring(0, 5), "张三的发短信", null, null, null, 1),
new Order(UUID._randomUUID_().toString().substring(0, 5), "张三的物流", null, null, null, 1),
new Order(UUID._randomUUID_().toString().substring(0, 5), "张三的签收", null, null, null, 1),
new Order(UUID._randomUUID_().toString().substring(0, 5), "李四的下订单", null, null, null, 2),
new Order(UUID._randomUUID_().toString().substring(0, 5), "李四的发短信", null, null, null, 2),
new Order(UUID._randomUUID_().toString().substring(0, 5), "李四的物流", null, null, null, 2),
new Order(UUID._randomUUID_().toString().substring(0, 5), "李四的签收", null, null, null, 2) );
// 我们控制流程为 下订单->发短信->物流->签收 hash的值为seq,也就是说 seq相同的会放在同一个队列里面,顺序消费
orders.forEach(order -> {
rocketMQTemplate.syncSendOrderly("powernode-obj", order, String._valueOf_(order.getSeq()));
});
}
24.5.3 发送消息
24.5.4 修改rocketmq-consumer的ObjMsgListener
package com.powernode.listener; import com.powernode.domain.Order;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/** * @Author 武汉动力节点 * 创建一个对象消息的监听 *
1.类上添加注解@Component和@RocketMQMessageListener *
2.实现RocketMQListener接口,注意泛型的使用 * consumeMode 指定消费类型 * CONCURRENTLY 并发消费 * ORDERLY 顺序消费 messages orderly. one queue, one thread */
@Component@RocketMQMessageListener(topic = "powernode-obj",
consumerGroup = "powernode-obj-group",
**consumeMode = ConsumeMode._ORDERLY_**)public class ObjMsgListener implements RocketMQListener<Order> {
/** * 消费消息的方法 * * @param message */
@Override public void onMessage(Order message) {
System._out_.println(message);
}}
24.5.5 重启rocketmq-consumer
查看控制台,消息按照我们的放入顺序进行消费了
24.6 发送事务消息
24.6.1 修改rocketmq-producer添加一个单元测试
/** * 测试事务消息 * 默认是sync(同步的) * 事务消息会有确认和回查机制 * 事务消息都会走到同一个监听回调里面,所以我们需要使用tag或者key来区分过滤 * * @throws Exception */
@Testpublic void testTrans() throws Exception {
// 构建消息体
Message<String> message = MessageBuilder._withPayload_("这是一个事务消息").build();
// 发送事务消息(同步的) 最后一个参数才是消息主题
TransactionSendResult transaction =
rocketMQTemplate.sendMessageInTransaction("powernode", message, "消息的参数");
// 拿到本地事务状态
System._out_.println(transaction.getLocalTransactionState());
// 挂起jvm,因为事务的回查需要一些时间
System._in_.read();
}
24.6.2 修改rocketmq-producer添加一个本地事务消息的监听(半消息)
_/** * 事务消息的监听与回查 * 类上添加注解@RocketMQTransactionListener 表示这个类是本地事务消息的监听类 * 实现RocketMQLocalTransactionListener接口 * 两个方法为执行本地事务,与回查本地事务 */_
@Component@RocketMQTransactionListener(corePoolSize = 4,maximumPoolSize = 8)
public class TmMsgListener implements RocketMQLocalTransactionListener {
_/** _
_* 执行本地事务,这里可以执行一些业务 _
_* 比如操作数据库,操作成功就return RocketMQLocalTransactionState.COMMIT; _
_* 可以使用try catch来控制成功或者失败; _
_* @param msg * @param arg * @return */ _
@Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
_// 拿到消息参数 _
System._out_.println(arg);
_// 拿到消息头 _
System._out_.println(msg.getHeaders());
_// 返回状态COMMIT,UNKNOWN _
return RocketMQLocalTransactionState._UNKNOWN_;
}
_/** _
_* 回查本地事务,只有上面的执行方法返回UNKNOWN时,才执行下面的方法 默认是1min回查 _
_* 此方法为回查方法,执行需要等待一会 _
_* xxx.isSuccess() 这里可以执行一些检查的方法 _
_* 如果返回COMMIT,那么本地事务就算是提交成功了,消息就会被消费者看到 *_
_ * @param msg * @return */ _
@Override public RocketMQLocalTransactionState
checkLocalTransaction(Message msg) {
System._out_.println(msg);
return RocketMQLocalTransactionState._COMMIT_;
}}
24.6.3 测试发送事务,建议断点启动
- 消息会先到事务监听类的执行方法,
- 如果返回状态为COMMIT,则消费者可以直接监听到
- 如果返回状态为ROLLBACK,则消息发送失败,直接回滚
- 如果返回状态为UNKNOW,则过一会会走回查方法
- 如果回查方法返回状态为UNKNOW或者ROLLBACK,则消息发送失败,直接回滚
- 如果回查方法返回状态为COMMIT,则消费者可以直接监听到
25. RocketMQ集成SpringBoot的消息过滤
25.1 tag过滤(常在消费者端过滤)
我们从源码注释得知,tag带在主题后面用:来携带,感谢注释
我们往下去看源码,在
org.apache.rocketmq.spring.support.RocketMQUtil 的getAndWrapMessage方法里面看到了具体细节,我们也知道了keys在消息头里面携带
25.1.1 修改rocketmq-producer添加一个单元测试
/** * 发送一个带tag的消息 * * @throws Exception */
@Testpublic void testTagMsg() throws Exception {
// 发送一个tag为java的数据
rocketMQTemplate.syncSend(“powernode-tag:java”, “我是一个带tag的消息”);
}
25.1.2 发送消息
25.1.3 修改rocketmq-consumer添加一个TagMsgListener
package com.powernode.listener;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.annotation.SelectorType;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/** * @Author 武汉动力节点 * 创建一个简单的标签消息的监听
* 1.类上添加注解@Component和@RocketMQMessageListener * selectorType = SelectorType.TAG, 指定使用tag过滤。(也可以使用sql92 需要在配置文件broker.conf中开启enbalePropertyFilter=true) * selectorExpression = "java" 表达式,默认是*,支持"tag1 || tag2 || tag3"
* 2.实现RocketMQListener接口,注意泛型的使用 */
@Component@RocketMQMessageListener(topic = "powernode-tag",
consumerGroup = "powernode-tag-group",
selectorType = SelectorType._TAG_,
selectorExpression = "java")
public class TagMsgListener implements RocketMQListener<String> {
/** * 消费消息的方法 *
* @param message */
@Override public void onMessage(String message) {
System._out_.println(message);
}}
25.1.4 重启rocketmq-consumer查看控制台
25.2 Key过滤(可以在事务监听的类里面区分)
25.2.1 修改rocketmq-producer添加一个单元测试
/** * 发送一个带key的消息,我们使用事务消息 打断点查看消息头 * * @throws Exception */
@Testpublic void testKeyMsg() throws Exception {
// 发送一个key为spring的事务消息
Message<String> message = MessageBuilder._withPayload_("我是一个带key的消息") **.setHeader(RocketMQHeaders._KEYS_, "spring")**
.build();
rocketMQTemplate.sendMessageInTransaction("powernode", message, "我是一个带key的消息");
}
25.2.2 断点发送这个消息,查看事务里面消息头
我们在mq的控制台也可以看到
26. RocketMQ集成SpringBoot消息消费两种模式
Rocketmq消息消费的模式分为两种:负载均衡模式和广播模式
负载均衡模式表示多个消费者交替消费同一个主题里面的消息
广播模式表示每个每个消费者都消费一遍订阅的主题的消息
26.1 再搭建一个消费者rocketmq-consumer-b,依赖和配置文件和rocketmq-consumer一致,记住端口修改一下,避免占用
26.2 rocketmq-consumer-b添加一个监听
package com.powernode.listener;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/** * messageModel 指定消息消费的模式 * CLUSTERING 为负载均衡模式 * BROADCASTING 为广播模式 */
@Component@RocketMQMessageListener(topic = "powernode",
consumerGroup = "powernode-group",** **
**messageModel = MessageModel._CLUSTERING_**)
public class ConsumerBListener
implements RocketMQListener<String> {
@Override public void onMessage(String message) {
System._out_.println(message);
}}
26.3 修改rocketmq-consumer的SimpleMsgListener
_/** * 创建一个简单消息的监听 * 1.类上添加注解@Component和@RocketMQMessageListener * * @RocketMQMessageListener(topic = "powernode", consumerGroup = "powernode-group") * topic指定消费的主题,consumerGroup指定消费组,一个主题可以有多个消费者组,一个消息可以被多个不同的组的消费者都消费 * 2.实现RocketMQListener接口,注意泛型的使用 */_
@Component@RocketMQMessageListener(topic = "powernode",
consumerGroup = "powernode-group", ** **
**messageModel = MessageModel._CLUSTERING_**)
public class SimpleMsgListener
implements RocketMQListener<String> {
@Override public void onMessage(String message) {
System._out_.println(new Date());
System._out_.println(message);
}}
26.4 启动两个消费者
26.5 在生产者里面添加一个单元测试并且运行
| /** * 测试消息消费的模式 * * @throws Exception */
@Testpublic void testMsgModel() throws Exception {
for (int i = 0; i < 10; i++) {
rocketMQTemplate.syncSend(“powernode”, “我是消息” + i);
}} |
---|
26.6 查看两个消费者的控制台,发现是负载均衡的模式
26.7 修改两个消费者的模式为BROADCASTING
重启测试,结果是广播模式,每个消费者都消费了这些消息
项目中 一般部署多态机器 消费者 2 - 3 根据业务可以选择具体的模式来配置
重置消费点位, 将一个组的消费节点 设置在之前的某一个时间点上去 从这个时间点开始往后消费
跳过堆积 选择一个组 跳过堆积以后 这个组里面的的所有都不会被消费了
27. 如何解决消息堆积问题?
一般认为单条队列消息差值>=10w时 算堆积问题
27.1 什么情况下会出现堆积
- 生产太快了
生产方可以做业务限流
增加消费者数量,但是消费者数量<=队列数量,适当的设置最大的消费线程数量(根据IO(2n)/CPU(n+1))
动态扩容队列数量,从而增加消费者数量 - 消费者消费出现问题
排查消费者程序的问题
28. 如何确保消息不丢失?
- 生产者使用同步发送模式 ,收到mq的返回确认以后 顺便往自己的数据库里面写
msgId status(0) time - 消费者消费以后 修改数据这条消息的状态 = 1
- 写一个定时任务 间隔两天去查询数据 如果有status = 0 and time < day-2
- 将mq的刷盘机制设置为同步刷盘
- 使用集群模式 ,搞主备模式,将消息持久化在不同的硬件上
- 可以开启mq的trace机制,消息跟踪机制
1.在broker.conf中开启消息追踪
traceTopicEnable=true
2.重启broker即可
3.生产者配置文件开启消息轨迹
enable-msg-trace: true
4. 消费者开启消息轨迹功能,可以给单独的某一个消费者开启
enableMsgTrace = true
在rocketmq的面板中可以查看消息轨迹
默认会将消息轨迹的数据存在 RMQ_SYS_TRACE_TOPIC 主题里面
29. 安全
- 开启acl的控制 在broker.conf中开启aclEnable=true
- 配置账号密码 修改plain_acl.yml
- 修改控制面板的配置文件 放开52/53行 把49行改为true 上传到服务器的jar包平级目录下即可