文章目录
一.概念
1.1 SpringCloudStream是什么?
1.2 SpringCloudStream作用?
屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型
1.3 SpringCloud Stream 标准流程
1.4 解析
Binder: 很方便的连接中间件,屏蔽差异;INPUT 对应于消费者,OUTPUT对应于生产者
Message:生产者/消费者之间靠消息媒介传递信息内容
消息通道MessageChannel:消息必须走特定的通道
消息通道MessageChannel的子接口SubscribableChannel:由MessageHandler消息处理器订阅
通道:是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置
Source和Sink:Stream自身,从Stream发布消息就是输出,接受消息就是输入
1.5 编码API和常用注解
二.发消息模块(生产者)
2.1 导入依赖
<!--rabbitMQ-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2.2 配置文件
server:
port: 8801
spring:
application:
name: cloud-stream-provider
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,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
2.3 启动类
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
2.4 发送消息接口及实现类
public interface IMessageProvider
{
public String send();
}
@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.5 服务调用接口
@RestController
public class SendMessageController
{
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage()
{
return messageProvider.send();
}
}
2.6 效果
启动rabbitMQ,和启动7001和8801
登录web管理界面,发现新增了一个Exchange
目前还没人订阅绑定
http://127.0.0.1:8801/sendMessage 连续发多次请求
rabbitmq检测到,说明无误,下面编写消费者订阅接收信息
三.消息接收模块(消费者:8002,8003)
3.1 导入依赖
<!--rabbitMQ-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
3.2 配置文件
server:
port: 8802
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,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
3.3 启动类
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}
3.4 消费者访问接口
这里的controller不做映射,只是接收
@Component
@EnableBinding(Sink.class)//开启绑定,作为接收方
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)//接收消息
public void input(Message<String> message) {
System.out.println("消费者1号,接受:"+message.getPayload()+"\t port:"+serverPort);
}
}
同理,新建一个消费者8003
3.5 效果
发现studyExchange绑定了一个队列
访问http://127.0.0.1:8801/sendMessage发送信息
8001:
8002接收到消息:
8003接收到消息:
四.重复消费问题
4.1 解析
这时当提供者生成一条消息,8802和8803同时都会拿到消息消费,导致重复消费问题
不同组时可以全面消费(重复消费)的
同一组内会发送竞争关系,只有其中一个可以消费
从web界面可以看到默认是一个服务一个组,所以导致重复消费
4.2 解决方案
原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
使用分组:在8802与8803的application.yml 的bindings.input下添加group
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: desswA
4.3 效果
可以看到这个组有两个服务
访问http://127.0.0.1:8801/sendMessage,发送消息
8001:
8002:
8003:
8002和8003采用的是轮询的方式接收
五.Stream的消息持久化
现在模拟意外关闭8802和8803,8802的group注释掉,8803不用注释,8801在继续发送4条消息,先启动8802
启动8802发现,8802宕机后发送的消息,8802并没有消费
启动8803,发现8803一启动就重新消费了消息,得出结论配置group很重要,因为8801发送消息到所绑定的队列(desswA),但服务关闭启动后会消费对应组中未被消费的信息,如果8802没绑定对应的组,启动后就会默认重新生成单独一个组