SpringCloud Stream 消息驱动

一、前言

        接下来是开展一系列的 SpringCloud 的学习之旅,从传统的模块之间调用,一步步的升级为 SpringCloud 模块之间的调用,此篇文章为第九篇,即介绍 Stream 消息驱动。

二、消息驱动概念

2.1 消息驱动是什么

        官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。

        说的通俗一点就是:以前你的项目中可能既用到了 rabbitmq,又用到了 kafka,现在只需要使用一个 Spring Cloud Stream 就可以了。

2.2 官网

        官网的地址在这,也可以访问它的中文指导手册,但是需要注意的是,目前仅支持 RabbitMQ Kafka

2.3 设计思想

2.3.1 标准 mq

        对于标准的 mq 来说,架构如下图,生产者/消费者之间靠消息媒介传递信息内容,消息必须走特定的通道

2.3.2 Stream

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

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

2.3.3 Stream 如何实现统一底层差异

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

        在没有绑定器这个概念的之前,我们的 SpringBoot 应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

2.3.4 绑定器 Binder

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

        Binder 可以生成 BindingBinding 用来绑定消息容器的生产者和消费者,它有两种类型, INPUT OUTPUTINPUT 对应于消费者,OUTPUT 对应于生产者。

        Stream 中的消息通信方式遵循了发布-订阅模式,在 RabbitMQ 就是 Exchange,在 Kakfa 中就是 Topic

2.4 Stream 标准流程套路

         1、Binder:很方便的连接中间件,屏蔽差异

        2、Channel:通道,是队列 Queue 的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过 Channel 对队列进行配置。

        3、SourceSink:简单的可理解为参照对象是 Spring Cloud Stream 自身,从 Stream 发布消息就是输出,接受消息就是输入。

2.5 编码 API 和常用注解

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

三、消息驱动之生产者

3.1 工程创建

        新建一个 cloud-stream-rabbitmq-consumer8801 模块用来充当消息的生产者,pom.xml 内容如下所示,只有一个依赖比较特殊。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.springcloud</groupId><artifactId>SpringCloud</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloud-stream-rabbitmq-provider8801</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><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><!--引入stream整合rabbit的依赖,如果是kafka,则也引入对于的依赖即可--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-rabbit</artifactId></dependency><!--基础配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>

        application.yml 内容如下所示,比较特殊的是引入了 stream 的相关配置,binders 标签指定继承了哪种消息中间件并配置其连接地址、用户名和密码等。bindings 标签表示要对哪些服务进行整合,output 表示这是一个消息的生产者,destination 表示的是 rabbitmq 里面的交换机名称,binder 绑定的是上面指定的消息中间件。

server:port: 8801spring:application:name: cloud-stream-providercloud:stream:binders: # 在此处配置要绑定的rabbitmq的服务信息;defaultRabbit: # 表示定义的名称,用于于binding整合type: rabbit # 消息组件类型environment: # 设置rabbitmq的相关的环境配置spring:rabbitmq:host: localhostport: 5672username: guestpassword: guestbindings: # 服务的整合处理output: # 这个名字是一个通道的名称destination: studyExchange # 表示要使用的Exchange名称定义content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”binder: defaultRabbit # 设置要绑定的消息服务的具体设置eureka:client: # 客户端进行Eureka注册的配置service-url:defaultZone: http://localhost:7001/eurekainstance: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);}
}

        创建发送消息 Service 接口和其实现类代码如下:

package com.springcloud.service;public interface IMessageProvider {public String send() ;
}
package com.springcloud.service.impl;import com.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;import javax.annotation.Resource;
import java.util.UUID;// 第一步:可以理解为是一个消息的发送管道的定义,消息的生产者用 source,消息的消费者用 sink
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider
{@Resourceprivate MessageChannel output; // 第二步:引入消息的发送管道@Overridepublic String send(){String serial = UUID.randomUUID().toString();// 第三步:调用 send 方法发送消息this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息System.out.println("***serial: "+serial);return serial;}
}

        创建 controller 类用于远程调用发送消息,代码如下:

@RestController
public class SendMessageController
{@Resourceprivate IMessageProvider messageProvider;@GetMapping(value = "/sendMessage")public String sendMessage(){return messageProvider.send();}
}

3.2 测试

        启动 cloud-eureka-server7001rabbitmq 服务和 cloud-stream-rabbitmq-consumer8801 模块,在浏览器输入 http://localhost:8801/sendMessage 进行测试,多刷新几次浏览器。如下图:

        可以看到,消息成功的被 rabbitmq 接收了。

四、消息驱动之消费者

4.1 工程创建

        新建一个 cloud-stream-rabbitmq-consumer8802 模块用来充当消息的消费者,pom.xml 内容如下所示,只有一个依赖比较特殊。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://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"><modelVersion>4.0.0</modelVersion><parent><groupId>com.springcloud</groupId><artifactId>SpringCloud</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloud-stream-rabbitmq-consumer8802</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!--引入stream整合rabbit的依赖,如果是kafka,则也引入对于的依赖即可--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-rabbit</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--基础配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>

        application.yml 内容如下所示,比较特殊的是引入了 stream 的相关配置,binders 标签指定继承了哪种消息中间件并配置其连接地址、用户名和密码等。bindings 标签表示要对哪些服务进行整合,input 表示这是一个消息的消费者,destination 表示的是 rabbitmq 里面的交换机名称,binder 绑定的是上面指定的消息中间件。

server:port: 8802spring:application:name: cloud-stream-consumercloud:stream:binders: # 在此处配置要绑定的rabbitmq的服务信息;defaultRabbit: # 表示定义的名称,用于于binding整合type: rabbit # 消息组件类型environment: # 设置rabbitmq的相关的环境配置spring:rabbitmq:host: localhostport: 5672username: guestpassword: guestbindings: # 服务的整合处理input: # 这个名字是一个通道的名称destination: studyExchange # 表示要使用的Exchange名称定义content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”binder: defaultRabbit # 设置要绑定的消息服务的具体设置eureka:client: # 客户端进行Eureka注册的配置service-url:defaultZone: http://localhost:7001/eurekainstance: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地址

        主启动类的代码如下:

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

       创建 listenr 类用于监听和接收消息,代码如下:

@Component
// 第一步:可以理解为是一个消息的发送管道的定义,消息的生产者用 source,消息的消费者用 sink
@EnableBinding(Sink.class)
public class ReceiveMessageListener
{@Value("${server.port}")private String serverPort;// 第二步:使用 @StreamListener 注解进行消息的监听和接收@StreamListener(Sink.INPUT)public void input(Message<String> message){System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);}
}

4.2 测试

        接下来测试使用 8801 发送消息,看 8802 是否可以正常的接收消息,启动 cloud-eureka-server7001rabbitmq 服务和 cloud-stream-rabbitmq-consumer8801 cloud-stream-rabbitmq-consumer8802 模块, 在浏览器输入 http://localhost:8801/sendMessage 进行测试,

        可以看到 8081 发送的消息可以被 8082 正常的消费,如下图:

五、分组消费与持久化

5.1 工程创建

        参照 cloud-stream-rabbitmq-consumer8802 模块,我们重新创建一个消费者模块  cloud-stream-rabbitmq-consumer8803,可能有点不一样的就是 application.yml,我把它的内容粘出来,如下:

server:port: 8803spring:application:name: cloud-stream-consumercloud:stream:binders: # 在此处配置要绑定的rabbitmq的服务信息;defaultRabbit: # 表示定义的名称,用于于binding整合type: rabbit # 消息组件类型environment: # 设置rabbitmq的相关的环境配置spring:rabbitmq:host: localhostport: 5672username: guestpassword: guestbindings: # 服务的整合处理input: # 这个名字是一个通道的名称destination: studyExchange # 表示要使用的Exchange名称定义content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”binder: defaultRabbit # 设置要绑定的消息服务的具体设置eureka:client: # 客户端进行Eureka注册的配置service-url:defaultZone: http://localhost:7001/eurekainstance: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地址

5.2 测试

        启动 cloud-eureka-server7001rabbitmq 服务和 cloud-stream-rabbitmq-consumer8801cloud-stream-rabbitmq-consumer8802 cloud-stream-rabbitmq-consumer8803 模块, 在浏览器输入 http://localhost:8801/sendMessage 进行测试,

        可以看到 8081 发送的消息可以既被 8082 又被 8083 消费了,如下图:

5.3 重复消费问题

        目前是生产者发送消息,8802 8803 同时都收到了,存在重复消费问题。

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

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

5.4 分组

5.4.1 原理

        微服务应用放置于同一个 group 中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

5.4.2 不同组测试

        我们先把 8802 8803 都变成不同组进行测试,首先修改 8802 application.yml,添加一个 group 属性,如下图:

        修改 8803 application.yml,添加一个 group 属性,如下图:

        启动 cloud-eureka-server7001rabbitmq 服务和 cloud-stream-rabbitmq-consumer8801cloud-stream-rabbitmq-consumer8802 cloud-stream-rabbitmq-consumer8803 模块, 在浏览器输入 http://localhost:8801/sendMessage 进行测试,

        可以得出结论:不同组是可以全面消费的(重复消费)。

5.4.3 同组测试

        使用 8802 8803 进行同组进行测试,在 8802 8803 application.yml 中设置相同的 group 属性,都属于 tansunA 组,如下图:

        启动 cloud-eureka-server7001rabbitmq 服务和 cloud-stream-rabbitmq-consumer8801cloud-stream-rabbitmq-consumer8802 cloud-stream-rabbitmq-consumer8803 模块, 在浏览器输入 http://localhost:8801/sendMessage 进行测试,

        可以得出结论:同一个组的多个微服务实例,每次只会有一个拿到。

5.5 持久化

        停止 8802 8803 ,并将 8802 group 属性去除掉,然后让 8801 先发送 4 条消息到 rabbitmq

        先启动 8802,无分组属性配置,后台没有打出来消息,如下图:

        再启动 8803,有分组属性配置,后台打出来了 MQ 上的消息

        可以得出结论:只要你有分组的属性,你的数据就不会丢失。

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

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

相关文章

学习通刷视频刷题脚本及安装使用过程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装插件二、复制脚本文件链接三、启动脚本四、登录学习通&#xff08;切记一倍速就行不然被封哦&#xff09;五、最好先把答题关掉先刷视频 前言 解决学习…

AI技术崛起:数据可视化之路更近

在当今AI技术蓬勃发展的时代&#xff0c;数据可视化作为信息传达的重要手段&#xff0c;其门槛逐渐降低。然而&#xff0c;这并不意味着我们可以忽视学习数据可视化的重要性。即使不需要深入专业技术&#xff0c;对数据可视化的基础知识的了解也是至关重要的。那么&#xff0c;…

02-Java变量和运算符

1. 基本数据类型转换&#xff08;Conversion&#xff09; 在Java程序中&#xff0c;不同的基本数据类型的值经常需要进行相互转换。Java语言所提供的七种数值类型之间可以相互转换&#xff0c;基本数据类型转换有两种转换方式&#xff1a;自动类型转换和强制类型转换。boolean…

Dubbo 的配置总线:抓住 URL,就理解了半个 Dubbo

概述 在互联网领域&#xff0c;每个信息资源都有统一的且在网上唯一的地址&#xff0c;该地址就叫 URL&#xff08;Uniform Resource Locator&#xff0c;统一资源定位符&#xff09;&#xff0c;它是互联网的统一资源定位标志&#xff0c;也就是指网络地址。 URL 本质上就是…

【C++从练气到飞升】03---构造函数和析构函数

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 ⛳️推荐 一、类的6个默认成员函数 二、构造函数 1. 构造函数的概念 2. 构造函数的定义 3. 构造函数的特性 三、析构函…

MySQL MHA故障切换

目录 一、案例分析 1.1、案例概述 1.2、案例前置知识点 1&#xff09;什么是 MHA 2&#xff09;MHA 的组成 3&#xff09;MHA 的优势 4&#xff09;MHA 现状 1.3、案例环境 1&#xff09;本案例环境 ​编辑 2&#xff09;案例需求 3&#xff09;案例实现思路…

01——LenNet网络结构,图片识别

目录 1、model.py文件 &#xff08;预训练的模型&#xff09; 2、train.py文件&#xff08;会产生训练好的.th文件&#xff09; 3、predict.py文件&#xff08;预测文件&#xff09; 4、结果展示&#xff1a; 1、model.py文件 &#xff08;预训练的模型&#xff09; impor…

一道题学会如何使用哈希表

给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xff1a;nums [1,2,3], …

视频素材哪里有?这几个高清无水印素材库看看

关于短视频素材&#xff0c;我知道大家都在找那种能让人一看就心旷神怡的地方&#xff0c;尤其是我们自媒体人&#xff0c;更是离不开这些优质的短视频素材来吸引观众的眼球。别着急&#xff0c;今天我就给大家安利几个网站&#xff0c;保证让你找到满意的短视频素材。 1&…

黑马微服务p30踩坑

报错详情 : orderservice开不起来 : 发生报错 : 然后检查了以下端口啥的 &#xff0c;配置啥的都是没有问题的 ; 解决办法 : 1 . 修改nacos1,2,3中的端口&#xff0c;将conf 中 cluster.conf中 的 127.0.0.1 全部改成自己本机的真实ipv4地址; 本机真实ipv4地址查看 :…

C#求水仙花数

目录 1.何谓水仙花数 2.求三位数的水仙花数 3.在遍历中使用Math.DivRem方法再求水仙花数 1.何谓水仙花数 水仙花数&#xff08;Narcissistic number&#xff09;是指一个 n 位正整数&#xff0c;它的每个位上的数字的 n 次幂之和等于它本身。例如&#xff0c;153 是一个 3 …

C++进阶:详解多态(多态、虚函数、抽象类以及虚函数原理详解)

C进阶&#xff1a;详解多态&#xff08;多态、虚函数、抽象类以及虚函数原理详解&#xff09; 结束了继承的介绍&#xff1a;C进阶&#xff1a;详细讲解继承 那紧接着的肯定就是多态啦 文章目录 1.多态的概念2.多态的定义和实现2.1多态的构成条件2.2虚函数2.2.1虚函数的概念2…

Ant Design Pro complete版本的下载及运行

前言 complete 版本提供了很多基础、美观的页面和组件&#xff0c;对于前端不太熟练的小白十分友好&#xff0c;可以直接套用或者修改提供的代码完成自己的页面开发&#xff0c;简直不要太爽。故记录一些下载的步骤。 环境 E:\code>npm -v 9.8.1E:\code>node -v v18.1…

RabbitMQ学习总结-延迟消息

1.死信交换机 一致不被消费的信息/过期的信息/被标记nack/reject的信息&#xff0c;这些消息都可以进入死信交换机&#xff0c;但是首先要配置的有私信交换机。私信交换机可以再RabbitMQ的客户端上选定配置-dead-letter-exchange。 2.延迟消息 像我们买车票&#xff0c;外卖…

Python | 机器学习中的模型验证曲线

模型验证是数据科学项目的重要组成部分&#xff0c;因为我们希望选择一个不仅在训练数据集上表现良好&#xff0c;而且在测试数据集上具有良好准确性的模型。模型验证帮助我们找到一个具有低方差的模型。 什么是验证曲线 验证曲线是一种重要的诊断工具&#xff0c;它显示了机…

基于Java+SpringMVC+vue+element宠物管理系统设计实现

基于JavaSpringMVCvueelement宠物管理系统设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

计算机二级C语言的注意事项及相应真题-4-程序设计

目录 31.找出学生的最高分&#xff0c;由函数值返回32.计算并输出下列多项式的值33.将一个数字字符串转换成与其面值相同的长整型整数。可调用strlen函数求字符串的长度34.将字符串中的前导*号全部移到字符串的尾部。函数fun中给出的语句仅供参考35.将一组得分中&#xff0c;去…

算法---滑动窗口练习-7(串联所有单词的子串)

串联所有单词的子串 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;串联所有单词的子串 2. 讲解算法原理 算法的基本思想是使用滑动窗口来遍历字符串s&#xff0c;并利用两个哈希表&#xff08;hash1和hash2&#xff09;来统计窗口中子串的频次。 …

docker容器技术基础入门-1

文章目录 容器(Container)传统虚拟化与容器的区别Linux容器技术Linux NamespacesCGroupsLXCdocker基本概念docker工作方式docker容器编排 容器(Container) 容器是一种基础工具&#xff1b;泛指任何可以用于容纳其他物品的工具&#xff0c;可以部分或完全封闭&#xff0c;被用于…

#QT(定时轮播电子相册)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a; &#xff08;1&#xff09;使用QOBJECT的TIMER &#xff08;2&#xff09;EVENT时间 &#xff08;3&#xff09;多定时器定时溢出判断 &#xff08;4&#xff09;QLABEL填充图片 3.记录 4.代码 widget.h #ifndef WIDGET_H…