1.1 一句话
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
什么是SpringCloudStream
1、官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
2、应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。
通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。
所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
3、通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
4、目前仅支持RabbitMQ、Kafka。
1.2 官网
1.2.1 https://spring.io/projects/spring-cloud-stream#overview
Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念
1.2.2 https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
1.2.3 Spring Cloud Stream中文指导手册
https://m.wang1314.com/doc/webapp/topic/20971999.html
1.生产者/消费者之间靠消息媒介传递信息内容
(Message)
2.消息必须走特定的通道
(消息通道MessageChannel)
3.消息通道里的消息如何被消费呢,谁负责收发处理
(消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅)
2.2 为什么用Cloud Stream
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,
像RabbitMQ有exchange,kafka有Topic和Partitions分区,
这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。
2.2.1 stream凭什么可以统一底层差异?
1、在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,
由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
2、通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
1、 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
2、 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
3、Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
2.3 Stream中的消息通信方式遵循了发布-订阅模式
Topic主题进行广播
在RabbitMQ就是Exchange
在Kakfa中就是Topic
1、Binder
详解:很方便的连接中间件,屏蔽差异
2、Channel
详解:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
3、Source和Sink
详解:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
1.RibbitMQ环境已经OK
2.工程中新建三个子模块
1、cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块
2、cloud-stream-rabbitmq-consumer8802,作为消息接收模块
3、cloud-stream-rabbitmq-consumer8803 作为消息接收模块
1.新建Module
cloud-stream-rabbitmq-provider8801
2.改pom
<?xml version="1.0" encoding="UTF-8"?>
://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
>
>mscloud >
>com.atguigu.springcloud >
>1.0-SNAPSHOT >
>
>4.0.0 >
>cloud-stream-rabbitmq-provider8801 >
>
>
>org.springframework.boot >
>spring-boot-starter-web >
>
>
>org.springframework.boot >
>spring-boot-starter-actuator >
>
>
>org.springframework.cloud >
>spring-cloud-starter-netflix-eureka-client >
>
>
>org.springframework.cloud >
>spring-cloud-starter-stream-rabbit >
>
<!--基础配置-->
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
>
>
3.新建yml
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
4.主启动类StreamMQMain8801
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
/**
* @auther zzyy
* @create 2020-02-08 15:45
*/
@SpringBootApplication
public class StreamMQMain8801
{
public static void main(String[] args)
{
SpringApplication.run(StreamMQMain8801.class,args);
}
}
5.业务类
5.1发送消息接口
package com.atguigu.springcloud.service;
/**
* @auther zzyy
* @create 2020-02-09 8:30
*/
public interface IMessageProvider
{
public String send() ;
}
5.2发送消息接口实现类
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import javax.annotation.Resource;
import org.springframework.cloud.stream.messaging.Source;
import java.util.UUID;
/**
* @auther zzyy
* @create 2020-02-09 8:31
*/
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider
{
@Resource
private MessageChannel output; // 消息的发送管道
@Override
public String send()
{
String serial = UUID.randomUUID().toString();
this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
System.out.println("***serial: "+serial);
return serial;
}
}
5.3Controller
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;
/**
* @auther zzyy
* @create 2020-02-08 15:47
*/
@RestController
public class SendMessageController
{
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage()
{
return messageProvider.send();
}
}
6.测试
6.1 启动7001eureka
6.2 启动rabbitmq
命令启动:
rabbitmq-plugins enable rabbitmq_management
访问地址:
http://localhost:15672/
http://localhost:8801/sendMessage
1.新建Module
cloud-stream-rabbitmq-consumer8802
2.改pom
<?xml version="1.0" encoding="UTF-8"?>
://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
>
>mscloud >
>com.atguigu.springcloud >
>1.0-SNAPSHOT >
>
>4.0.0 >
>cloud-stream-rabbitmq-provider8801 >
>
>
>org.springframework.boot >
>spring-boot-starter-web >
>
>
>org.springframework.cloud >
>spring-cloud-starter-netflix-eureka-client >
>
>
>org.springframework.cloud >
>spring-cloud-starter-stream-rabbit >
>
>
>org.springframework.boot >
>spring-boot-starter-actuator >
>
<!--基础配置-->
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
>
>
3.新建yml
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
4.主启动类StreamMQMain8802
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @auther zzyy
* @create 2020-02-08 15:57
*/
@SpringBootApplication
public class StreamMQMain8802
{
public static void main(String[] args)
{
SpringApplication.run(StreamMQMain8802.class,args);
}
}
5.业务类
package com.atguigu.springcloud.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* @auther zzyy
* @create 2020-02-09 9:30
*/
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener
{
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message)
{
System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
}
}
6.测试8801发送8802接收消息
http://localhost:8801/sendMessage
1.依照8802,clone出来一份运行8803
端口不同,其他一样
2.启动
1.RabbitMQ
2.7001(服务注册)
3.8801(消息生产)
4.8802(消息消费)
5.8803(消息消费)
3.运行后有两个问题
1.有重复消费问题
2.消息持久化问题
4.消费
目前是8802/8803同时都收到了,存在重复消费问题
4.1 8801发送消息后8802与8803日志情况
4.2 如何解决
分组和持久化属性group
4.3 生产实例
1、 比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,
那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。
这时我们就可以使用Stream中的消息分组来解决
2、注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
3、不同组是可以全面消费的(重复消费),
4、同一组内会发生竞争关系,只有其中一个可以消费。
5.分组
5.1 原理
微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。
不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
5.2 8802/8803都变成不同组,group两个不同
5.2.1 配置属性
group: atguiguA、atguiguB
5.2.2 8802修改yml
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
5.2.3 8803修改yml
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguB
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8803.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
1、 分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,本例阳哥启动了两个消费微服务(8802/8803)
2、多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,
但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念。
5.2.5 结论
还是重复消费
5.3 8802/8803都变成相同组,group两个相同
8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
5.3.1 8802与8803属性配置
group: atguiguA
5.3.2 修改8802和8803的yml文件的group属性为一样
group: atguiguA
5.3.3 结论
同一个组的多个微服务实例,每次只会有一个拿到
6.持久化
6.1通过上述,解决了重复消费问题,再看看持久化
6.2 停止8802/8803并去除掉8802的分组group: atguiguA
8803的分组group: atguiguA没有去掉
6.3 8801先发送4条消息到rabbitmq
6.4 先启动8802,无分组属性配置,后台没有打出来消息
6.5 再启动8803,有分组属性配置,后台打出来了MQ上的消息
ps:尚硅谷SpringCloud学习笔记
SpringCloud(第一章 零基础理论入门)
SpringCloud(第二章 从2.2.x和H版开始说起)
SpringCloud(第三章 关于Cloud各种组件的停更/升级/替换)
SpringCloud(第四章 服务架构编码构建)
SpringCloud(第五章 Eureka服务注册与发现)
SpringCloud(第六章 Zookeeper服务注册与发现)
SpringCloud(第七章 Consul服务注册与发现)
SpringCloud(第八章 Ribbon负载均衡服务调用)
SpringCloud(第九章 OpenFeign服务接口调用)
SpringCloud(第十章 Hystrix断路器)
SpringCloud(第十一章 Gateway新一代网关)
SpringCloud(第十二章 SpringCloud Config 分布式配置中心)
SpringCloud(第十三章 SpringCloud Bus 消息总线)
SpringCloud(第十四章 SpringCloud Stream 消息驱动)
SpringCloud(第十五章 SpringCloud Sleuth 分布式请求链路跟踪)
SpringCloud(第十六章 SpringCloud Alibaba 入门简介)
SpringCloud(第十七章 SpringCloud Alibaba Nacos服务注册和配置中心)
SpringCloud(第十八章 SpringCloud Alibaba Sentinel实现熔断与限流)
SpringCloud(第十九章 SpringCloud Alibaba Seata处理分布式事务)
SpringCloud(第二十章 SpringCloud之雪花算法)