目录
1.4、SpringCloud Stream 编码API及常用注解
1、概述
1.1、基本概念
- SpringCloud Stream 是一个构建消息驱动微服务的框架
- 应用程序通过 outputs 或 inputs 来与 SpringCloud Stream 中的 binder 对象交互
- SpringCloud Stream 中的 binder 对象负责与消息中间件交互
- 通过 SpringCloud Stream 连接消息中间件,以实现消息事件驱动
1.2、SpringCloud Stream设计思想
1.2.1、标准MQ
- 生产者/消费者之间靠消息媒介传递信息内容:Message
- 消息必须走特定的通道:MessageChannel
- 消息通道中消息的收发处理:由MessageHandler消息处理器所订阅
1.2.2、SpringCloud Stream设计思想
1.消息中间件与系统耦合度较高,当换用不同的消息中间件时,需要实现大量的代码更新工作
2.SpringCloud Stream 实现了消息中间件的解耦
- 通过定义绑定层作为消息中间层,实现了应用程序与消息中间件细节之间的隔离
- 通过向应用程序暴露统一的channel通道,使得应用程序不需要考虑不同的消息中间件的实现
3.Binder 绑定器
1.3、SpringCloud Stream 的标准流程
-
Binder:很方便的连接中间件,屏蔽差异
-
Channel:通道,是队列Queue的一种抽象,在消息通讯系统中实现存储和转发的媒介
-
Source和Sink:从Stream发布消息就是输出,接受消息就是输入
1.4、SpringCloud Stream 编码API及常用注解
2、案例(基于RabbitMQ消息中间件)
2.1、消息生产者
2.1.1、POM文件
添加 spring-cloud-starter-stream-rabbit 依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.uclass.springcloud</groupId>
<artifactId>Api-Commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
2.1.2、yaml配置文件
绑定器(binders)及消息通道(bindings)设置
消息通道设置为 output
server:
port: 8801
spring:
application:
name: cloud-stream-privider
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: {defaultRabbit} #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: send-8801.com
prefer-ip-address: true #访问的路径变为IP地址
2.1.3、主启动类
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
2.1.4、业务类
- 未使用 @Service 注解:此处并非调用 dao 层,实现crud操作
- 使用 @EnableBinding(Source.class) 注解:此处定义与绑定器的交互逻辑
- Source.class 表示是消息的推送方
//此处的业务逻辑是与绑定器进行信息交互,而不是之前的crud
@EnableBinding(Source.class) //定义消息的推送管道(消息源)
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("serial:" + serial);
return null;
}
}
2.1.5、Controller
调用 Service,实现消息的推送
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}
2.2、消息消费者
2.2.1、POM文件与2.1.1一致
2.2.2、yaml配置文件
绑定器(binders)及消息通道(bindings)设置
消息通道设置为 input
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: {defaultRabbit} #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: recive-8803.com
prefer-ip-address: true #访问的路径变为IP地址
2.2.3、主启动类与2.1.3一致
2.2.4、controller(无业务类)
- 使用 @EnableBinding(Sink.class) 注解:此处定义与绑定器的交互逻辑
- Sink.class 表示是消息的接收方
@RestController
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者2号" + message.getPayload() + "\t port:" + serverPort);
}
}
2.3、 分组消费与持久化
2.3.1、问题
1)依照8803,clone出来一份消息消费者:8804
2)存在问题:
- 消息重复消费:即8803和8804均接收了消息发送方的消息
- 消息持久化:当微服务宕机,已发送的消息的处理问题
2.3.2、解决方法(消息消费者分组)
1)分组原理:
- 同一个组中的多个微服务,消息只会被其中一个消费一次
- 不同组中的微服务,均可以实现对同一个消息的消费
2)分组操作:修改yaml配置文件
在上述yaml配置文件的 bindings 属性中添加 group 属性,实现分组
3)测试:
1、重复消费测试:
8803/8804实现了轮询分组,每次只有一个消费者。
8801模块的发的消息,只能被8803或者8804其中一个接受到
2、持久化测试:
停止8803/8804;除掉8803的分组group:UCLASSA;8004的分组属性保留
8801先发送4条消息到rabbitmq
先启动8803,无分组属性配置,后台没有打出来消息
再启动8804,有分组属性配置,后台打出来了MQ上的消息