【SpringCloud笔记】(11)消息驱动之Stream

news2024/11/18 23:34:33

Stream

技术背景

底层不同模块可能使用不同的消息中间件,这就导致技术的切换,微服务的维护及开发变得麻烦起来

在这里插入图片描述

概述

官网:
https://spring.io/projects/spring-cloud-stream#overview
https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/

Spring Cloud Stream中文指导手册:
https://m.wang1314.com/doc/webapp/topic/20971999.html

什么是SpringCloudStream

官方定义Spring Cloud Stream是一个构建消息驱动微服务的框染

应用程序通过inputs 或者 outputsSpring Cloud Stream中binder对象交互

通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互

所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。

Spring Ccloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQ、Kafka

Stream设计思想

标准MQ流程

在这里插入图片描述

  • 生产者/消费者之间靠消息媒介传递信息内容(Message)

  • 消息必须走特定的通道(消息通道MessageChannel)

  • 消息通道里的消息如何被消费呢,谁负责收发处理(消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅)

为什么要引入Stream

比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区

这些中间件的差异导致我们实际项目开发给我们造成了一定的困难,我们如果用了两个消息队列的其中一种,后面的业务需求,我们想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候SpringCloud Stream给我们提供了一种解耦合的方式。

在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性。

通过定义绑定器Binder作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离

Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

Stream的消息通信方式遵循了发布-订阅模式

INPUT对应于生产者,OUTPUT对应于消费者

在这里插入图片描述

Stream标准流程套路

在这里插入图片描述

  • binder:很方便的连接中间件,屏蔽差异
  • Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置
  • Source(生产)和sink(消费):简单地可理解为参照对象是spring cloud stream自身,从stream发布消息就是输出,接收消息就是输入

常用注解

组成说明
Middleware中间件,目前只支FRabbitMQ和Kafka
BinderBinder是应用与消息中间件之间的封装,目前实行了KafKa和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
@Input注解标识输入通道,通过该输入通接收到的消息息进入应用程序
@Output注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding指信道channel和exchange绑定在一起

要新建3个子模块

cloud-stream-rabbitmq-consumer8802:作为消息接收模块
cloud-stream-rabbitmq-consumer8802:作为消息接收模块

消息驱动之生产者

新建cloud-stream-rabbitmq-provide8801:作为生产者进行发消息模块

pom文件

<artifactId>cloud-stream-rabbitmq-provide8801</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
      <groupId>com.mzr.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>${project.version}</version>
    </dependency>
  </dependencies>

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地址

主启动类

@SpringBootApplication
public class StreamMQMain8801
{
    public static void main(String[] args)
    {
        SpringApplication.run(StreamMQMain8801.class,args);
    }
}

业务类

定义发送消息的接口

public interface IMessageProvider
{
    public String send();
}

定义发送消息的接口

//@Service 这个地方不再需要该注解,不再是和controller打交道的service,而是发送消息的推送服务类与Stream打交道,可结合Stream标准流程图来看
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider
{
    @Resource
    private MessageChannel output; // 消息发送管道

    @Override
    public String send()
    {
        String serial = UUID.randomUUID().toString();
        //将消息与绑定器绑定
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("*****serial: "+serial);
        return null;
    }
}

controller

@RestController
public class SendMessageController
{
    @Resource
    private IMessageProvider messageProvider;

    @GetMapping(value = "/sendMessage")
    public String sendMessage()
    {
        return messageProvider.send();
    }
}

启动rabbitMQ

在这里插入图片描述
这里的studyExchange便是yml文件配置的

在这里插入图片描述
启动eureka7001、生产者8801,多次访问localhost:8801/sendMessage

可以看到rabbitMQ中也有流量的起伏

在这里插入图片描述

消息驱动之消费者

新建模块 cloud-stream-rabbitmq-consumer8802

pom文件与8801一样

省略..

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: # 服务的整合处理
          #output: # 这个名字是一个通道的名称,这是8802与8801唯一的区别,一个是消息发送一个是消息接收
          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地址

主启动类与8801一致

省略..

业务类

因为这是消费者是接受消息,所以只需关注controller层业务逻辑

controller层

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController
{
    @Value("${server.port}")
    private String serverPort;


    @StreamListener(Sink.INPUT)//监听输入源
    public void input(Message<String> message)
    {
        System.out.println("消费者1号,----->接受到的消息: "+message.getPayload()+"\t  port: "+serverPort);
    }
}

启动8802测试

可以看到这个通道已经被监听了

在这里插入图片描述

可以看到8801发送的流水号与8802接受到的流水号对应

在这里插入图片描述

在这里插入图片描述

分组消费及持久化

依照8802,clone出来一份cloud-stream-rabbitmq-consumer8802作为消费者并启动

运行后有两个问题

  • 有重复消费问题
  • 消息持久化

消息重复消费

8801发送的消息,8802及8803同时都收到了,存在着重复消费问题

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

在这里插入图片描述

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次不同组是可以全面消费的(重复消费)

默认分组group是不同的,组流水号不一样,被认为不同组,可以消费,所以导致8002 8003同时收到相同的消息

在这里插入图片描述

解决消息重复消费

原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。

实现步骤:

1、8802/8803都变成不同组,group两个不同

8802/8803配置yml文件

	# 8802
	bindings:
        input:   
          destination: studyExchange  
          content-type: application/json  
          binder: defaultRabbit  
          group: atguiguA  # 自定义分组配置
          
    # 8803
	bindings:
        input:   
          destination: studyExchange  
          content-type: application/json  
          binder: defaultRabbit  
          group: atguiguB  # 自定义分组配置

可以看到RabbitMQ上已经变成了我们自定义的分组

在这里插入图片描述
此时8002 8003同时收到相同的消息

2、8802/8803都变成相同组,group两个相同

将8802/8803yml文件中group修改为atguiguA

在这里插入图片描述

访问2次localhost:8801/sendMessage触发生产者发送2条信息

在这里插入图片描述

消费者8802接收到1条

在这里插入图片描述

消费者8803接收到1条

在这里插入图片描述

8802/8803实现了轮询分组,每次只有一个消费者,8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。

消息持久化

做个小测试感受一下消息持久化的重要性

停止8802/8803并去除掉8802的分组group: atguiguA,此时8803的分组group: atguiguA并没有去掉

8801先发送4条消息到rabbitmq

在这里插入图片描述

重新启动8802,因为没有分组属性配置,后台没有打出来消息

重新启动8803,因为有分组属性配置,后台打出来了8801生产者发送的消息

在这里插入图片描述

group分组属性对于避免消息重复消费 及 消息持久化避免消息丢失是非常重要的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1338287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Go语言中处理HTTP文件上传

大家好&#xff0c;我是你们可爱又迷人的编程小助手&#xff0c;今天要带你们一起探讨在Go语言中如何处理HTTP文件上传&#xff0c;让我们把这场技术之旅变得轻松有趣吧&#xff01; 首先&#xff0c;想象一下这个场景&#xff1a;你是一个网站的开发者&#xff0c;用户们急切…

pycharm 工具栏不见了

新版pycharm后&#xff0c; 菜单栏和工具栏不见了 目录 我发现的解决方法&#xff1a; 其他旧版的解决方法&#xff1a; 我发现的解决方法&#xff1a; 其他旧版的解决方法&#xff1a; 另外&#xff0c;一些使用pycharm的新手可能会由于不熟悉软件的功能而误操作&#xff…

文件夹共享(普通共享和高级共享的区别)防火墙设置(包括了jdk安装和Tomcat)

文章目录 一、共享文件1.1为什么需要配置文件夹共享功能&#xff1f;1.2配置文件共享功能1.3高级共享和普通共享的区别&#xff1a; 二、防火墙设置2.1先要在虚拟机上安装JDK和Tomcat供外部访问。2.2设置防火墙&#xff1a; 一、共享文件 1.1为什么需要配置文件夹共享功能&…

springboot对接WebSocket实现消息推送

1.修改pom文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 2.增加配置WebSocketConfig.java import org.springframework.context.annotation.Bean…

接口测试工具——ApiFox使用初体验 postman导出和ApiFox导入

目录 ApiFox使用初体验初步使用从postman导出到apifox导入 IDEA简单测试Postman测试工具post请求 接口测试工具swaggerKnife4j1.引入依赖2.配置3.常用注解4.接口测试 JMeter什么是JMeter?JMeter安装配置1.官网下载2.下载后解压3.汉语设置 JMeter的使用方法1.新建线程组2.设置参…

智能监控平台/视频共享融合系统EasyCVR海康设备国标GB28181接入流程

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…

基于springboot的数码论坛系统设计与实现

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 摘 要 网络的广泛应用给…

laravel api资源的问题记录

resource 转换层 可以帮助我们转换一些字段的结果&#xff0c;类似前端的filter。 可以使用比如对象或者模型的形式来处理&#xff0c;但使用sql查询会导致n1的问题。如图&#xff1a; 层次嵌套很多&#xff0c;而且很深&#xff0c;这样虽然开发方便了&#xff0c;但是维护就…

Mysql(5日志备份恢复)

一.日志管理 MySQL 的日志默认保存位置为 /usr/local/mysql/data 先看下mysql的日志文件有无&#xff1a; 修改配置文件添加&#xff1a;错误日志&#xff0c;用来记录当MySQL启动、停止或运行时发生的错误信息&#xff0c;默认已开启 修改配置文件添加&#xff1a;通用查…

PWR 电源控制-stm32入门

这一节我们来学习 STM32 的 PWR 电源控制。 其中&#xff0c;我们重点学习的主要就是 3 种低功耗模式&#xff1a;睡眠模式、停机模式和待机模式。 低功耗模式的目的呢&#xff1f;简单明了&#xff0c;就是省电&#xff0c;这对于一些使用电池供电&#xff0c;又需要长时间待…

腾讯云4核8G服务器轻量和CVM标准型S5对比

腾讯云4核8G服务器优惠价格表&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;阿腾云atengyun.com分享腾讯云4核8G服务器详细配置、优惠价格及限制条件&…

nodejs微信小程序+python+PHP基于Android自习室管理系统的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

基于ssm的电影评论系统论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装电影评论系统软件来发挥其高效地信息处理的作用&#xff0c…

电商低价 窜货问题怎么解决

渠道中最常见的问题一般都是围绕价格的&#xff0c;低价、窜货、假货问题尤为明显&#xff0c;经销商为了利益&#xff0c;总会进行价格的突破&#xff0c;窜货也是为了获得更多流量利润&#xff0c;也会变相低价&#xff0c;所以治理好低价、窜货问题&#xff0c;就是在管控渠…

【LeetCode刷题笔记(13-1)】【Python】【回文数】【反转整数】【简单】

文章目录 引言回文数题目描述提示 题意分析解决方案1&#xff1a;【反转字符串】解决方案2&#xff1a;【反转整数】题外话结束语 9. 回文数 引言 编写通过所有测试案例的代码并不简单&#xff0c;通常需要深思熟虑和理性分析。虽然这些代码能够通过所有的测试案例&#xff0…

[PyTorch][chapter 8][李宏毅深度学习][DNN 训练技巧]

前言&#xff1a; DNN 是神经网络的里面基础核心模型之一.这里面结合DNN 介绍一下如何解决 深度学习里面过拟合,欠拟合问题 目录&#xff1a; DNN 训练常见问题 过拟合处理 欠拟合处理 keras 项目 一 DNN 训练常见问题 我们在深度学习网络训练的时候经常会遇到下面…

Android原生实现分段选择

六年前写的一个控件&#xff0c;一直没有时间总结&#xff0c;趁年底不怎么忙&#xff0c;整理一下之前写过的组件。供大家一起参考学习。废话不多说&#xff0c;先上图。 一、效果图 实现思路使用的是radioGroup加radiobutton组合方式。原理就是通过修改RadioButton 的backgr…

什么是EMC工程师?

摘要: 今天来介绍一下什么是EMC工程师。一 EMC工程师起源要了解什么是EMC工程师&#xff0c;我们首先要了解什么是EMC。 今天来介绍一下什么是EMC工程师。 一 EMC工程师起源 要了解什么是EMC工程师&#xff0c;我们首先要了解什么是EMC。 工程师这个职业相信大家都耳熟能详…

Android10.0 人脸解锁流程分析

人脸解锁概述 人脸解锁即用户通过注视设备的正面方便地解锁手机或平板。Android 10 为支持人脸解锁的设备在人脸认证期间添加了一个新的可以安全处理相机帧、保持隐私与安全的人脸认证栈的支持&#xff0c;也为安全合规地启用集成交易的应用&#xff08;网上银行或其他服务&am…

华为OD机试 - 推荐多样性(Java JS Python C)

题目描述 推荐多样性需要从多个列表中选择元素,一次性要返回 N 屏数据(窗口数量),每屏展示 K 个元素(窗口大小),选择策略: 各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表中为每屏选择一个元素,依次类推每个列表的元素尽量均分为…