使用 Spring Cloud Stream 构建消息驱动微服务
相关源码: spring cloud demo
微服务的目的: 松耦合
事件驱动的优势:高度解耦
Spring Cloud Stream 的几个概念
Spring Cloud Stream is a framework for building message-driven microservice applications.
官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。

应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互,通过我们配置来 binding ,而 Spring Cloud Stream 的 binder 负责与中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式
Binder
Binder 是 Spring Cloud Stream 的一个抽象概念,是应用与消息中间件之间的粘合剂。目前 Spring Cloud Stream 实现了 Kafka 和 Rabbit MQ 的binder。
通过 binder ,可以很方便的连接中间件,可以动态的改变消息的
destinations(对应于 Kafka 的topic,Rabbit MQ 的 exchanges),这些都可以通过外部配置项来做到。
甚至可以任意的改变中间件的类型而不需要修改一行代码。
Publish-Subscribe
消息的发布(Publish)和订阅(Subscribe)是事件驱动的经典模式。Spring Cloud Stream 的数据交互也是基于这个思想。生产者把消息通过某个 topic 广播出去(Spring Cloud Stream 中的 destinations)。其他的微服务,通过订阅特定 topic 来获取广播出来的消息来触发业务的进行。
这种模式,极大的降低了生产者与消费者之间的耦合。即使有新的应用的引入,也不需要破坏当前系统的整体结构。
Consumer Groups
“Group”,如果使用过 Kafka 的童鞋并不会陌生。Spring Cloud Stream 的这个分组概念的意思基本和 Kafka 一致。
微服务中动态的缩放同一个应用的数量以此来达到更高的处理能力是非常必须的。对于这种情况,同一个事件防止被重复消费,只要把这些应用放置于同一个 “group” 中,就能够保证消息只会被其中一个应用消费一次。
Durability
消息事件的持久化是必不可少的。Spring Cloud Stream 可以动态的选择一个消息队列是持久化,还是 present。
Bindings
bindings 是我们通过配置把应用和spring cloud stream 的 binder 绑定在一起,之后我们只需要修改 binding 的配置来达到动态修改topic、exchange、type等一系列信息而不需要修改一行代码。
基于 RabbitMQ 使用
以下内容源码: spring cloud demo
消息接收
Spring Cloud Stream 基本用法,需要定义一个接口,如下是内置的一个接口。
public interface Sink {
String INPUT = "input";
@Input("input")
SubscribableChannel input();
}
注释 @Input 对应的方法,需要返回 SubscribableChannel ,并且参入一个参数值。
这就接口声明了一个 binding 命名为 “input” 。
其他内容通过配置指定:
spring:
cloud:
stream:
bindings:
input:
destination: mqTestDefault
destination:指定了消息获取的目的地,对应于MQ就是 exchange,这里的exchange就是 mqTestDefault
@SpringBootApplication
@EnableBinding(Sink.class)
public class Application {
// 监听 binding 为 Sink.INPUT 的消息
@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("一般监听收到:" + message.getPayload());
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
定义一个 class (这里直接在启动类),并且添加注解@EnableBinding(Sink.class) ,其中 Sink 就是上述的接口。同时定义一个方法(此处是 input)标明注解为@StreamListener(Processor.INPUT) ,方法参数为 Message 。
启动后,默认是会创建一个临时队列,临时队列绑定的exchange为 “mqTestDefault”,routing key为 “#”。
所有发送 exchange 为“mqTestDefault” 的MQ消息都会被投递到这个临时队列,并且触发上述的方法。
以上代码就完成了最基本的消费者部分。
消息发送
消息的发送同消息的接受,都需要定义一个接口,不同的是接口方法的返回对象是MessageChannel,下面是 Spring Cloud Stream 内置的接口:
public interface Source {
String OUTPUT = "output";
@Output("output")
MessageChannel output();
}
这就接口声明了一个 binding 命名为 “output” ,不同于上述的 “input”,这个binding 声明了一个消息输出流,也就是消息的生产者。
spring:
cloud:
stream:
bindings:
output:
destination: mqTestDefault
contentType: text/plain
contentType:用于指定消息的类型。具体可以参考 spring cloud stream docs
destination:指定了消息发送的目的地,对应 RabbitMQ,会发送到 exchange 是 mqTestDefault 的所有消息队列中。
代码中调用:
@SpringBootApplication
@EnableBinding(Source.class)
public class Application implements CommandLineRunner {
@Autowired
@Qualifier("output")
MessageChannel output;
@Override
public void run(String... strings) throws Exception {
// 字符串类型发送MQ
System.out.println("字符串信息发送");
output.send(MessageBuilder.withPayload("大家好").build());
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
通过注入MessageChannel的方式,发送消息。
通过注入Source 接口的方式,发送消息。 具体可以查看样例
以上代码就完成了最基本的生产者部分。
自定义消息发送接收
自定义接口
Spring Cloud Stream 内置了两种接口,分别定义了 binding 为 “input” 的输入流,和 “output” 的输出流,而在我们实际使用中,往往是需要定义各种输入输出流。使用方法也很简单。
interface OrderProcessor {
String INPUT_ORDER = "inputOrder";
String OUTPUT_ORDER = "outputOrder";
@Input(INPUT_ORDER)
SubscribableChannel inputOrder();
@Output(OUTPUT_ORDER)
MessageChannel outputOrder();
}
一个接口中,可以定义无数个输入输出流,可以根据实际业务情况划分。上述的接口,定义了一个订单输入,和订单输出两个 binding。
使用时,需要在 @EnableBinding 注解中,添加自定义的接口。
使用 @StreamListener 做监听的时候,需要指定OrderProcessor.INPUT_ORDER
spring:
cloud:
stream:
defaultBinder: defaultRabbit
bindings:
inputOrder:
destination: mqTestOrder
outputOrder:
destination: mqTestOrder
如上配置,指定了 destination 为 mqTestOrder 的输入输出流。
分组与持久化
上述自定义的接口配置中,Spring Cloud Stream 会在 RabbitMQ 中创建一个临时的队列,程序关闭,对应的连接关闭的时候,该队列也会消失。而在实际使用中,我们需要一个持久化的队列,并且指定一个分组,用于保证应用服务的缩放。
只需要在消费者端的 binding 添加配置项 spring.cloud.stream.bindings.[channelName].group = XXX 。对应的队列就是持久化,并且名称为:mqTestOrder.XXX。
rabbitMQ routing key 绑定
用惯了 rabbitMQ 的童鞋,在使用的时候,发现 Spring Cloud Stream 的消息投递,默认是根据 destination + group 进行区分,所有的消息都投递到 routing key 为 “#‘’ 的消息队列里。
如果我们需要进一步根据 routing key 来进行区分消息投递的目的地,或者消息接受,需要进一步配,Spring Cloud Stream 也提供了相关配置:
spring:
cloud:
stream:
bindings:
inputProductAdd:
destination: mqTestProduct
group: addProductHandler # 拥有 group 默认会持久化队列
outputProductAdd:
destination: mqTestProduct
rabbit:
bindings:
inputProductAdd:
consumer:
bindingRoutingKey: addProduct.* # 用来绑定消费者的 routing key
outputProductAdd:
producer:
routing-key-expression: '''addProduct.*''' # 需要用这个来指定 RoutingKey
spring.cloud.stream.rabbit.bindings.[channelName].consumer.bindingRoutingKey
指定了生成的消息队列的routing keyspring.cloud.stream.rabbit.bindings.[channelName].producer.routing-key-expression 指定了生产者消息投递的routing key
DLX 队列
DLX 作用
DLX:Dead-Letter-Exchange(死信队列)。利用DLX, 当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。消息变成死信一向有一下几种情况:
消息被拒绝(basic.reject/ basic.nack)并且requeue=false
消息TTL过期(参考:RabbitMQ之TTL(Time-To-Live 过期时间))
队列达到最大长度
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性,当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列,可以监听这个队列中消息做相应的处理。
Spring Cloud Stream 中使用
spring.cloud.stream.rabbit.bindings.[channelName].consumer.autoBindDlq=true
spring.cloud.stream.rabbit.bindings.[channelName].consumer.republishToDlq=true
配置说明,可以参考 spring cloud stream rabbitmq consumer properties
结论
Spring Cloud Stream 最大的方便之处,莫过于抽象了事件驱动的一些概念,对于消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件,切换topic。使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。
相关文档
使用 Spring Cloud Stream 构建消息驱动微服务的更多相关文章
- Spring Cloud Alibaba学习笔记(12) - 使用Spring Cloud Stream 构建消息驱动微服务
什么是Spring Cloud Stream 一个用于构建消息驱动的微服务的框架 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互, ...
- 「 从0到1学习微服务SpringCloud 」08 构建消息驱动微服务的框架 Spring Cloud Stream
系列文章(更新ing): 「 从0到1学习微服务SpringCloud 」01 一起来学呀! 「 从0到1学习微服务SpringCloud 」02 Eureka服务注册与发现 「 从0到1学习微服务S ...
- SpringCloud之Spring Cloud Stream:消息驱动
Spring Cloud Stream 是一个构建消息驱动微服务的框架,该框架在Spring Boot的基础上整合了Spring Integrationg来连接消息代理中间件(RabbitMQ, Ka ...
- 消息驱动微服务:Spring Cloud Stream
最近在学习Spring Cloud的知识,现将消息驱动微服务:Spring Cloud Stream 的相关知识笔记整理如下.[采用 oneNote格式排版]
- 今天介绍一下自己的开源项目,一款以spring cloud alibaba为核心的微服务架构项目,为给企业与个人提供一个零开发基础的微服务架构。
LaoCat-Spring-Cloud-Scaffold 一款以spring cloud alibab 为核心的微服务框架,主要目标为了提升自己的相关技术,也为了给企业与个人提供一个零开发基础的微服务 ...
- Spring Cloud Stream如何处理消息重复消费?
最近收到好几个类似的问题:使用Spring Cloud Stream操作RabbitMQ或Kafka的时候,出现消息重复消费的问题.通过沟通与排查下来主要还是用户对消费组的认识不够.其实,在之前的博文 ...
- Spring cloud stream【消息分区】
在上篇文章中我们给大家介绍了Stream的消息分组,可以实现消息的重复消费的问题,但在某些场景下分组还不能满足我们的需求,比如,同时有多条同一个用户的数据,发送过来,我们需要根据用户统计,但是消息 ...
- Spring cloud stream【消息分组】
上篇文章我们简单的介绍了stream的使用,发现使用还是蛮方便的,但是在上个案例中,如果有多个消息接收者,那么消息生产者发送的消息会被多个消费者都接收到,这种情况在某些实际场景下是有很大问题的,比 ...
- spring cloud 入门,看一个微服务框架的「五脏六腑」
Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 注:Spring Boot 简单理解就是简化 Spring 项目的搭建.配置.组 ...
随机推荐
- java 工厂模式 转载
下面介绍三种设计模式,简单工厂模式,工厂方法模式,抽象工厂模式 思考如下场景: 有一天,林同学准备去买笔记本,他到商城发现有两款电脑他特别喜欢, 一款是 Macbook Pro, 另一款是 Surfa ...
- 剑指offer(10)
题目: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 思路: 如果忽略题目中 ...
- 剑指offer(8)
题目: 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 思路: 第一反应想到的是把数右移,每一位与1相与,然后判断个数,但是若输入的为负数,会出现死循环现象. 所以我们设置一个标志量 ...
- js 解决中文乱码的问题
1.对象 request response 对象setCharacterEncoding=UTF-8 1 <%@ page language="java" contentTy ...
- 关于controller的书写
private Logger log = LoggerFactory.getLogger(ReportFormController.class); // 读取配置文件 ResourceBundle r ...
- 【.NET】.NET MVC4 微信扫一扫功能实现-附全部代码
写在前面的 首先在调用微信的JS-SDK接口的时候需要仔细阅读一下官方的注意事项,否则可能事倍功半.这里先大概概述一下主要的流程,首先,使用微信扫一扫需要一个已经通过认证的公众号:其次,需要知道 ...
- 【理论】X理论、Y理论及Z理论
道格拉斯·麦格雷戈(Douglas Mcgregor)把对人的基本假设作了区分,即X理论和Y理论.X理论认为:人们总是尽可能地逃避工作,不愿意承担责任,因此要想有效地进行管理,实现组织的目标,就必 ...
- Spring Boot 构建电商基础秒杀项目 (十二) 总结 (完结)
SpringBoot构建电商基础秒杀项目 学习笔记 系统架构 存在问题 如何发现容量问题 如何使得系统水平扩展 查询效率低下 活动开始前页面被疯狂刷新 库存行锁问题 下单操作步骤多,缓慢 浪涌流量如何 ...
- JUC虚假唤醒(六)
为什么条件锁会产生虚假唤醒现象(spurious wakeup)? 在不同的语言,甚至不同的操作系统上,条件锁都会产生虚假唤醒现象.所有语言的条件锁库都推荐用户把wait()放进循环里: whil ...
- webpack 打包编译-webkit-box-orient: vertical 后消失
/* autoprefixer: off */ -webkit-box-orient: vertical; // 参考 https://github.com/postcss/autoprefixer/ ...