【分布式事务之spring实践】分布式事务选型实战

news2024/11/13 12:34:52

Spring中使用事务

Spring是一个伟大的框架,从一开始只是一个容器框架,到现在已经发展成为了一个包含企业开发中的方方面面的很多框架的总称。它不但从复杂度上,发展出了用于各个方面的子框架。它还从易用性出发,推出了像Spring-Boot这样的框架,使得搭建环境变得异常的简单。

很早之前Spring就已经有了一套自己的事务规范。(在org.springframework.transaction包中),而且用起来也非常的简单:

@Service

public Class OrderService {

@Transactional

public TicketOrder buyTicket(OrderDTO orderDTO) {

TicketOrder tkOrder = new TicketOrder();

jdbcTemplate.execute(createOrderSQL);

return tkOrder;

}

}

我们只需要在方法上加一个 Transactional 标签,那个这个方法就会在一个事务里面执行。这是用代理模式实现的。Spring 容器在初始化这个 service 实例的时候,实际上是创建一个代理类,然后在调用这个方法的时候,包装一个事务的处理。上面的方式使用代理模式展开,大致如下:

public Class OrderServiceProxy {

// 通过代理实现的伪代码,在原先的代码外再包一层事务的创建、commit、rollback

public Order buyTicket(OrderDTO orderDTO) {

// get transaction from entityManager

theTransaction.begin();

try {

orderServiceImpl.buyTicket(orderDTO)

theTransaction.commit();

} catch(Exception e) {

theTransaction.rollback();

throw e;

}

}

}

从这个流程可以看出,在更改数据的时候 jdbcTemplate.save (order),事务并没有提交,用户查看最新的数据的时候,也看不到这条数据(隔离性),只有 commit 以后,所有的数据修改才会同时起效(原子性)。如果期间发生任何错误,事务就会回退 rollback,所有的数据修改又回到未修改状态。

Spring 的事务抽象

由于历史原因,从很早之前,Spring 就已经有了一套自己的事务规范(在 org.springframework.transaction 包中)。但是针对 JTA 规范,Spring 也做了很多工作,使得我们在实现事务的时候不需要关心具体用的是哪一个。但是,这也有一些问题:
第一个问题就是,很多做 Java 开发的人,都不知道 JTA 和 Spring Transaction 的区别。
第二,这两种规范在某些地方还是有些区别,使用不当也会出现问题。
我们在上面介绍本地事务的时候说,使用 Spring 框架的标签 @Transactional,来方便的实现事务,但是需要说明的是,这个标签类,在 Spring 的事务以及 JavaEE 事务规范中都有定义,分别是:

  • org.springframework.transaction.annotation.Transactional
  • javax.transaction.Transactional

我们在使用的时候就需要注意,如果你用 Spring boot,那么大部分情况下这两种标签都能使用。spring boot 提供了很多自动配置,能够根据你是否包含了 JTA 的依赖,来判断是否要使用 JTA 的事务。如果没有,即使你用的 javax.transaction.Transactional 标签,也会使用 spring 的事务机制来处理。

Spring 之所以能够实现对两种事务的支持,是因为在 spring 的 Transaction 规范中,定义了一个统一的 PlatformTransactionManager 事务管理器。即使你没有使用某个 JPA 框架,而是直接用 JDBTemplate,Spring 也能够使用默认的 DataSourceTransactionManager 来使用 JDBC 的事务来实现事务。而且也可以直接通过标签 org.springframework.transaction.annotation.Transactional 来实现事务。也就是说,你不需要任何 JPA 的实现框架,只是使用 Spring-Transaction 就能实现数据库的事务。

Spring 的 PlatformTransactionManager,也有 JTA 的实现 JtaTransactionManager。也就是说,你可以使用 Spring 的事务规范,却使用 JTA 的实现,而且也几乎不需要任何配置,只要在具体的运行环境中包含包含 JTA 的实现可以。比如你用 JBoss 的应用服务器,系统就会使用 Jboss 的 JTA 实现;如果你的 class path 里面有 Atomikos 的库,系统就会使用 Atomikos 的 JTA 实现。如果你使用 spring-boot,你只需要在你的依赖里面、或运行环境里面,提供你所需要的 JTA 实现,它就会自动使用。

除了数据库,spring 的事务还支持 JMS 的事务,也就是在通过 JMS 使用某个消息中间件时,也能用 spring 的事务来实现读写消息的事务。

再总结一下 Spring 的事务抽象,它定义了抽象的事务管理,可以管理任何支持事务操作 (也就是 commit 和 rollback) 的资源,如:

  • JDBC Connection: JDBC 的连接支持 commit () 和 rollback () 操作。。
  • JPA 的 Entity: 从 JPA 的 EntityManager 中获得 EntityTransaction,通过它实现。
  • JMS 的 session:jms 的 session 提供 commit () 和 rollback () 操作。
  • JTA 的事务:JTA 的事务当然提供了事务的操作。

在微服务情况下,我们经常会遇到使用多个微服务,调用而且需要保证事务一致性的情况。
Spring 中 @Transactional ,默认只对抛出的 RuntimeException 的出常,事务才会回滚。

如果希望无论抛出是 RuntimeException ,还是 Exception,事务都要回滚,请使用如下配置。

@Transactional(rollbackFor={RuntimeException.class, Exception.class})

Transactional为什么不能保证微服务条件下事务?

但是如果发起方挂了,或者网络挂了事务讲无法保证

  @Transactional
    public TicketOrder buyTicket(OrderDTO orderDTO) {
        orderRepository.save(order);
        
        String res = restTemplate.updatexxxx(order);
        if (res!="200") {
            throw new RuntimeException("xxx");
        }
        return tkOrder;
    }

我们根据updatexxxx的返回值来决定是否抛出异常,如果有异常就回滚。

这种在发起发方正常运作的时候,是可行过是吧的。但是如果发起方挂了或者发起方事务不成功怎么办?,会出现事务不一致的情况,意味着我们需要回滚被调用侧。这意味着我们代码中将需要同时处理两者的事务,并且需要被调用放提供回滚接口。
所以出现了XA规范,把事务管理器给独立出去。
单仔细想下,我们其实可以在本地拿一张表记下本地的事务提交记录,如果提交成功,我们删除这条记录,如果没有提交成功,我们要要调用被调用方的回滚接口。
在Java中,使用了java.sql.Connection实例来表示和数据库的一个连接,通信的方式目前基本上采用的是TCP/IP 连接方式。通过对Connection进行一系列的事务控制。

请添加图片描述

MVCC(Multi-Version Concurrency Control)

来复习一下mysql是如何实现事务的?MVCC(Multi-Version Concurrency Control)多版本并发控制,是数据库控制并发访问的一种手段。
1. 版本链
版本链是一条链表,链接的是每条数据曾经的修改记录
那么这个版本链又是如何形成的呢,每条数据又是靠什么链接起来的呢?

其实是这样的,对于InnoDB存储引擎的表来说,它的聚簇索引记录包含两个隐藏字段

  • trx_id: 存储修改此数据的事务id,只有这个事务操作了某些表的数据后当更改操作发生的时候(update,delete,insert),才会分配唯一的事务id,并且此事务id是递增的

  • roll_pointer: 指针,指向上一次修改的记录

  • row_id(非必须): 当有主键或者有不允许为null的unique键时,不包含此字段
    假如说当前数据库有一条这样的数据,假设是事务ID为100的事务插入的这条数据,那么此条数据的结构如下
    请添加图片描述
    后来,事务200,事务300,分别来修改此数据
    所以此时的版本链如下
    请添加图片描述
    我们每更改一次数据,就会插入一条undo日志,并且记录的roll_pointer指针会指向上一条记录,如图所示
    1、第一条数据是小杰,事务ID为100
    2、事务ID为200的事务将名称从小杰改为了A
    3、事务ID为200的事务将名称从A又改为了B
    4、事务ID为300的事务将名称从B又改为了C
    所以串成的链表就是 C -> B -> A -> 小杰 (从最新的数据到最老的数据)
    2. 一致性视图(ReadView)
    需要判断版本链中的哪个版本是是当前事务可见的,因此有了一致性视图的概念。其中有四个属性比较重要

  • m_ids: 在生成ReadView时,当前活跃的读写事务的事务id列表

  • min_trx_id: m_ids的最小值

  • max_trx_id: m_ids的最大值+1

  • creator_trx_id: 生成该事务的事务id,单纯开启事务是没有事务id的,默认为0,creator_trx_id是0。
    版本链中的当前版本是否可以被当前事务可见的要根据这四个属性按照以下几种情况来判断

  • 当 trx_id = creator_trx_id 时:当前事务可以看见自己所修改的数据, 可见,

  • 当 trx_id < min_trx_id 时 : 生成此数据的事务已经在生成readView前提交了, 可见

  • 当 trx_id >= max_trx_id 时 :表明生成该数据的事务是在生成ReadView后才开启的, 不可见

  • 当 min_trx_id <= trx_id < max_trx_id 时

  • trx_id 在 m_ids 列表里面 :生成ReadView时,活跃事务还未提交,不可见

  • trx_id 不在 m_ids 列表里面 :事务在生成readView前已经提交了,可见
    如果某个版本数据对当前事务不可见,那么则要顺着版本链继续向前寻找下个版本,继续这样判断,以此类推。

注:RR和RC生成一致性视图的时机不一样 (这也是两种隔离级别实现的主要区别)

读提交(read committed RC) 是在每一次select的时候生成ReadView的 可重复读(repeatable read
RR)是在第一次select的时候生成ReadView的

那么我们知道了mysql数据库是根据事务id,只有知道事务id,才可以找到undolog实现,而事务id是被数据库连接管理的,数据库连接又是关联到我们应用线程的。 分布式环境中,我们只要知道一个发起方的全局事务id,然后在上下文调用链中传递,每在被调用中开启一个事务,就绑定一个分支事务id,一个全局事务可能包含多个事务分支,那么我们就能追踪所有的分布式事务,并控制其状态。

两阶段提交协议

阿里的seata框架很好地完成了这一工作
官网:https://seata.io/zh-cn/index.html
阿里GTS:https://help.aliyun.com/document_detail/157850.html
在这里插入图片描述
分布式事务包含以下 3 个核心组件:

  • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

一个典型的事务过程包括:

1、TM 向 TC 申请开启(Begin)一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
2、XID 在微服务调用链路的上下文中传播。
3、RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
4、TM 向 TC 发起针对 XID 的全局提交(Commit)或回滚(Rollback)决议。
5、TC 调度 XID 下管辖的全部分支事务完成提交(Commit)或回滚(Rollback)请求。

事务框架

基于架构上定义的 3 个核心组件,分布式事务被抽象成如下事务框架。

在这里插入图片描述
3个核心组件的功能如下:

  • TM定义全局事务的边界。
  • RM负责定义分支事务的边界和行为。
  • TC、TM和RM交互,做全局的协调。交互包括开启(Begin)、提交(Commit)、回滚(Rollback)全局事务;分支注册(Register Branch)、状态上报(Branch Status Report)和分支提交(Branch Commit)、分支回滚(Branch Rollback)。

事务模式

事务模式是这个框架下 RM 驱动的分支事务的不同行为模式,即事务(分支)模式。事务模式包括 AT 模式、TCC 模式、Saga 模式和 XA 模式。

AT 模式 RM 驱动分支事务的行为分为以下两个阶段:

  • 执行阶段:
    1、代理 JDBC 数据源,解析业务 SQL,生成更新前后的镜像数据,形成 UNDO LOG。
    2、向 TC 注册分支。
    1、分支注册成功后,把业务数据的更新和 UNDO LOG 放在同一个本地事务中提交。
  • 完成阶段:
    1、全局提交,收到 TC 的分支提交请求,异步删除相应分支的 UNDO LOG。
    2、全局回滚,收到 TC 的分支回滚请求,查询分支对应的 UNDO LOG 记录,生成补偿回滚的 SQL 语句,执行分支回滚并返回结果给 TC。

TCC模式

在这里插入图片描述
TCC 模式 RM 驱动分支事务的行为分为以下两个阶段:

  • 执行阶段:
    1、向 TC 注册分支。
    2、执行业务定义的 Try 方法。
    3、向 TC 上报 Try 方法执行情况:成功或失败。

  • 完成阶段:
    1、全局提交,收到 TC 的分支提交请求,执行业务定义的 Confirm 方法。
    2、全局回滚,收到 TC 的分支回滚请求,执行业务定义的 Cancel 方法。
    在这里插入图片描述
    Saga 模式 RM 驱动分支事务的行为包含以下两个阶段:

  • 执行阶段:
    1、向 TC 注册分支。
    2、执行业务方法。
    3、 向 TC 上报业务方法执行情况:成功或失败。

  • 完成阶段:
    1、全局提交,RM 不需要处理。
    2、全局回滚,收到 TC 的分支回滚请求,执行业务定义的补偿回滚方法。

XA 模式

在这里插入图片描述
XA 模式 RM 驱动分支事务的行为包含以下两个阶段:

  • 执行阶段:
    1、向 TC 注册分支。
    2、XA Start,执行业务 SQL,XA End。
    3、XA prepare,并向 TC 上报 XA 分支的执行情况:成功或失败。
  • 完成阶段:
    1、收到 TC 的分支提交请求,XA Commit。
    2、收到 TC 的分支回滚请求,XA Rollback。

当然还有其它很多分布式框架如Atomikos,是分布式刚性事务的一种解决方案,但是不推荐。通常情况下使用2pc提交方案的场景都是单服务多数据源(多数据库)的情况。大名dingding的分库分表中间件ShardingSphere 也集成了 SEATA作为分布式事务解决方案

以上的都是基于同步机制,对业务都有一定侵入,并且使用rpc或者是rest进行通信,这种性能有限。所以出现了利用消息中间件来处理分布式事务的方式:通知型事务,我们只需要保证消息的投递是事务的即可。通知型事务分:MQ事务消息、最大努力通知型。

事务消息

异步确保型事务:主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,如订单和购物车、收货与清算、支付与结算等等场景;

最大努力通知:主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接等等跨网络系统级别对接;
  (1)本地消息表
    这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。它和MQ事务消息的实现思路都是一样的,都是利用MQ通知不同的服务实现事务的操作。不同的是,针对消息队列的信任情况,分成了两种不同的实现。本地消息表它是对消息队列的稳定性处于不信任的态度,认为消息可能会出现丢失,或者消息队列的运行网络会出现阻塞,于是在数据库中建立一张独立的表,用于存放事务执行的状态,配合消息队列实现事务的控制。
  (2)MQ事务消息
   有一些第三方的MQ是支持事务消息的,比如RocketMQ,ActiveMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。 以RocketMQ中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务。
第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了 RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

RocketMQ事务消息的执行流程图
在这里插入图片描述

总结:

seata是实现分布式事务的不错选择。一般需要单独部署服务,并且保证其高可用。
官网:https://seata.io/zh-cn/index.html
https://seata.io/zh-cn/docs/overview/what-is-seata.html
源码:https://github.com/seata/seata
Demo:https://github.com/seata/seata-samples

(1)TCC方案
    特点:严格一致性 执行时间短 实时性要求高
    适用场景:抢红包 实时转账汇款 收付款
(2)异步确保方案
    特点:实时性不高 执行周期较长
    适用场景:非实时汇款 退货退款业务
(3)最大努力通知方案
    特点:高并发低耦合 不支持回滚
    适用场景:获取交易结果(例如:共享单车支付等)
对于事务要求不高的,也可使用消息方式。
参考:
https://www.cnblogs.com/huyangshu-fs/p/15600093.html
https://mp.weixin.qq.com/s/nROY4rFH8SgnP2kYkuww8g
https://blog.csdn.net/flyzing/article/details/116493720
https://my.oschina.net/u/133911/blog/1563796
https://www.likecs.com/show-305819252.html
https://seata.io/zh-cn/index.html

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

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

相关文章

多旅行商问题:世界杯优化算法(World Cup Optimization,WCO)求解多仓库多旅行商问题(提供Matlab代码)

一、世界杯优化算法 世界杯优化算法&#xff08;World Cup Optimization&#xff0c;WCO)由Navid Razmjooy等人于2016年提出&#xff0c;该算法模拟了国际足联世界杯比赛&#xff0c;思路新颖&#xff0c;收敛速度快&#xff0c;全局寻优能力强。 算法原理参考&#xff1a;智…

[附源码]Node.js计算机毕业设计河南美丽乡村旅游信息网Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

[附源码]Nodejs计算机毕业设计基于远程协作的汽车故障诊断系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

DevOps实战系列【第十四章-完结篇】:Jenkins Pipeline最佳实践案例

个人亲自录制全套DevOps系列实战教程 &#xff1a;手把手教你玩转DevOps全栈技术 我们将之前的案例&#xff0c;使用流水线构建一下 搭建一个Jenkinsfile模型 pipeline {agent anystages {stage(拉取gitlab项目代码) {steps {echo "拉取git代码"}}stage(构建代码) {…

FRP进阶篇之解决方案

目录 一、前言 二、多客户端使用 1、场景 2、解决方案 3、使用 3.1、服务端配置 3.2、客户端配置 3.3、结果验证 三、dashboard 仪表盘使用 1、场景 2、解决方案 3、使用 4、验证 5、使用Java调用API接口获取设备列表 5.1、样例代码 5.2、结果验证 一、前言 通…

27. SAP OData 框架里的缓存(Cache)设计专题讲座

本教程前一篇文章,25. 答疑 - SAP OData 框架处理 Metadata 元数据请求的实现细节,前后端组件部署在同一台物理服务器,我们介绍了 SAP OData 框架处理 metadata 请求的流程,如下图所示: 其中左边的分支,当读取本地服务器的 metadata 时,OData 框架会从 Share Memory 即共…

npm-开发自己的包并发布

目录 1.开发自己的包 1.1. 需要实现的功能 1.2. 初始化包的基本结构 1.3. 初始化 package.json 1.4. 在 index.js 中定义格式化时间的方法 1.5. 在 定义转义 和还原HTML 的方法 1.6. 编写包的说明文档 1.7包的入口文件 2.发布自己的包 2.1注册npm账号 2.2登录npm账…

Jmeter(二十):jmeter对图片验证码的处理

jmeter对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入&#xff0c;而且每次登录时图片验证码都是随机的&#xff1b;当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段&#xff0c;然后再登录接口中使用&#xff1b; 通过jmeter对图片验证码…

Qt下载安装及配置教程

进入qt中文网站&#xff1a;https://www.qt.io/zh-cn/ 下载开源版 往下滑&#xff0c;下载Qt在线安装程序 它已经检测出我的是windows系统&#xff0c;直接点击download就好。如果是其它的系统&#xff0c;需要找到对应自己系统的安装包。 然后跟网速有关&#xff0c;等…

计算机毕业设计springboot+vue大学生个人财务管理系统

项目介绍 本大学生个人财务管理系统设计目标是实现收入和支出的信息化管理,提高管理效率,使得支出收入管理工作规范化、科学化、高效化。 本文研究的大学生个人财务管理系统基于springboot架构,采用JSP技术、JAVA编程语言和MYSQL数据库设计开发。通过本系统,实现了管理员和用户…

设计模式之享元模式

Flyweight design pattern 享元模式的概念、享元模式的结构、享元模式的优缺点、享元模式的使用场景、享元模式的实现示例、享元模式的源码分析 1、享元模式的概念 享元模式&#xff0c;即运用共享技术来有效的支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少…

基于nodejs博客系统的设计与实现.rar(论文+源码+ppt文档+视频录制)

第一章绪论 3 1.1项目开发的背景和意义 3 1.2国内外研究的现状 4 1.3研究的主要内容 4 第2章系统相关技术介绍 5 2.1 B/S结构技术介绍 5 2.2nodejs技术介绍 6 2.3mysql数据库 7 第三章系统分析 8 3.1可行性分析 8 3.2功能需求分析 8 3.2.1登录模块需求分析 9 3.2.2分类博客模块…

NetSuite 如何统一用户的时区

在NetSuie的原始设置中&#xff0c;用户可以设置自己的时区&#xff0c;这在单一国家的环境中实际上是个缺点。例如&#xff0c;有些客户并没有注意到自己的时区是否是本国时区&#xff0c;所以在查看系统日志时&#xff0c;发现时间不对头&#xff0c;产生了困扰。所以&#x…

操作系统,计算机网络,数据库刷题笔记13

操作系统&#xff0c;计算机网络&#xff0c;数据库刷题笔记13 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xf…

SpringBoot3.0自定义stater整合chatGPT49种应用场景代码已开源

导读 导读 | 12月总体来说互联网的技术圈是非常热闹的&#xff0c;chatGPT爆火&#xff0c;SpringBoot3.0发布等重磅陆消息续进入大家的视线&#xff0c;而本文作者将以技术整合的角度&#xff0c;带大家把最火的两个技术整合在一起。读完本文&#xff0c;你将熟悉SpringBoot3…

我的java学习

犹豫了很久&#xff0c;还是打算简述一下自己学java 的历程&#xff0c;花了些时间&#xff0c;但结果不赖 文章目录从0的开始开始加速过度项目学习懵懂的进入项目进入综合项目学习第二次学习开始从0的开始 没有计算机基础&#xff0c;英语也非常糟糕。 起初也时常在想&#x…

Java+MySQL基于SSM的高校科研仪器共享平台

随着在校学生人数的不断增加,学生的数量也在不断的增加,同时面临的就是如何更加方便快捷和高效的管理高校科研仪器的问题,传统模式的科研仪器管理明显已经不能够满足当下的需求,于是我们提出了高校科研仪器共享平台的设计与开发。 本课题是一个基于SSM的管理系统,本高校科研仪器…

架构设计(六):引入消息队列

架构设计&#xff08;六&#xff09;&#xff1a;引入消息队列 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;架构设计&#xff08;六&#xff09;&#xff1a;引入消息队列 CSDN&#xff1a;架构设计&#xff08;六&#xff09;&#xff1a;引入消息队列…

【LeetCode】1703. 得到连续 K 个 1 的最少相邻交换次数

题目描述 给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 。每一次移动&#xff0c;你可以选择 相邻 两个数字并将它们交换。 请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,0,0,1,0,1], k 2 输出&#xf…

入门:镜像结构介绍

前面我们了解了Docker的相关基本操作&#xff0c;实际上容器的基石就是镜像&#xff0c;有了镜像才能创建对应的容器实例&#xff0c;那么我们就先从镜像的基本结构开始说起&#xff0c;我们来看看镜像到底是个什么样的存在。 我们在打包项目时&#xff0c;实际上往往需要一个基…