SpringCloudStream消息驱动
1. 基本介绍
官方文档: https://spring.io/projects/spring-cloud-stream#learn
背景:
在一般的大型项目中,或者分布式微服务结构的系统里,一般都会使用到消息中间件,例如 RabbitMq 或者 ActiveMq 等等来实现系统间的异步消费, 对系统进行削峰填谷,提高系统的并发性能和扩展性
但是市面的 消息中间件种类繁多, 使用的方式也不相同, 但是达成的目的有时确实一致的, 此时若系统中使用了多个中间件,或者对消息中间件进行修改,那么对程序员的要求也将提高, 同时也对系统的扩展有了限制,
而SpringCloud Stream 就是解决这个问题的, 它屏蔽了底层具体中间件的工作流程(目前仅支持 RibbitMq和Kafka), 对外暴露一个"Binder" 实现与外部用户的交互, 并通过Spring Cloud Stream插入的input(相当于消费者consumer,它是从队列中接收消息的)和output(相当于生产者producer,它是从队列中发送消息的。)通道与外界交流
示意图:

上面图片中的各个角色:
- RabbitMq的底层支持, 被上层Binder 屏蔽
- Binder ,应用和消息中间件的封装,通过Binder可以很方便的连接中间件, 可以动态改变消息类型(对应Kafka的topic RabbitMq的exchange) ,这些都可以通过配置文件来实现
- Input, 通过该输入通道接受到的消息进入应用程序
- Output , 发布的消息将通过该通道离开应用程序
2. 基本使用
2.1 消息发送方搭建
pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- rabbit作为实现的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yaml配置文件
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #下面定义一个binders对象,连接用户和消息中间件
testRabbit: # 定义的名称,用于binding整合,在本例Rabbitmq中就是一个交换机名
type: rabbit #消息组件类型
environment: #环境配置
spring:
rabbitmq:
virtual-host: /test
host: 192.168.100.222
port: 5672
username: username
password: password
bindings: # 下面定义一个bindings 通道,供用户使用
output: # 名字是通道的名称,代码中需要指定这个名字,用于构建通道,并且此时并不确定是input还是output
destination: testExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #将本通道和定义的一个Binder对象绑定
测试发送消息代码
//发送信息业务接口
public interface IMessageProvider {
String send();
}
//实现类
@Service
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送的管道
@Override
public void send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: " +serial);
}
}
//controller调用
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
}
启动类:
@SpringBootApplication
@EnableBinding(Source.class) //构建消息通道,并注入到容器中
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
对上述代码的说明:
在配置文件中,分别定义了一个 Binder 对象,和一个Binding 通道, 并指定中间件类型和交换机名称,并将两者绑定
本例使用了简单的,框架自带的接口来构建初始化通道,
public interface Source {
String OUTPUT = "output"; @Output("output")
MessageChannel output();
}这个接口是框架为我们提供的简单的发送消息的使用方式, 使用注解
@Output指定使用此方法构建一个通道对象, 并且指定使用的配置就是配置文件中配置的Binding,名字为"output"的信息,此方法返回一个MessageChannel对象,并注册到容器中,可以使用此对象发送信息,bean的id也为"output"启动类上
@EnableBinding(Source.class)的定义,意味在启动时就将加载初始化此通道
当我们启动项目成功后,查看RabbitMq 管理后台,看见已经自动为我们创建一个名为"testRabbit"的exchange

2.2 消息接收方搭建
pom依赖和发送方一致
yaml:
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #需要绑定的rabbitmq的服务信息
testRabbit: #定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #环境配置
spring:
rabbitmq:
virtual-host: /test
host: 192.168.100.222
port: 5672
username: username
password: password
bindings: # 服务的整合处理
input: # 名字是一个通道的名称
destination: testExchange # 表示要使用的Exchange名称定义,和消息发送方一致
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #设置要绑定的消息服务的具体设置
接收业务类:
@Component
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);
}
}
启动类:
@SpringBootApplication
@EnableBinding(Sink.class)
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}
对上述代码的说明:
和发送方一样,配置文件中,需要指明整合的消息服务类型和配置,
启动类上使用注解
@EnableBinding,来初始化通道,使用的是自带的简单的接口Sinkpublic interface Sink {
String INPUT = "input"; @Input("input") //指明名称,返回的对象则为客户端接收对象使用到的对象
SubscribableChannel input();
}在接收消息的处理业务类中, 使用注解
@StreamListener,来指明接收的方法, 并标注和哪个通道相连,本例为Sink.INPUT
测试:
启动两个服务, 可以通过rabbitmq的后台管理页面看到, 有两个连接,并且创建了一个队列与exchange绑定
图一:

图二:

调用发送方接口http://127.0.0.1:8801/sendMessage,接收方成功打印信息:消费者1号,------>接收到的消息:aa86f3fb-a479-4eb0-92f2-03ab578b50f0 port8802
3. 自定义通道连接
上面的Demo中 ,我们使用了框架自带的 Source 和 Sink 接口,来实现简单的通信,但是在实际使用中, 我们还需要自定义连接规则,来实现复杂的功能,下面通过对上面的Demo 进行改造,来实现
3.1 消息发送方修改:
yaml添加新的通道:
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #下面定义一个binders对象,连接用户和消息中间件
testRabbit: # 定义的名称,用于binding整合,在本例Rabbitmq中就是一个交换机名
type: rabbit #消息组件类型
environment: #环境配置
spring:
rabbitmq:
virtual-host: /test
host: 192.168.100.222
port: 5672
username: username
password: password
bindings: # 下面定义一个bindings 通道,供用户使用
output: # 名字是通道的名称,代码中需要指定这个名字,用于构建通道,并且此时并不确定是input还是output
destination: testExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #将本通道和定义的一个Binder对象绑定
myOutput: #新的名称
destination: myExchange # 新的exchange
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #使用的消息服务还是同一个
自定义接口,初始化通道:
public interface MySource {
String OUTPUT = "myOutput"; // 绑定通道的配置,并且这也是MessageChannel 对象注册到容器中的id
@Output(OUTPUT)
MessageChannel output();
}
添加新的业务方法:
@Service
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送管道
@Resource
private MessageChannel myOutput; //字段名字必须为myOutput 才能接受到
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: " +serial);
return null;
}
@Override
public String mySend() {
String serial = UUID.randomUUID().toString();
myOutput.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: " +serial);
return null;
}
}
主启动类添加通道的绑定声明:
@SpringBootApplication
@EnableBinding({Source.class, MySource.class}) //定义消息的推送广播
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
3.2 消息接收方修改:
yaml添加配置:
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #需要绑定的rabbitmq的服务信息
testRabbit: #定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #环境配置
spring:
rabbitmq:
virtual-host: /test
host: 192.168.100.222
port: 5672
username: username
password: password
bindings: # 服务的整合处理
input: # 名字是一个通道的名称
destination: testExchange # 表示要使用的Exchange名称定义,和消息发送方一致
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #设置要绑定的消息服务的具体设置
myInput:
destination: myExchange # 和发送方一致
content-type: application/json
binder: testRabbit
同样需要自定义接口:
public interface MySink {
String INPUT = "myInput";
@Input(INPUT)
SubscribableChannel input();
}
添加接收方法:
@Component
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);
}
@StreamListener(MySink.INPUT)
public void myInput(Message<String> message) {
System.out.println("自定义通道-->接收到的消息:"+message.getPayload()+"\t port" +serverPort);
}
}
主启动类添加声明:
@SpringBootApplication
@EnableBinding({Sink.class, MySink.class}) //添加声明
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}
测试:
通过后台查看,同样创建了一个名为myexchange的交换机,并且接收方创建了一个队列与之绑定

4. 消息分组
对上述Demo 的消息接收方进行拷贝,修改端口号为8803, 模拟消息接收方有两个时的情况,并启动三个服务
查看rabbitmq后台:

可以看到, testExchange 交换机下已经创建了两个队列,分别对应了两个接收方服务,并且队列名是随机分配的.
这时我们通过消息发送方服务向该交换机发送消息:
发现两个接收方,都进行了消费:说明在发送消息时,并没有指定对应的队列名为RoutingKey.所以默认为对应的队列名,即每个队列都发,这样每个队列的监听客户端都可以监听到,
消费者1号,------>接收到的消息:fe392be0-c779-4e99-9c8c-ab85a9144399 port8802
消费者2号,------>接收到的消息:fe392be0-c779-4e99-9c8c-ab85a9144399 port8803
那如果需要指定某个队列如何解决: SpringCloud Stream ,提供了分组的概念解决这个问题
只需在不想要重复消费的binding 上指定其分组即可:
bindings: # 服务的整合处理
input: # 名字是一个通道的名称
destination: testExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #设置要绑定的消息服务的具体设置
group: groupA
myInput:
destination: myExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json
binder: testRabbit #设置要绑定的消息服务的具体设置
本例中,将两个客户端input通道的分组都设置为groupA ,代表其为同一个分组下的客户端,一次只能有一个消费即可,对应Rabbitmq,也是创建了groupA为名字的队列,并且两个客户端共同监听:


所以,每次生产方发送的信息,只由一个客户端消费,并且默认为轮询的方式
如果指定不同的分组,即创建不同的队列,如下所示,此时也将重复消费

消息持久化:
在没有指定分组队列名前,使用默认生成的队列名,并且创建的队列随着其监听客户端的销毁而销毁, 没有保持持久化,后续当服务重新启动后,就再创建一个新的,
如果使用group 指定队列名,则队列持久化,此时在客户端宕机过程中,如果有消息则持久化在 Rabbitmq的队列中,当再次连接时,继续消费
SpringCloudStream消息驱动的更多相关文章
- spring-cloud-stream消息驱动的微服务
Spring Cloud Stream 是 一 个用来为微服务应用构建消息驱动能力的框架. 它可以基于Spring Boot 来创建独立的. 可用于生产的 Spring 应用程序. 它通过使用 Spr ...
- SpringCloud实战9-Stream消息驱动
官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架. 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互 ...
- SpringCloud之Spring Cloud Stream:消息驱动
Spring Cloud Stream 是一个构建消息驱动微服务的框架,该框架在Spring Boot的基础上整合了Spring Integrationg来连接消息代理中间件(RabbitMQ, Ka ...
- spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)
本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...
- SpringCloud学习之Stream消息驱动【默认通道】(十)
在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,而以往使用了中间件比如RabbitMQ,那么该中间件和系统的耦合性就会非常高,如果我们要替换为Kafka那么变动会比较大,这时我们可以使用S ...
- SpringCloud(七)Stream消息驱动
Stream消息驱动 概述 屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型 官网:https://cloud.spring.io/spring-cloud-static/spring-cl ...
- Intellij IDEA 创建消息驱动Bean - 接收JMS消息
除了同步方式的调用之外,有时还需要异步调用,用来处理不需要即时处理的信息,例如短信.邮件等,这需要使用EJB中的独特组件——消息驱动Bean(Message-Driven Bean,MDB),它提供了 ...
- JMS和消息驱动Bean(MDB)
一.说明 本示例使用的ActiveMQ作为消息中间件,服务器为Glassfish,使用JMS发送消息,在MDB接收到消息之后做打印输出. 二.ActiveMQ安装配置 1.安装console war包 ...
- EJB_消息驱动发展bean
消息驱动发展bean Java信息服务(Java MessageService) Java 信息服务(Java Message Service,简称 JMS)是用于訪问企业消息系统的开发商中立的API ...
- Akka(1):Actor - 靠消息驱动的运算器
Akka是由各种角色和功能的Actor组成的,工作的主要原理是把一项大的计算任务分割成小环节,再按各环节的要求构建相应功能的Actor,然后把各环节的运算托付给相应的Actor去独立完成.Akka是个 ...
随机推荐
- 【发现一个问题】macos m2 下无法使用 x86_64-linux-musl-gcc 链接含有 avx512 指令的 c 代码
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 一开始是使用 golang 中的 cgo 来编译: env ...
- Linux线程API使用与分析
线程是操作系统进程调度器可调度的最小粒度的执行单元 执行ps -eLF查看线程 UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD root 1 ...
- 人工智能自然语言处理:N-gram和TF-IDF模型详解
人工智能自然语言处理:N-gram和TF-IDF模型详解 1.N-gram 模型 N-Gram 是一种基于统计语言模型的算法.它的基本思想是将文本里面的内容按照字节进行大小为 N 的滑动窗口操作,形成 ...
- TeXStudio与Bakoma TeX 结合实现实时阅览
参考链接:VSCode 或 TeXStudio LaTeX 配置方法 - 知乎 相信大家在使用TeXStudio时候,每次修改完毕都要运行一下再能看到PDF界面,这样做十分不方便,因此先给出如下操作办 ...
- Metasploit 生成各种后门
Metasploit 是一款开源的安全漏洞检测工具,可以帮助安全和IT专业人士识别安全性问题,验证漏洞的缓解措施,同时该工具也是渗透测试环境中的利器,它支持多平台Payload的生成具有完全的跨平台性 ...
- 多路io复用pool [补档-2023-07-19]
多路IO- poll 3.1简介 poll的机制与select类似,他们都是让内核在以线性的方法对文件描述符集合进行检测,根据描述符的状态进行具体的操作.并且poll和select在检测描述符集合 ...
- CF452F Permutation 与 P2757 [国家集训队] 等差子序列 题解
两道基本一样的题: 题目链接: P2757 [国家集训队] 等差子序列 Permutation 链接:CF 或者 洛谷 等差子序列那题其实就是长度不小于 \(3\) 的等差数列是否存在,我们考虑等于 ...
- 知乎利用 JuiceFS 给 Flink 容器启动加速实践
本文作者胡梦宇,知乎大数据架构开发工程师,主要负责知乎内部大数据组件的二次开发和数据平台建设. 背景 Flink 因为其可靠性和易用性,已经成为当前最流行的流处理框架之一,在流计算领域占据了主导地位. ...
- Linux-yum卸载软件包
yum是Linux操作系统中最常用的软件包管理器之一,它可以帮助你很容易地安装.删除和更新软件包.然而,有时候yum在删除软件包时可能会出现一些问题,本文将告诉你如何正确地使用yum卸载软件包,并解决 ...
- Python-open函数-读写文件
一.open 函数语法 open() 函数的作用是打开一个文件,并返回一个 file对象(即文件对象). open 是一个动作,可以理解为我们打开文档的点击动作. file 对象是一个实物,可以理解为 ...