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绑定在一起

基本构建

新建三个子模块,一个作为生产者进行发送消息模块,两个作为消息接收模块

服务端

  1. 导入 pom 依赖
<!-- spring-cloud-starter-stream-rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
  1. 修改 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
  1. 业务类

    发送消息的接口
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();
}
}

消费者

  1. 导入 pom 依赖
<!-- spring-cloud-starter-stream-rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
  1. 修改 yml 配置文件
# 服务整合处理
bindings:
# 通道名称
input:
# 表示要使用的 Exchange 名称定义
destination: studyExchange
# 设置消息类型,本次为为 json,文本则设置“text/plain”
content-type: application/json
binder: defaultRabbit
  1. 业务类
@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消息驱动的更多相关文章

  1. SpringCloud Stream 消息驱动

    1.什么是消息驱动 SpringCloud Stream消息驱动可以简化开发人员对消息中间件的使用复杂度,让系统开发人员更多尽力专注与核心业务逻辑的开发.SpringCloud Stream基于Spr ...

  2. SpringCloud学习之Stream消息驱动【自定义通道】(十一)

    如果不清楚本篇内容的,请务必先去看完上一篇再看本篇,否则阅读起来可能会有部分障碍和困难: 上一篇文章<SpringCloud学习之Stream消息驱动[默认通道](十)>我们简单用自定义通 ...

  3. SpringCloud实战9-Stream消息驱动

    官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架. 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互 ...

  4. spring cloud 2.x版本 Spring Cloud Stream消息驱动组件基础教程(kafaka篇)

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ri ...

  5. Spring Cloud 系列之 Stream 消息驱动(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Stream 消息驱动(一) 本篇文章讲解 Stream 如何实现消息分组和消息分区. 消息分组 如果有多个消息消费者 ...

  6. SpringCloud学习之Stream消息驱动【默认通道】(十)

    在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,而以往使用了中间件比如RabbitMQ,那么该中间件和系统的耦合性就会非常高,如果我们要替换为Kafka那么变动会比较大,这时我们可以使用S ...

  7. 九. SpringCloud Stream消息驱动

    1. 消息驱动概述 1.1 是什么 在实际应用中有很多消息中间件,比如现在企业里常用的有ActiveMQ.RabbitMQ.RocketMQ.Kafka等,学习所有这些消息中间件无疑需要大量时间经历成 ...

  8. Spring Cloud 系列之 Stream 消息驱动(一)

    在实际开发过程中,服务与服务之间通信经常会使用到消息中间件,消息中间件解决了应用解耦.异步处理.流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构. 不同中间件内部实现方式是不一样的,这些中间 ...

  9. Spring Cloud架构教程 (七)消息驱动的微服务(核心概念)【Dalston版】

    下图是官方文档中对于Spring Cloud Stream应用模型的结构图.从中我们可以看到,Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定 ...

随机推荐

  1. 算法 - 链表操作思想 && case

    算法 - 链表操作题目套路 前面这一篇文章主要讲链表操作时候的实操解决方式,本文从本质讲解链表操作的元信息,学完后,再也不怕链表操作题目了. 1.链表的基本操作 链表的基本操作无外乎插入,删除,遍历 ...

  2. Kubernetes 实战 —— 01. Kubernetes 介绍

    简介 P2 Kubernetes 能自动调度.配置.监管和故障处理,使开发者可以自主部署应用,并且控制部署的频率,完全脱离运维团队的帮助. Kubernetes 同时能让运维团队监控整个系统,并且在硬 ...

  3. Java并发之ThreadPoolExecutor源码解析(三)

    Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...

  4. ubuntu上pyecharts V1版本环境搭建

    1 背景 今天想用pyecharts画图,在新的环境下使用pip安装之后发现,导入pyecharts模块一直失败,报错如下. 图 1 导入pyecharts错误图 请注意:我这里使用的python版本 ...

  5. Python3+pygame中国象棋 代码完整 非常好 有效果演示

    这几天看到抖音上有个妹子下象棋超级猛,我的中国象棋也差不到哪去啊,走 做一个.... 一.运行效果 二.代码 下面的代码用到图片素材(images文件夹),下载地址如下:https://www.itp ...

  6. 13. Vue CLI脚手架

    一. Vue CLI 介绍 1. 什么是Vue CLI? Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统.Vue CLI 致力于将 Vue 生态中的工具基础标准化.它确保了各种构建工 ...

  7. JVM笔记 -- JVM的发展以及基于栈的指令集架构

    2011年,JDK7发布,1.7u4中,开始启用新的垃圾回收器G1(但是不是默认). 2017年,发布JDK9,G1成为默认GC,代替CMS.(一般公司使用jdk8的时候,会通过参数,指定GC为G1) ...

  8. Prometheus时序数据库-数据的插入

    Prometheus时序数据库-数据的插入 前言 在之前的文章里,笔者详细的阐述了Prometheus时序数据库在内存和磁盘中的存储结构.有了前面的铺垫,笔者就可以在本篇文章阐述下数据的插入过程. 监 ...

  9. Tomcat8弱口令+后台getshell

    漏洞原因 用户权限在conf/tomcat-users.xml文件中配置: <?xml version="1.0" encoding="UTF-8"?&g ...

  10. 利用xslt与xml实现具体字段字母的大小写转换

    定义一个全局的变量 <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" ...