SpringCloud(七)Stream消息驱动
Stream消息驱动
概述
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.4.RELEASE/reference/html/
官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架
应用程序通过 inputs 或者 outputs 来与Spring Cloud Stream中的 binder对象交互,通过配置来binding(绑定),而 Spring Cloud Stream 的 binder 对象负责与消息中间件交互,所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动
Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已建立和熟悉的Spring和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念
目前仅支持RabbitMQ、Kafka

设计思想
标准MQ

生产者/消费者之间靠消息媒介(Message)传递信息内容
消息必须走特定的通道(消息通道MessageChannel)
消息通道里的消息如何被消费呢,谁负责收发处理:消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
使用原因
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区
这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
通过定义绑定器 Binder 作为中间层,实现了应用程序与消息中间件细节之间的隔离
通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件实现
Stream中的消息通信方式遵循了发布-订阅模式,Topic主题进行广播,在RabbitMQ就是Exchange,在Kafka中就是Topic
基本流程
- Binder:很方便的连接中间件,屏蔽差异
- Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
- Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
常用API和注解
- Middleware:中间件,目前只支持RabbitMQ和Kafka
- Binder:Binder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
- @Input:注解标识输入通道,通过该输入通道接收到的消息进入应用程序
- @Output:注解标识输出通道,发布的消息将通过该通道离开应用程序
- @StreamListener:监听队列,用于消费者的队列的消息接收
- @EnableBinding:指信道channel和exchange绑定在一起
基本构建
新建三个子模块,一个作为生产者进行发送消息模块,两个作为消息接收模块
服务端
- 导入 pom 依赖
<!-- spring-cloud-starter-stream-rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 修改 yml 配置文件
server:
port: 8801
spring:
application:
name: stream-rabbitmq-provider
cloud:
stream:
# 配置要绑定的 rabbitmq 的服务信息
binders:
# 表示定义的名称,用于与 binding 整合
defaultRabbit:
# 消息组件类型
type: rabbit
# 设置 rabbitmq 相关配置环境
enviroment:
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: 123456
# 服务整合处理
bindings:
# 通道名称
output:
# 表示要使用的 Exchange 名称定义
destination: studyExchange
# 设置消息类型,本次为为 json,文本则设置“text/plain”
content-type: application/json
binder: defaultRabbit
- 业务类
发送消息的接口
public interface MessageProvider {
/**
* 消息发送
* @return :返回值
*/
Message<?> send();
}
发送消息接口的实现类
@EnableBinding(Source.class)
public class MessageProviderImpl implements MessageProvider {
/**
* @ InboundChannelAdapter
* 作用:表示定义的方法能产生消息
* fixedDelay:多少毫秒发送1次
*/
@Override
@InboundChannelAdapter(channel = Source.OUTPUT,poller = @Poller(fixedDelay = "10000")) // 每隔10秒发送一次
public Message<String> send() {
String serial = UUID.randomUUID().toString();
return MessageBuilder.withPayload(serial)
.build();
}
}

Controller 层
@RestController
public class SendMessageController {
@Resource
private MessageProvider provider;
@GetMapping(value = "/senMessage")
public Message senMessage(){
return provider.send();
}
}

消费者
- 导入 pom 依赖
<!-- spring-cloud-starter-stream-rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 修改 yml 配置文件
# 服务整合处理
bindings:
# 通道名称
input:
# 表示要使用的 Exchange 名称定义
destination: studyExchange
# 设置消息类型,本次为为 json,文本则设置“text/plain”
content-type: application/json
binder: defaultRabbit
- 业务类
@RestController
public class SendMessageController {
@Resource
private MessageProvider provider;
@GetMapping(value = "/senMessage")
public Message senMessage(){
return provider.send();
}
}

分组消费和持久化
当使用两个消费者来进行接收消息时,会出现两个问题:重复消费和消息持久化的问题
重复消费
目前是8802/8803同时都收到了,存在重复消费问题


解决方法:分组和持久化属性Group
比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息
那如果一个订单同时被两个服务获取到,那么就会造成数据错误,为了避免这种情况这时我们就可以使用Stream中的消息分组来解决

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次,不同组是可以全面消费的(重复消费),同一组内会发生竞争关系,只有其中一个可以消费
分组
微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费
自定义分组
修改消费者的 yml 文件,新增一个 group 的属性
# 服务整合处理
bindings:
# 通道名称
input:
# 表示要使用的 Exchange 名称定义
destination: studyExchange
# 设置消息类型,本次为为 json,文本则设置“text/plain”
content-type: application/json
binder: defaultRabbit
group: Consumer
分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,这里举例实现两个消费微服务
多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面启动两个应用的例子,虽然它们同属一个应用,但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念
实现轮询分组,每次只有一个消费者,生产者发送的消息只能被一个消费者接收到,避免重复消费
将案例的两个消费者变成相同组
同一个组的多个微服务实例,每次只会有一个拿到


持久化
配置好 group 这个属性后可以发现
当消费者发生一些错误停止服务时,但是此时的生产者还在不断的发送消息,如果消费者没有配置 group ,那么这些消息就被错过了
当配置好 group 这个属性,消费者就算发生一些错误停止服务,再启动时,就会获取到之前停止服务期间生产者发来的消息
SpringCloud(七)Stream消息驱动的更多相关文章
- SpringCloud Stream 消息驱动
1.什么是消息驱动 SpringCloud Stream消息驱动可以简化开发人员对消息中间件的使用复杂度,让系统开发人员更多尽力专注与核心业务逻辑的开发.SpringCloud Stream基于Spr ...
- SpringCloud学习之Stream消息驱动【自定义通道】(十一)
如果不清楚本篇内容的,请务必先去看完上一篇再看本篇,否则阅读起来可能会有部分障碍和困难: 上一篇文章<SpringCloud学习之Stream消息驱动[默认通道](十)>我们简单用自定义通 ...
- SpringCloud实战9-Stream消息驱动
官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架. 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互 ...
- spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)
本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...
- Spring Cloud 系列之 Stream 消息驱动(二)
本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Stream 消息驱动(一) 本篇文章讲解 Stream 如何实现消息分组和消息分区. 消息分组 如果有多个消息消费者 ...
- SpringCloud学习之Stream消息驱动【默认通道】(十)
在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,而以往使用了中间件比如RabbitMQ,那么该中间件和系统的耦合性就会非常高,如果我们要替换为Kafka那么变动会比较大,这时我们可以使用S ...
- 九. SpringCloud Stream消息驱动
1. 消息驱动概述 1.1 是什么 在实际应用中有很多消息中间件,比如现在企业里常用的有ActiveMQ.RabbitMQ.RocketMQ.Kafka等,学习所有这些消息中间件无疑需要大量时间经历成 ...
- Spring Cloud 系列之 Stream 消息驱动(一)
在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,消息中间件解决了应用解耦.异步处理.流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构. 不同中间件内部实现方式是不一样的,这些中间 ...
- Spring Cloud架构教程 (七)消息驱动的微服务(核心概念)【Dalston版】
下图是官方文档中对于Spring Cloud Stream应用模型的结构图.从中我们可以看到,Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定 ...
随机推荐
- keras报错:AttributeError: '_thread._local' object has no attribute 'value'
需求是使用pyqt5中的槽函数运行keras模型训练,为了不让工具在模型训练的过程中出现假死的现象,于是把训练操作放到单独的线程中运行,于是问题来了,训练操作在主线程运行时正常,但是界面假死,假若训练 ...
- ss_port_change - 一键修改ss配置与Centos7的Firewall策略脚本
ss_port_change 修改ss配置与Centos7的Firewall策略脚本 注意是否需要修改config路径与ss服务的名 脚本的敏感字用了*代替 项目地址 Github 脚本 #!/bin ...
- 剑指 Offer 19. 正则表达式匹配 + 动态规划
剑指 Offer 19. 正则表达式匹配 题目链接 一. 字符串匹配大致可以分为三种情况: 第一种:正则串的最后一个字符为正常字符,此时根据主串的最后一个字符是否和它相同来判断是否匹配, 如果相同,则 ...
- solr 远程代码执行(CVE-2019-12409)
Apache Solr 远程代码执行漏洞(CVE-2019-12409) 简介 Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口.是apache的顶级开源项 ...
- springboot源码解析-管中窥豹系列之BeanFactoryPostProcessor(十一)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- 单元测试 - SpringBoot2+Mockito实战
单元测试 - SpringBoot2+Mockito实战 在真实的开发中,我们通常是使用SpringBoot的,目前SpringBoot是v2.4.x的版本(SpringBoot 2.2.2.RELE ...
- Bits.java
package java.io; /** * Utility methods for packing/unpacking primitive values in/out of byte arrays ...
- SpringCloud 中 Feign 调用使用总结
最近做微服务架构的项目,在用 feign 来进行服务间的调用.在互调的过程中,难免出现问题,根据错误总结了一下,主要是请求方式的错误和接参数的错误造成的.在此进行一下总结记录. 以下通过分为三种情况说 ...
- IPFS挖矿的成本有哪些?
IPFS作为区块链新贵,近来风头一时无量.截止3月9日,Filecoin以257亿的流通市值超越门罗币,稳居区块链流通排行榜. 无论什么投资,其门槛一定在成本.今天就和大家细说投资市面上常见实体矿机的 ...
- 6、MyBatis教程之日志实现
7.日志实现 思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率? 如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题. 对于以 ...