点击上方“芋道源码”,选择“设为星标”
做积极的人,而不是积极废人!
源码精品专栏
摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/RocketMQ/ 「芋道源码」欢迎转载,保留摘要,谢谢!
1. 概述
2. RocketMQ-Spring
3. 快速入门
4. 批量发送消息
5. 定时消息
6. 消费重试
7. 广播消费
8. 顺序消息
9. 事务消息
10. 接入阿里云的消息队列 RocketMQ
666. 彩蛋
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-31 目录。
原创不易,给点个 Star 嘿,一起冲鸭!
1. 概述
如果胖友还没了解过分布式消息队列 Apache RocketMQ ,建议先阅读下艿艿写的 《芋道 RocketMQ 极简入门》 文章。虽然这篇文章标题是安装部署,实际可以理解成《一文带你快速入门 RocketMQ》,哈哈哈。
考虑这是 RocketMQ 如何在 Spring Boot 整合与使用的文章,所以还是简单介绍下 RocketMQ 是什么?
FROM 《消息中间件 Apache RocketMQ》
RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。同时,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。
具有以下特点:
能够保证严格的消息顺序
提供丰富的消息拉取模式
高效的订阅者水平扩展能力
实时的消息订阅机制
亿级消息堆积能力
ps: Metaq 3.0 版本改名,产品名称改为 RocketMQ
在本文中,我们会比 《芋道 RocketMQ 极简入门》 提供更多的生产者 Producer 和消费者 Consumer 的使用示例。例如说:
Producer 三种发送消息的方式。
Producer 发送顺序消息,Consumer 顺序消费消息。
Producer 发送定时消息。
Producer 批量发送消息。
Producer 发送事务消息。
Consumer 广播和集群消费消息。
胖友你就说,艿艿是不是很良心。????
2. RocketMQ-Spring
RocketMQ-Spring 项目,RocketMQ 对 Spring 的集成支持。主要有两方面的功能:
功能一:支持 Spring Message 规范,方便开发者从其它 MQ 快速切换到 RocketMQ 。
功能二:帮助开发者在 Spring Boot 中快速集成 RocketMQ 。
我们先一起了解下功能一。对于大多数国内的开发者,相信对 Spring Message 是比较陌生的,包括艿艿自己。所幸艿艿是一个专业的收藏家,无意中看到有篇文章介绍了 RocketMQ-Spring 在这块的设计上的想法:
FROM 《我用这种方法在 Spring 中实现消息的发送和消息》
Spring Messaging 是 Spring Framework 4 中添加的模块,是Spring 与消息系统集成的一个扩展性的支持。它实现了从基于 JmsTemplate 的简单的使用 JMS 接口到异步接收消息的一整套完整的基础架构,Spring AMQP 提供了该协议所要求的类似的功能集。在与 Spring Boot 的集成后,它拥有了自动配置能力,能够在测试和运行时与相应的消息传递系统进行集成。
单纯对于客户端而言,Spring Messaging 提供了一套抽象的 API 或者说是约定的标准,对消息发送端和消息接收端的模式进行规定,不同的消息中间件提供商可以在这个模式下提供自己的 Spring 实现:
在消息发送端,需要实现的是一个 XXXTemplate 形式的 Java Bean ,结合 Spring Boot 的自动化配置选项提供多个不同的发送消息方法;
在消息的消费端,是一个 XXXMessageListener 接口(实现方式通常会使用一个注解来声明一个消息驱动的 POJO ),提供回调方法来监听和消费消息,这个接口同样可以使用 Spring Boot 的自动化选项和一些定制化的属性。
如果有兴趣深入的了解 Spring Messaging 及针对不同的消息产品的使用,推荐阅读这个文件。参考 Spring Messaging 的既有实现, RocketMQ 的 spring-boot-starter 中遵循了相关的设计模式,并结合 RocketMQ 自身的功能特点提供了相应的 API(如,顺序,异步和事务半消息等)。
这样一撸,是不是清晰多了。简单来说,RocketMQ-Spring 就是基于 Spring Message 来实现 RocketMQ 的发送端和接收端。
我们再一起了解下功能二。比较好理解,就是提供了 RocketMQ 的 spring-boot-starter 功能,实现 RocketMQ 的自动化配置。
不过,这里艿艿还是想弱弱吐槽一句,RocketMQ 的官方 spring-boot-starter 真的有点出的太晚了。如下是整理的时间轴:
2014-08 Spring Boot 1 正式发布。
2018-03 Spring Boot 2 正式发布。
2018-12 RocketMQ 团队发布 RocketMQ 集成到 Spring Boot 的解决方案,并且提供了中文文档。
3. 快速入门
示例代码对应仓库:lab-31-rocketmq-demo 。
本小节,我们先来对 RocketMQ-Spring 做一个快速入门,实现 Producer 三种发送消息的方式的功能,同时创建一个 Consumer 消费消息。
考虑到一个应用既可以使用生产者 Producer ,又可以使用消费者 Consumer ,所以示例就做成一个 lab-31-rocketmq-demo 项目。
3.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-31-rocketmq-demo</artifactId>
<dependencies>
<!-- 实现对 RocketMQ 的自动化配置 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
具体每个依赖的作用,胖友自己认真看下艿艿添加的所有注释噢。
3.2 应用配置文件
在 resources
目录下,创建 application.yaml
配置文件。配置如下:
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv
# Producer 配置项
producer:
group: demo-producer-group # 生产者分组
send-message-timeout: 3000 # 发送消息超时时间,单位:毫秒。默认为 3000 。
compress-message-body-threshold: 4096 # 消息压缩阀值,当消息体的大小超过该阀值后,进行消息压缩。默认为 4 * 1024B
max-message-size: 4194304 # 消息体的最大允许大小。。默认为 4 * 1024 * 1024B
retry-times-when-send-failed: 2 # 同步发送消息时,失败重试次数。默认为 2 次。
retry-times-when-send-async-failed: 2 # 异步发送消息时,失败重试次数。默认为 2 次。
retry-next-server: false # 发送消息给 Broker 时,如果发送失败,是否重试另外一台 Broker 。默认为 false
access-key: # Access Key ,可阅读 https://github.com/apache/rocketmq/blob/master/docs/cn/acl/user_guide.md 文档
secret-key: # Secret Key
enable-msg-trace: true # 是否开启消息轨迹功能。默认为 true 开启。可阅读 https://github.com/apache/rocketmq/blob/master/docs/cn/msg_trace/user_guide.md 文档
customized-trace-topic: RMQ_SYS_TRACE_TOPIC # 自定义消息轨迹的 Topic 。默认为 RMQ_SYS_TRACE_TOPIC 。
# Consumer 配置项
consumer:
listeners: # 配置某个消费分组,是否监听指定 Topic 。结构为 Map<消费者分组, <Topic, Boolean>> 。默认情况下,不配置表示监听。
test-consumer-group:
topic1: false # 关闭 test-consumer-group 对 topic1 的监听消费
在
rocketmq
配置项,设置 RocketMQ 的配置,对应 RocketMQProperties 配置类。RocketMQ-Spring RocketMQAutoConfiguration 自动化配置类,实现 RocketMQ 的自动配置,创建相应的 Producer 和 Consumer 。
rocketmq.name-server
配置项,设置 RocketMQ Namesrv 地址。如果多个,使用逗号分隔。rocketmq.producer
配置项,一看就知道是 RocketMQ Producer 所独有。-
group
配置,生产者分组。retry-next-server
配置,发送消息给 Broker 时,如果发送失败,是否重试另外一台 Broker 。默认为false
。如果胖友使用多主 Broker 的情况下,需要设置true
,这样才会在发送消息失败时,重试另外一台 Broker 。其它配置,一般默认即可。
rocketmq.consumer
配置项,一看就知道是 RocketMQ Consumer 所独有。-
listener
配置,配置某个消费分组,是否监听指定 Topic 。结构为Map<消费者分组, <Topic, Boolean>>
。默认情况下,不配置表示监听。一般情况下,只有我们在想不监听消费某个消费分组的某个 Topic 时,才需要配listener
配置。
3.3 空
不存在 3.3 ,目录标记错了。咳咳咳
3.4 Application
创建 Application.java
类,配置 @SpringBootApplication
注解即可。代码如下:
// Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.5 Demo01Message
在 cn.iocoder.springboot.lab31.rocketmqdemo.message
包下,创建 Demo01Message 消息类,提供给当前示例使用。代码如下:
// Demo01Message.java
public class Demo01Message {
public static final String TOPIC = "DEMO_01";
/**
* 编号
*/
private Integer id;
// ... 省略 set/get/toString 方法
}
TOPIC
静态属性,我们设置该消息类对应 Topic 为"DEMO_01"
。
3.6 Demo01Producer
在 cn.iocoder.springboot.lab31.rocketmqdemo.producer
包下,创建 Demo01Producer 类,它会使用 RocketMQ-Spring 封装提供的 RocketMQTemplate ,实现三种发送消息的方式。代码如下:
// Demo01Producer.java
@Component
public class Demo01Producer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public SendResult syncSend(Integer id) {
// 创建 Demo01Message 消息
Demo01Message message = new Demo01Message();
message.setId(id);
// 同步发送消息
return rocketMQTemplate.syncSend(Demo01Message.TOPIC, message);
}
public void asyncSend(Integer id, SendCallback callback) {
// 创建 Demo01Message 消息
Demo01Message message = new Demo01Message();
message.setId(id);
// 异步发送消息
rocketMQTemplate.asyncSend(Demo01Message.TOPIC, message, callback);
}
public void onewaySend(Integer id) {
// 创建 Demo01Message 消息
Demo01Message message = new Demo01Message();
message.setId(id);
// oneway 发送消息
rocketMQTemplate.sendOneWay(Demo01Message.TOPIC, message);
}
}
三个方法,对应三个 RocketMQ 发送消息的方式,分别调用 RocketMQTemplate 提供的
#syncSend(...)
和#asyncSend(...)
以及#sendOneWay(...)
方法。
我们来简单聊下 RocketMQTemplate 类,它继承 Spring Messaging 定义的 AbstractMessageSendingTemplate 抽象类,以达到融入 Spring Messaging 体系中。
在 RocketMQTemplate 中,会创建一个 RocketMQ DefaultMQProducer 生产者 producer
,所以 RocketMQTemplate 后续的各种发送消息的方法,都是使用它。???? 当然,因为 RocketMQTemplate 的封装,所以我们可以像使用 Spring Messaging 一样的方式,进行消息的发送,而无需直接使用 RocketMQ 提供的 Producer 发送消息。
对于胖友来说,可能最关心的是,消息 Message 是怎么序列化的。我们来看看 RocketMQUtil#convertToRocketMessage(...) 方法的代码:
// RocketMQTemplate.java
public SendResult syncSend(String destination, Object payload, long timeout) {
Message<?> message = MessageBuilder.withPayload(payload).build(); // <X>
// ... 省略其它代码
}
// RocketMQUti.java
public static org.apache.rocketmq.common.message.Message convertToRocketMessage(
MessageConverter messageConverter, String charset,
String destination, org.springframework.messaging.Message<?> message) {
Object payloadObj = message.getPayload();
byte[] payloads;
try {
if (null == payloadObj) {
throw new RuntimeException("the message cannot be empty");
}
// 如果是 String 类型,则直接获得其 byte[] 内容。
if (payloadObj instanceof String) {
payloads = ((String)payloadObj).getBytes(Charset.forName(charset));
// 如果是 byte[] 类型,则直接使用即可
} else if (payloadObj instanceof byte[]) {
payloads = (byte[])message.getPayload();
// 如果是复杂对象类型,则使用 MessageConverter 进行转换成字符串,然后再获得字符串的 byte[] 内容。
} else {
String jsonObj = (String)messageConverter.fromMessage(message, payloadObj.getClass());
if (null == jsonObj) {
throw new RuntimeException(String.format(
"empty after conversion [messageConverter:%s,payloadClass:%s,payloadObj:%s]",
messageConverter.getClass(), payloadObj.getClass(), payloadObj));
}
payloads = jsonObj.getBytes(Charset.forName(charset));
}
} catch (Exception e) {
throw new RuntimeException("convert to RocketMQ message failed.", e);
}
// 转换成 RocketMQ Message
return getAndWrapMessage(destination, message.getHeaders(), payloads);
}
在
<X>
处,RocketMQTemplate 会通过 Spring Messaging 的 MessageBuilder 将我们传入的消息payload
转换成 Spring Messaging 的 Message 消息对象。RocketMQUtil#convertToRocketMessage(...)
的代码,胖友自己看下艿艿添加的注释,进行下理解。因为我们一般消息都是复杂对象类型,所以会采用 MessageConverter 进行转换。RocketMQ-Spring 的默认使用 MappingJackson2MessageConverter 或 MappingFastJsonMessageConverter ,即使用 JSON 格式序列化和反序列化 Message 消息内容。为什么是这两个 MessageConverter ,胖友可以自己看看 RocketMQ-Spring 的 MessageConverterConfiguration 配置类。
3.7 空
不存在 3.7 ,目录标记错了。咳咳咳
3.8 Demo01Consumer
在 cn.iocoder.springboot.lab31.rocketmqdemo.consumer
包下,创建 Demo01Consumer 类,实现 Rocket-Spring 定义的 RocketMQListener 接口,消费消息。代码如下:
// Demo01Consumer.java
@Component
@RocketMQMessageListener(
topic = Demo01Message.TOPIC,
consumerGroup = "demo01-consumer-group-" + Demo01Message.TOPIC
)
public class Demo01Consumer implements RocketMQListener<Demo01Message> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onMessage(Demo01Message message) {
logger.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
}
}
在类上,添加了
@RocketMQMessageListener
注解,声明消费的 Topic 是"DEMO_01"
,消费者分组是"demo01-consumer-group-DEMO_01"
。一般情况下,我们建议一个消费者分组,仅消费一个 Topic 。这样做会有