Spring Cloud 系列之 Stream 消息驱动(一)
在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,消息中间件解决了应用解耦、异步处理、流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。
不同中间件内部实现方式是不一样的,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,比如项目中间件为 Kafka,如果我们要替换为 RabbitMQ,这无疑就是一个灾难性的工作,一大堆东西都要重做,因为它跟我们系统的耦合性非常高。这时我们可以使用 Spring Cloud Stream 来整合我们的消息中间件,降低系统和中间件的耦合性。
1|0消息中间件的几大应用场景
1|1应用解耦
假设公司有几个不同的系统,各系统在某些业务有联动关系,比如 A 系统完成了某些操作,需要触发 B 系统及 C 系统,但是各个系统之间产生了耦合。针对这种场景,用消息中间件就可以完成解耦,当 A 系统完成操作时将数据放进消息队列,B 和 C 系统去订阅消息就可以了,这样各系统只要约定好消息的格式就可以了。
传统模式:
中间件模式:
1|2异步处理
比如用户在电商网站下单,下单完成后会给用户推送短信或邮件,发短信和邮件的过程就可以异步完成。因为下单付款才是核心业务,发邮件和短信并不属于核心功能,且可能耗时较长,所以针对这种业务场景可以选择先放到消息队列中,由其他服务来异步处理。
传统模式:
中间件模式:
1|3流量削峰
比如秒杀活动,一下子进来好多请求,有的服务可能承受不住瞬时高并发而崩溃,针对这种场景,在中间加一层消息队列,把请求先入队列,然后再把队列中的请求平滑的推送给服务,或者让服务去队列拉取。
传统模式:
中间件模式:
1|4日志处理
对于小型项目来说,我们通常对日志的处理没有那么多的要求,但是当用户量,数据量达到一定的峰值之后,问题就会随之而来。比如:
- 用户日志怎么存放
- 用户日志存放后怎么利用
- 怎么在存储大量日志而不对系统造成影响
等很多其他的问题,这样我们就需要借助消息队列进行业务的上解耦,数据上更好的传输。
Kafka 最开始就是专门为了处理日志产生的。
1|5总结
消息队列,是分布式系统中重要的组件,其通用的使用场景可以简单地描述为:当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
当遇到上面几种情况的时候,就要考虑用消息队列了。如果你碰巧使用的是 RabbitMQ 或者 Kafka ,而且同样也在使用 Spring Cloud,那你可以考虑下用 Spring Cloud Stream。
2|0什么是 Spring Cloud Stream
Spring Cloud Stream 是用于构建消息驱动微服务应用程序的框架。该框架提供了一个灵活的编程模型,该模型建立在已经熟悉 Spring 习惯用法的基础上,它提供了来自多家供应商的中间件的合理配置,包括 publish-subscribe,消息分组和消息分区处理的支持。
Spring Cloud Stream 解决了开发人员无感知的使用消息中间件的问题,因为 Stream 对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件,使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。
3|0核心概念
组成 | 说明 |
---|---|
Middleware | 中间件,支持 RabbitMQ 和 Kafka。 |
Binder | 目标绑定器,目标指的是 Kafka 还是 RabbitMQ。绑定器就是封装了目标中间件的包。如果操作的是 Kafka 就使用 spring-cloud-stream-binder-kafka ,如果操作的是 RabbitMQ 就使用 spring-cloud-stream-binder-rabbit 。 |
@Input | 注解标识输入通道,接收(消息消费者)的消息将通过该通道进入应用程序。 |
@Output | 注解标识输出通道,发布(消息生产者)的消息将通过该通道离开应用程序。 |
@StreamListener | 监听队列,消费者的队列的消息接收。 |
@EnableBinding | 注解标识绑定,将信道 channel 和交换机 exchange 绑定在一起。 |
4|0工作原理
通过定义绑定器作为中间层,实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件的实现。当需要升级消息中间件,或者是更换其他消息中间件产品时,我们需要做的就是更换对应的 Binder
绑定器而不需要修改任何应用逻辑。
该模型图中有如下几个核心概念:
Source
:当需要发送消息时,我们就需要通过Source.java
,它会把我们所要发送的消息进行序列化(默认转换成 JSON 格式字符串),然后将这些数据发送到 Channel 中;Sink
:当我们需要监听消息时就需要通过Sink.java
,它负责从消息通道中获取消息,并将消息反序列化成消息对象,然后交给具体的消息监听处理;Channel
:通常我们向消息中间件发送消息或者监听消息时需要指定主题(Topic)和消息队列名称,一旦我们需要变更主题的时候就需要修改消息发送或消息监听的代码。通过Channel
对象,我们的业务代码只需要对应Channel
就可以了,具体这个 Channel 对应的是哪个主题,可以在配置文件中来指定,这样当主题变更的时候我们就不用对代码做任何修改,从而实现了与具体消息中间件的解耦;Binder
:通过不同的Binder
可以实现与不同的消息中间件整合,Binder
提供统一的消息收发接口,从而使得我们可以根据实际需要部署不同的消息中间件,或者根据实际生产中所部署的消息中间件来调整我们的配置。
5|0环境准备
stream-demo
聚合工程。SpringBoot 2.2.4.RELEASE
、Spring Cloud Hoxton.SR1
。
-
RabbitMQ
:消息队列 -
eureka-server
:注册中心 -
eureka-server02
:注册中心
6|0入门案例
点击链接观看:Stream 入门案例视频(获取更多请关注公众号「哈喽沃德先生」)
6|1消息生产者
创建项目
在 stream-demo
项目下创建 stream-producer
子项目。
添加依赖
要使用 RabbitMQ 绑定器,可以通过使用以下 Maven 坐标将其添加到 Spring Cloud Stream 应用程序中:
或者使用 Spring Cloud Stream RabbitMQ Starter:
完整依赖如下:
配置文件
配置 RabbitMQ 消息队列和 Stream 消息发送与接收的通道。
发送消息
MessageProducer.java
启动类
StreamProducerApplication.java
6|2消息消费者
创建项目
在 stream-demo
项目下创建 stream-consumer
子项目。
添加依赖
要使用 RabbitMQ 绑定器,可以通过使用以下 Maven 坐标将其添加到 Spring Cloud Stream 应用程序中:
或者使用 Spring Cloud Stream RabbitMQ Starter:
完整依赖如下:
配置文件
配置 RabbitMQ 消息队列和 Stream 消息发送与接收的通道。
接收消息
MessageConsumer.java
启动类
StreamConsumerApplication.java
6|3测试
单元测试
MessageProducerTest.java
访问
启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:
RabbitMQ 界面如下:
7|0自定义消息通道
7|1创建消息通道
参考源码 Source.java
和 Sink.java
创建自定义消息通道。
自定义消息发送通道 MySource.java
自定义消息接收通道 MySink.java
7|2配置文件
消息生产者。
消息消费者。
7|3代码重构
消息生产者 MyMessageProducer.java
。
消息消费者 MyMessageConsumer.java
。
7|4测试
单元测试
MessageProducerTest.java
访问
启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:
RabbitMQ 界面如下:
8|0配置优化
Spring Cloud 微服务开发之所以简单,除了官方做了许多彻底的封装之外还有一个优点就是约定大于配置。开发人员仅需规定应用中不符约定的部分,在没有规定配置的地方采用默认配置,以力求最简配置为核心思想。
简单理解就是:Spring 遵循了推荐默认配置的思想,当存在特殊需求时候,自定义配置即可否则无需配置。
在 Spring Cloud Stream 中,@Output("output")
和 @Input("input")
注解的 value
默认即为绑定的交换机名称。所以自定义消息通道的案例我们就可以重构为以下方式。
8|1创建消息通道
参考源码 Source.java
和 Sink.java
创建自定义消息通道。
自定义消息发送通道 MySource02.java
自定义消息接收通道 MySink02.java
8|2配置文件
消息生产者。
消息消费者。
8|3代码重构
消息生产者 MyMessageProducer02.java
。
消息消费者 MyMessageConsumer02.java
。
8|4测试
单元测试
MessageProducerTest.java
访问
启动消息消费者,运行单元测试,消息消费者控制台打印结果如下:
RabbitMQ 界面如下:
9|0短信邮件发送案例
一个消息驱动微服务应用可以既是消息生产者又是消息消费者。接下来模拟一个短信邮件发送的消息处理过程:
- 原始消息发送至
source.message
交换机; - 消息驱动微服务应用通过
source.message
交换机接收原始消息,经过处理分别发送至sms.message
和email.message
交换机; - 消息驱动微服务应用通过
sms.message
和email.message
交换机接收处理后的消息并发送短信和邮件。
9|1创建消息通道
发送原始消息,接收处理后的消息并发送短信和邮件的消息驱动微服务应用。
接收原始消息,经过处理分别发送短信和邮箱的消息驱动微服务应用。
9|2配置文件
约定大于配置,配置文件只修改端口和应用名称即可,其他配置一致。
9|3消息驱动微服务 A
发送消息
发送原始消息 10086|10086@email.com
至 source.message
交换机。
接收消息
接收处理后的消息并发送短信和邮件。
9|4消息驱动微服务 B
接收消息
接收原始消息 10086|10086@email.com
处理后并发送至 sms.message
和 email.message
交换机。
发送消息
发送电话号码 10086
和邮箱地址 10086@email.com
至 sms.message
和 email.message
交换机。
9|5测试
单元测试
MessageProducerTest.java
访问
消息驱动微服务 A 控制台打印结果如下:
消息驱动微服务 B 控制台打印结果如下:
RabbitMQ 界面如下:
下一篇我们讲解 Stream 如何实现消息分组和消息分区,记得关注噢~