案例源码gitee地址:
Spring Cloud Alibaba RocketMQ
一、RocketMQ 介绍
RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。同时,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。
具有以下特点:
- 能够保证严格的消息顺序
- 提供丰富的消息拉取模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力
二、RocketMQ 基本使用
2.1 下载 RocketMQ
使用浏览器打开:
http://rocketmq.apache.org/release_notes/release-notes-4.4.0/
这里我们选择 4.4.0 版本的原因在于,我们 spring cloud alibaba 版本为:2.2.0.RELEASE,
它里面控制的 rocketMQ 的版是 4.4.0。
直接下载的速度太慢,大家可以从百度云网盘下载该文件。
链接:https://pan.baidu.com/s/1o4HAuRQIiEQzMEJCD9YX0Q 提取码:18x3
2.2 RocketMQ 目录分析
将该压缩包复制到软件目录里面,使用压缩软件进行解压。
Benchmark:包含一些性能测试的脚本;
Bin:可执行文件目录;
Conf:配置文件目录;
Lib:第三方依赖;
LICENSE:授权信息;
NOTICE:版本公告;
2.3 配置环境变量
找到配置环境变量的对话框:
点击新建创建一个环境变量:
- 变量名:ROCKETMQ_HOME
- 变量值:D:\devtools\rocketMQ\rocketmq-all-4.4.0-bin-release
2.4 RocketMQ 的启动
我们进入到${rocketMQ}/bin,在此目录里面启动和停止命令。
2.4.1 启动 NameServer
注意:弹出的黑窗口不要关闭。
2.4.2 启动 Broker
./mqbroker.cmd -nlocalhost:9876
其中:
-n localhost:9876 是为了指定nameserver 的地址
2.5 RocketMQ 的停止
直接把弹出的黑框关闭,即可停止 RocketMQ 的 namesrv 和 broker。
2.6 RocketMQ 控制台的安装
Rocketmq 控制台可以可视化 MQ的消息发送!
2.6.1 下载 RocketMQ 控制台
直接从官网下载的是源码,比较麻烦,我提供了可以运行的 jar,大家可以直接下载
链接:https://pan.baidu.com/s/1BWokojTSl6n8nxHL0mvTww 提取码:nhnj
2.6.2 复制到软件目录里面
2.6.3 运行该 jar
java -jar rocketmq-console-ng-1.0.0.jar --rocketmq.config.namesrvAddr=127.0.0.1:9876
其中:
--rocketmq.config.namesrvAddr=127.0.0.1:9876 是为了指定 nameserver 的地址
运行成功后:
访问:
http://localhost:8080/#/
三、Spring Cloud Stream 介绍
Spring Cloud Stream是一个用于构建基于消息的微服务应用框架。它基于 SpringBoot来创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration 与 Broker 进行连接。
Spring Cloud Stream 提供了消息中间件配置的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。
Spring Cloud Stream 内部有两个概念:Binder 和Binding:
-
Binder: 跟外部消息中间件集成的组件,用来创建 Binding,各消息中间件都有自己的Binder 实现。
举例说明:
Kafka 的实现 KafkaMessageChannelBinder,RabbitMQ 的实现 RabbitMessageChannelBinder以及 RocketMQ 的实现 RocketMQMessageChannelBinder。
-
Binding: 包括 Input Binding 和 Output Binding。
Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。
四、测试框架搭建
我们将创建 spring-cloud-bus-rocketmq-example 项目,用来测试它的所有功能。
4.1 搭建 spring-cloud-bus-rocketmq-example
spring-cloud-bus-rocketmq-example 将去除子模块的公共依赖部分。
4.1.1 使用 IDEA 创建一个 Maven 项目
选择 Maven 项目:
点击 Next ,填写以下的内容:
Parent:我们选择 spring-cloud-alibaba-examples
Name:spring-cloud-bus-rocketmq-example
其他的项保持不变。
点击 Finish 完成创建。
4.1.2 添加依赖
打开项目的 pom.xml 文件,我们添加以下的内容:
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-bus-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
4.1.3 完整的 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>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-bus-rocketmq-example</artifactId>
<packaging>pom</packaging>
<modules>
<module>rocketmq-produce-example</module>
</modules>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-bus-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 搭建 rocketmq-produce-example
produce 代表服务的生产者,用来发送消息。
4.2.1 使用 IDEA 创建一个 Maven 项目
选择 Maven:
点击 Next 添加以下的内容:
Parent:spring-cloud-bus-rocketmq-example
Name:rocketmq-produce-example
点击 Finish 完成项目的创建
4.2.2 修改 Maven 的打包方式
此项目我们以后可能需要使用 jar 发布,在此,我们添加 spring-boot 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
4.2.3 完整的 pom.xml 文件如下
<?xmlversion="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>
<artifactId>spring-cloud-bus-rocketmq-example</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rocketmq-produce-example</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.3 搭建 rocketmq-produce-example
4.3.1 使用 IDEA 创建一个 Maven 项目
选择 Maven:
点击 Next 添加以下的内容:
Parent:spring-cloud-bus-rocketmq-example
Name:rocketmq-consumer-example
点击 Finish 完成项目的创建
4.3.2 修改 Maven 的打包方式
为了以后打包为一个 jar 发布,我们添加一个打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
4.3.3 完整的 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>
<artifactId>spring-cloud-bus-rocketmq-example</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rocketmq-consume-example</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.4 项目的完整结构如下
五、完善 rocketmq-produce-example 项目
5.1 添加一个配置文件
配置信息如下:
logging.level.com.alibaba.cloud.stream.binder.rocketmq=DEBUG
spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876
spring.cloud.stream.bindings.output1.destination=test-topic
spring.cloud.stream.bindings.output1.content-type=application/json
spring.cloud.stream.rocketmq.bindings.output1.producer.group=binder-group
spring.cloud.stream.rocketmq.bindings.output1.producer.sync=true
spring.cloud.stream.bindings.output2.destination=TransactionTopic
spring.cloud.stream.bindings.output2.content-type=application/json
spring.cloud.stream.rocketmq.bindings.output2.producer.transactional=true
spring.cloud.stream.rocketmq.bindings.output2.producer.group=myTxProducerGroup
spring.cloud.stream.bindings.output3.destination=pull-topic
spring.cloud.stream.bindings.output3.content-type=text/plain
spring.cloud.stream.rocketmq.bindings.output3.producer.group=pull-binder-group
spring.application.name=rocketmq-produce-example
server.port=28081
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
5.2 添加一个启动类
@SpringBootApplication
public classRocketMQProduceApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQProduceApplication.class, args);
}
}
5.3 添加 MQSource
在 Source 里面定义输出:
public interface MQSource{
@Output("output1")
MessageChannel output1() ;
@Output("output2")
MessageChannel output2() ;
@Output("output1")
MessageChannel output3() ;
}
5.4 添加配置类
代码如下:
@Configuration
@EnableBinding({MQSource.class})
public class MQConfig {
}
5.5 添加发送消息的类
@Service
public class SendService {
@Autowired
private MQSource source;
/**
* 发送简单的测试消息
*@param msg
*@throws Exception
*/
public void send(String msg) throws Exception {
source.output1().send(MessageBuilder.withPayload(msg).build());
}
/**
* 发消息时添加标签
*@param msg
*@param tag
*@param <T>
*@throws Exception
*/
public <T> voidsendWithTags(T msg, String tag) throws Exception {
Messagemessage = MessageBuilder.createMessage(msg,
newMessageHeaders(Stream.of(tag).collect(Collectors
.toMap(str -> MessageConst.PROPERTY_TAGS,
String::toString))));
source.output1().send(message);
}
/**
* 发送一个对象消息
*@param msg
*@param tag
*@param <T>
*@throws Exception
*/
public <T> voidsendObject(T msg, String tag) throws Exception {
Messagemessage = MessageBuilder.withPayload(msg)
.setHeader(MessageConst.PROPERTY_TAGS, tag)
.setHeader(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.APPLICATION_JSON)
.build();
source.output1().send(message);
}
/**
* 发送事务的消息
*@param msg
*@param num
*@param <T>
*@throws Exception
*/
public <T> voidsendTransactionalMsg(T msg, int num) throws Exception {
MessageBuilder builder = MessageBuilder.withPayload(msg)
.setHeader(MessageHeaders.CONTENT_TYPE,
MimeTypeUtils.APPLICATION_JSON);
builder.setHeader("test", String.valueOf(num));
Messagemessage = builder.build();
source.output2().send(message);
}
public void sendMassiveMessage(String msg) {
source.output3().send(MessageBuilder.withPayload(msg).build());
}
}
5.6 事务消息往往需要我们监听回查
新建一个类:
代码如下:
/**
* TransactionStatus.CommitTransaction :消息提交,当消息状态为 CommitTransaction ,表示允许消费者允许消费当前消息
* TransactionStatus.RollbackTransaction :消息回滚,表示 MQ 服务端将会删除当前半消息,不允许消费者消费。
* TransactionStatus.Unknown :中间状态,表示 MQ 服务需要发起回查操作,检测当前发送方本地事务的执行状态。
*/
@RocketMQTransactionListener(txProducerGroup = "myTxProducerGroup",corePoolSize = 5,maximumPoolSize = 10)
public class TransactionListenerImplimplements RocketMQLocalTransactionListener {
/**
* 消息生产者需要在 executeLocalTransaction 中执行本地事务 , 当事务半消息提交成功,执行完毕后需要返回事务状态码。
* @param msg
* @param o
* @return
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object o)
{
Object num = msg.getHeaders().get("test");
if ("1".equals(num)) {
System.out.println("executer: " + new String((byte[]) msg.getPayload()) + " unknown");
return RocketMQLocalTransactionState.UNKNOWN; // 将会导致再次查询本地事务
}
else if ("2".equals(num)) {
System.out.println("executer: " + new String((byte[]) msg.getPayload()) + " rollback");
return RocketMQLocalTransactionState.ROLLBACK; // 半消息将会被 mq 服务器删除,并且消费者不会消费到该消息
}
System.out.println("executer: " + new String((byte[]) msg.getPayload()) + "commit");
return RocketMQLocalTransactionState.COMMIT; // 半消息提交,消费者会消费到该消息。
}
/**
* 实现 checkLocalTransaction 方法,该方法用于进行本地事务执行情况回查,并回应事务状态给MQ 的 broker,
* 执行完成之后需要返回对应的事务状态码
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionStatecheckLocalTransaction(Message message) {
System.out.println("check: " + new String((byte[]) message.getPayload()));
return RocketMQLocalTransactionState.COMMIT;
}
}
5.7 构建一个简单的模型
代码如下:
5.8 测试消息的发送
5.9 启动类
代码如下:
@SpringBootApplication
public class RocketMQProduceApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQProduceApplication.class, args);
}
}
六、完善 rocketmq-consumer-example 项目
6.1 添加配置文件
内容如下:
spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876
spring.cloud.stream.bindings.input1.destination=test-topic
spring.cloud.stream.bindings.input1.content-type=text/plain
spring.cloud.stream.bindings.input1.group=test-group1
spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true
spring.cloud.stream.bindings.input2.destination=test-topic
spring.cloud.stream.bindings.input2.content-type=text/plain
spring.cloud.stream.bindings.input2.group=test-group2
spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false
spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr
spring.cloud.stream.bindings.input2.consumer.concurrency=20
spring.cloud.stream.bindings.input2.consumer.maxAttempts=1
spring.cloud.stream.bindings.input3.destination=test-topic
spring.cloud.stream.bindings.input3.content-type=application/json
spring.cloud.stream.bindings.input3.group=test-group3
spring.cloud.stream.rocketmq.bindings.input3.consumer.tags=tagObj
spring.cloud.stream.bindings.input3.consumer.concurrency=20
spring.cloud.stream.bindings.input4.destination=TransactionTopic
spring.cloud.stream.bindings.input4.content-type=text/plain
spring.cloud.stream.bindings.input4.group=transaction-group
spring.cloud.stream.bindings.input4.consumer.concurrency=5
spring.cloud.stream.bindings.input5.destination=pull-topic
spring.cloud.stream.bindings.input5.content-type=text/plain
spring.cloud.stream.bindings.input5.group=pull-topic-group
spring.application.name=rocketmq-consume-example
server.port=28082
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
6.2 添加一个 Sink
在 Sink 里面添加输入:
public interface Sink {
@Input("input1")
SubscribableChannel input1();
@Input("input2")
SubscribableChannel input2();
@Input("input3")
SubscribableChannel input3();
@Input("input4")
SubscribableChannel input4();
@Input("input5")
PollableMessageSource input5();
}
6.3 创建消息的监听器
/**
* receive
*/
@Service
public class ReceiveService {
@StreamListener("input1")
public void receiveInput1(String receiveMsg) {
System.out.println("input1 receive: " + receiveMsg);
}
@StreamListener("input2")
public void receiveInput2(String receiveMsg) {
System.out.println("input2 receive: " + receiveMsg);
}
@StreamListener("input3")
public void receiveInput3(@Payload User user) {
System.out.println("input3 receive: " + user);
}
@StreamListener("input4")
public void receiveTransactionalMsg(String transactionMsg) {
System.out.println("input4 receive transaction msg: " + transactionMsg);
}
}
6.4 主动去 mq 服务器拉起消息
使用定时任务,主动去服务器拉取消息:
@Service
public class PullMessageTask {
@Autowired
private Sink sink ;
@Scheduled(fixedRate = 5*1000)
public void pullMessage(){
sink.input5().poll((message) -> {
String payload = (String) message.getPayload();
System.out.println("pull msg: " + payload);
}, new ParameterizedTypeReference<String>() {
});
}
}
6.5 模型类
直接从 produce 里面复制过来:
6.6 配置类
新建 MQConfig:
代码如下:
@Configuration
@EnableBinding({Sink.class})
public classMQConfig {
}
6.7 启动类
@SpringBootApplication
@EnableScheduling
public classRocketMQConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQConsumerApplication.class ,args) ;
}
}
七、测试案例测试
7.1 启动服务
启动 2 个服务:
- rocketmq-produce-example
- rocketmq-consumer-example
7.2 发送消息测试
7.2.1 发送简单的字符串
http://localhost:28081/send/simple?msg=RocketMQ
7.2.2 发送带标签的消息
7.2.3 发送对象消息
http://localhost:28081/send/object?id=1&userName=bjsxt&password=123456&tags=xxx
7.2.4 发送事务消息
http://localhost:28081/send/transaction?msg=order&num=1
http://localhost:28081/send/transaction?msg=order&num=2
http://localhost:28081/send/transaction?msg=order&num=3
7.2.5 手动拉取消息
http://localhost:28081/send/poll?msg=order
案例源码gitee地址:https://gitee.com/BanSheng/spring-cloud-alibaba-examples/tree/master/spring-cloud-bus-rocketmq-example