Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52

news2025/1/12 6:15:51

理论部分来自Seata官网:http://seata.io/zh-cn/docs/dev/mode/at-mode.html

一、前提

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

二、整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

三、写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
    以一个示例来说明:

两个全局事务 tx1tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000

  1. tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900

  2. 本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。

  3. tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800

  4. 本地事务提交前,尝试拿该记录的 全局锁tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁

在这里插入图片描述
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

在这里插入图片描述
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

3.1 脏写示例

假设你的业务代码是这样的:

  • updateAll()用来同时更新AB表记录,updateA()updateB()则分别更新AB表记录
  • updateAll()已经加上了@GlobalTransactional
@Service
class YourBussinessService {

	@Autowired
    DbServiceA serviceA;
    @Autowired
    DbServiceB serviceB;

    @GlobalTransactional
    public boolean updateAll(DTO dto) {
        serviceA.update(dto.getA());
        serviceB.update(dto.getB());
    }
    
    public boolean updateA(DTO dto) {
        serviceA.update(dto.getA());
    }
}
@Service
class DbServiceA {
    @Transactional
    public boolean update(A a) {
    
    }
}

在这里插入图片描述

3.2 使用@GlobalTransactional 防止脏写

updateA()也加上@GlobalTransactional

@Service
class DbServiceA {

    @GlobalTransactional
    @Transactional
    public boolean updateA(DTO dto) {
        serviceA.update(dto.getA());
    }
}
  • updateAll()先被调用(未完成),updateA()后被调用

在这里插入图片描述

异常信息:

  • 底层异常:io.seata.rm.datasource.exec.LockConflictException: get global lock fail
  • 上层异常:io.seata.rm.datasource.exec.LockWaitTimeoutException: Global lock wait timeout

3.3 使用@GlobalLock+select for update防止脏写

@Service
class DbServiceA {
    
    @GlobalLock
    @Transactional
    public boolean updateA(DTO dto) {
        serviceA.selectForUpdate(dto.getA());
        serviceA.update(dto.getA());
    }
}
  • updateAll()先被调用(未完成),updateA()后被调用

在这里插入图片描述

  • 那如果是updateA()先被调用(未完成),updateAll()后被调用呢?
    由于2个业务都是要先获得本地锁,因此同样不会发生脏写。

  • 单独用@GlobalLock能不能防止脏写? 能

  • 利用@GlobalLock+select for update方式中select for update能带来的好处?

    • 锁冲突更“温柔”些。如果只有@GlobalLock,检查到全局锁,则立刻抛出异常,也许再“坚持”那么一下,全局锁就释放了,抛出异常岂不可惜了。
    • updateA()中可以通过select for update获得最新的A,接着再做更新。

四、读隔离

目前数据库事务的隔离级别一共有 4 种,由低到高分别为:

  • Read uncommitted:读未提交
  • Read committed:读已提交
  • Repeatable read:可重复读
  • Serializable:序列化

在数据库本地事务隔离级别 读已提交Read Committed) 或以上的基础上,SeataAT 模式)的默认全局隔离级别是 读未提交Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

在这里插入图片描述

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句

4.1 脏读示例

假设你的业务代码是这样的:

  • updateAll()用来同时更新AB表记录,queryA()查询A记录,updateAll()未执行完成,另一业务后调用queryA()
  • updateAll()已经加上了@GlobalTransactional
@Service
class YourBussinessService {

	@Autowired
    DbServiceA serviceA;
    @Autowired
    DbServiceB serviceB;

    @GlobalTransactional
    public boolean updateAll(DTO dto) {
        serviceA.update(dto.getA());
        serviceB.update(dto.getB());
    }
    
    public boolean updateA(DTO dto) {
        serviceA.update(dto.getA());
    }
}
@Service
class DbServiceA {
    
    public A queryA(A a) {
    
    }
}

4.2 使用@GlobalLock+select for update防止脏读

queryA()也加上@GlobalLock并使用select for update语句

@Service
class DbServiceA {
    
    @GlobalLock
    public A queryA(A a) {
    
    }
}

在这里插入图片描述

结论:
读隔离:如果业务表的更新操存在于分布式事务中,此时本地方法中对业务表进行查询操作,建议在本地事务方法上使用@GlobalTransactional+select for update@GlobalLock+select for update注解防止出现数据脏读,优选@GlobalLock+select for update方式

注意事项:
使用select for update时需动态传入参数列表,不可使用拼接好的完整字符串查询语句,会导致获取lockKeys为空,引起脏读

五、源码解析

5.1 脉络

  • 代理数据源的用途
    • DataSourceProxy的作用(返回ConnectionProxy
      • 介绍 ConnectionProxy的功能:
        • 存放undolog
        • 判断inGlobalTransaction() or isGlobalLockRequire()
    • ConnectionProxy的作用(返回StatementProxy
    • StatementProxy.execute()的处理逻辑
      • io.seata.rm.datasource.exec.UpdateExecutor的执行逻辑(查前镜像、执行sql、查后镜像、准备undoLog
    • SelectForUpdateExecutor的执行逻辑(挣本地锁,查全局锁。有全局锁,回滚,再争…)
    • ConnectionProxy.commit()的处理逻辑(注册分支事务(争全局锁),写入undoLog,数据库提交)
  • 介绍RootContext
  • GlobalTransactionalInterceptor的不同代理逻辑
    • 带有@GlobalTransactional如何处理
    • 带有@GlobalLock如何处理

5.2 DataSourceProxy的作用

DataSourceProxy帮助我们获得几个重要的代理对象

在这里插入图片描述

  • 通过DataSourceProxy.getConnection()获得ConnectionProxy

在这里插入图片描述

  • ConnectionProxy中的ConnectionContext,它的有一个功能是存放undoLog

在这里插入图片描述

io.seata.rm.datasource.ConnectionProxy#appendUndoLog

在这里插入图片描述

io.seata.rm.datasourc.ConnectionContext#appendUndoItem

在这里插入图片描述 在这里插入图片描述

5.3 通过ConnectionProxy获得PreparedStatement

io.seata.rm.datasource.ConnectionProxy#ConnectionProxy

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

io.seata.rm.datasource.AbstractConnectionProxy.prepareStatement()获取StatementProxy

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.4 StatementProxy.execute()的处理逻辑

  • 当调用io.seata.rm.datasource.StatementProxy.execute()会将sql交给io.seata.rm.datasource.exec.ExecuteTemplate.execute()处理。

在这里插入图片描述

  • ExecuteTemplate.execute()方法中,Seata根据不同dbTypesql语句类型使用不同的Executer,调用io.seata.rm.datasource.exec.Executer类的execute()方法。

在这里插入图片描述
在这里插入图片描述

5.4.1 UpdateExecutor处理逻辑

io.seata.rm.datasource.exec.UpdateExecutor举例,UpdateExecutor extends AbstractDMLBaseExecutor extends BaseTransactionalExecutor。 观察execute()方法的具体操作

在这里插入图片描述

execute()方法继承至BaseTransactionalExecutor类中execute方法,内部调用受保护的抽象方法doExecute(实际调用子类AbstractDMLBaseExecutor中的doExecute实现)

io.seata.rm.datasource.exec.BaseTransactionalExecutor#execute

在这里插入图片描述

io.seata.rm.datasource.exec.AbstractDMLBaseExecutor#doExecute重写父类BaseTransactionalExecutordoExecute受保护抽象方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

io.seata.rm.datasource.exec.UpdateExecutor

在这里插入图片描述

如果是DML类型Executer,可以在上面的executeAutoCommitFalse()中看到,主要做了以下事情:

  1. 查询前镜像(select for update,因此此时获得本地锁)
    在这里插入图片描述
    在这里插入图片描述

  2. 执行业务sql

  3. 查询后镜像
    在这里插入图片描述

  4. 准备undoLog
    在这里插入图片描述

    使用到上节介绍的ConnectionProxy中的ConnectionContext,它的有一个功能是存放undoLog

5.4.2 SelectForUpdateExecutor的执行逻辑

如果你的sqlselect for update则会使用SelectForUpdateExecutorSeata代理了select for update),代理后处理的逻辑是这样的:

  • 先执行 select for update(获取数据库本地锁)

  • 如果处于@GlobalTransactional or @GlobalLock,检查是否有全局锁

    RootContext.inGlobalTransaction()RootContext.requireGlobalLock() 请见5.6章节介绍RootContext

  • 如果有全局锁,则未开启本地事务下会rollback本地事务,再重新争抢本地锁和查询全局锁,直到全局锁释放

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.5 ConnectionProxy.commit()的处理逻辑

io.seata.rm.datasource.ConnectionProxy#commit

在这里插入图片描述
在这里插入图片描述

  • 处于全局事务中(即,数据持久化方法带有@GlobalTransactional
    • 注册分支事务,获取全局锁
    • undoLog数据入库
    • 让数据库commit本次事务

io.seata.rm.datasource.ConnectionProxy#processGlobalTransactionCommit

在这里插入图片描述

io.seata.rm.datasource.ConnectionProxy#register

在这里插入图片描述

io.seata.rm.AbstractResourceManager#branchRegister

在这里插入图片描述

  • 处于@GlobalLock中(即,数据持久化方法带有@GlobalLock
    • tc查询是否有全局锁存在
    • 让数据库commit本次事务

在这里插入图片描述
在这里插入图片描述

  • 除了以上情况(else分支)

    • 让数据库commit本次事务

5.6 介绍RootContext

5.6.1 RootContext.getBranchType()的返回值怎么会是AT?

RootContext.getBranchType()调用来自于5.4章节**ExecuteTemplate.execute()方法

io.seata.core.context.RootContext#getBranchType

在这里插入图片描述在这里插入图片描述

io.seata.core.context.RootContext#inGlobalTransaction

在这里插入图片描述

方法RootContext.inGlobalTransaction()也被5.4.2章节SelectForUpdateExecutor.doExecute()方法调用

新的问题:哪里调用了RootContext.bind()方法?

io.seata.core.context.RootContext#bind

在这里插入图片描述

5.6.2 RootContext.requireGlobalLock()怎么判断当前是否需要全局锁?

RootContext.requireGlobalLock()调用来自于5.4章节ExecuteTemplate.execute()方法和5.4.2章节SelectForUpdateExecutor.doExecute()方法

io.seata.core.context.RootContext#requireGlobalLock

在这里插入图片描述

新的问题:哪里调用了RootContext.bindGlobalLockFlag()方法?

io.seata.core.context.RootContext#bindGlobalLockFlag

在这里插入图片描述

5.6.3 ConnectionProxy.commit()会根据context的不同状态区分处理,那ConnectionContext是如何判断inGlobalTransaction() or isGlobalLockRequire()的呢?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 如何判断inGlobalTransaction()?(注意下,这里和上面提到的RootContext不是一个东西)

io.seata.rm.datasource.ConnectionContext#inGlobalTransaction

在这里插入图片描述

哪里调用的ConnectionContext.bind(xid)?

  • 如何判断isGlobalLockRequire()

在这里插入图片描述

哪里调用的ConnectionContext.setGlobalLockRequire(xid)?

以上问题的答案都在下面:

io.seata.rm.datasource.exec.BaseTransactionalExecutor#execute

在这里插入图片描述

execute(Object... args)重点解析:

  • 读取RootContext.getXID()内容,通过statementProxy.getConnectionProxy().bind调用ConnectionContext.bind(xid)方法
  • 读取RootContext.requireGlobalLock()内容,通过statementProxy.getConnectionProxy().setGlobalLockRequire调用ConnectionContext.setGlobalLockRequire(isLock)方法
  • 执行doExecute方法

新的问题:RootContext.getXID()RootContext.requireGlobalLock()获取的值来自哪里?

  • RootContext.getXID() # 和@GlobalTransactional有关
  • RootContext.requireGlobalLock() # 和@GlobalLock有关

在看过代码后,我们知道,最后的问题回归到5.6.1章节5.6.2章节提出两个问题:

  • RootContext.bind()
  • RootContext.bindGlobalLockFlag()

在哪儿被调用的呢?答案就在下方。

5.8 GlobalTransactionalInterceptor处理带有@GlobalTransactional或@GlobalLock的方法

带有@GlobalTransactional@GlobalLock的方法会被代理,交给GlobalTransactionalInterceptor处理

io.seata.spring.annotation.GlobalTransactionalInterceptor#invoke

在这里插入图片描述
在这里插入图片描述

5.8.1 @GlobalTransactional处理逻辑

io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

来到了经典的seata事务模板方法,我们要关注开启事务的部分:

io.seata.tm.api.TransactionalTemplate#execute

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

io.seata.tm.api.TransactionalTemplate#beginTransaction

在这里插入图片描述

io.seata.tm.api.TransactionalTemplate#completeTransactionAfterThrowing#commitTransaction

在这里插入图片描述

io.seata.tm.api.DefaultGlobalTransaction#begin

在这里插入图片描述

看到了吗?RootContext.bind(xid);

io.seata.tm.api.DefaultGlobalTransaction#commit

在这里插入图片描述

io.seata.tm.api.DefaultGlobalTransaction#rollback

在这里插入图片描述

5.8.2 @GlobalLock处理逻辑

io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalLock

在这里插入图片描述

也使用了模板方法来处理GlobalLock

io.seata.rm.GlobalLockTemplate#execute

在这里插入图片描述

看到吗,一进模板方法就RootContext.bindGlobalLockFlag();

5.9 @GlobalLock 源码解析

io.seata.spring.annotation.GlobalLock

/**
 * declare the transaction only execute in single local RM
 * but the transaction need to ensure records to update(or select for update) is not in global transaction middle
 * stage
 *
 * use this annotation instead of GlobalTransaction in the situation mentioned above will help performance.
 *
 * @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, and TCC mode
 * @see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalLock(MethodInvocation, GlobalLock)  // the interceptor of GlobalLock
 * @see io.seata.spring.annotation.datasource.SeataAutoDataSourceProxyAdvice#invoke(MethodInvocation) // the interceptor of GlobalLockLogic and AT/XA mode
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Inherited
public @interface GlobalLock {
    /**
     * customized global lock retry interval(unit: ms)
     * you may use this to override global config of "client.rm.lock.retryInterval"
     * note: 0 or negative number will take no effect(which mean fall back to global config)
     * @return lock retry interval
     */
    int lockRetryInterval() default 0;

    /**
     * customized global lock retry interval(unit: ms)
     * you may use this to override global config of "client.rm.lock.retryInterval"
     * note: 0 or negative number will take no effect(which mean fall back to global config)
     * @return lock retry interval
     */
    @Deprecated
    @AliasFor("lockRetryInterval")
    int lockRetryInternal() default 0;

    /**
     * customized global lock retry times
     * you may use this to override global config of "client.rm.lock.retryTimes"
     * note: negative number will take no effect(which mean fall back to global config)
     * @return lock retry times
     */
    int lockRetryTimes() default -1;

}

源码注释大概含义:

  • 对于某条数据,如果正在 全局事务 中进行更新(或者选择更新)操作,这时某个本地事务需要更新该数据,需要在本地事务方法上使用@GlobalLock注解,确保其不会对全局事务中正在操作的数据造成影响(防止出现脏写)。
  • 声明事务仅在单个本地RM中执行
  • 使用@GlobalLock注解而不是@GlobalTransaction将有助于提高性能
  • 属性值lockRetryInterval覆盖全局配置client.rm.lock.retryInterval校验或占用全局锁重试间隔
  • 属性值lockRetryTimes覆盖全局配置client.rm.lock.retryTimes校验或占用全局锁重试次数`

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

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

相关文章

算法设计与分析:数理基础与串匹配程序设计

目录 前言实验内容实验流程实验过程实验分析伪代码代码实现分析算法复杂度用例测试 总结 前言 本实验是算法设计与分析课程的一个实验,旨在帮助掌握数理基础和串匹配算法的相关知识,以及如何用C语言实现串匹配程序。本实验分为两个部分:第一…

【Leetcode -643.子数组最大平均值Ⅰ -645.错误的集合】

Leetcode Leetcode -643.子数组最大平均值ⅠLeetcode -645.错误的集合 Leetcode -643.子数组最大平均值Ⅰ 题目:给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。 请你找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。 任何误差小…

Buildroot 切换到国内源

可以在make menuconfig的界面里的Build options–>Mirrors and Download locations中的几个地址依次填入下面几个国内的加速镜像源url地址,速度可以快非常多!! BACKUP_SITE"http://sources.buildroot.net" KERNEL_MIRROR"…

linux消息队列总结

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID) 来标识 1、特点 (1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级 (2)消息队列独立于发送与接收…

chatgpt赋能Python-python_pendown

Python PenDown: 一个简单易用的MarkDown编辑器 在现代化的互联网环境下,搜索引擎优化已经成为了每个网站都必须要面对的问题,而一个网站的SEO质量往往与网站的内容相关。而MarkDown则是现代网络环境下非常受欢迎的一种文本标记语言,因为其语…

数据仓库漫谈-前世今生

数据仓库的内容非常多,每一个子模块拎出来都能讲很久。这里没法讲太多细节,大致思考了三个备选议题: 数据仓库的前世今生 数据仓库体系知识介绍 数仓开发者的路在何方? 既然是第一次分享,感觉还是跟大家普及下数仓的…

百度营销:百度扩量投放技巧

众所周知百度是国内大部分用户都在使用的搜索引擎。百度搜索投放的是关键词形式。今天将带来一些账户优化的建议。放量模式共享预算有哪些投放细节呢?以下梳理了5个小技巧: 1.适合的账户类型 更适合预算充足的广告主。如果当前,你每天的获客…

shell编程快捷命令

shell编程快捷命令 一、快捷排序 — sort 命令二、快捷去重 — uniq 命令三、快捷替换 — tr 命令四、快速裁剪 — cut 命令五、文件拆分 — split 命令七、变量扫描器 — eval 命令 一、快捷排序 — sort 命令 sort命令用于以行为单位,对文件的内容进行排序 语法格…

【轻量化网络系列(3)】MobileNetV3论文超详细解读(翻译 +学习笔记+代码实现)

前言 上周我们学习了MobileNetV1和MobileNetV2,本文的MobileNetV3,它首先引入MobileNetV1的深度可分离卷积,然后引入MobileNetV2的具有线性瓶颈的倒残差结构,后来使用了网络搜索算法,并引入了SE模块以及H-Swish激活函…

Vue监视属性

1&#xff0c;click事件的属性可以些什么&#xff1f; 答&#xff1a;click即click"xxx"&#xff0c;其中xxx可以是一个methods方法&#xff0c;也可以是一些简单的语句&#xff0c;比如i&#xff0c;i<0&#xff1f;250 : 520。即click"add&#xff1b;i&am…

【数据结构】---堆排序:时间复杂度高于(N*logN)的排序别来沾边

文章目录 前言&#x1f31f;一、建堆的两种方式&#xff1a;&#x1f30f;1.1 向上调整建堆(堆排序)&#xff1a;&#x1f4ab;1.1.1 完整代码&#xff1a;&#x1f4ab;1.1.2 流程图(以小堆为例)&#xff1a;升序&#xff1a;建大堆&#x1f4ab;1.1.3 流程图(以小堆为例)&…

数码港元≠港元稳定币,为何被视为法币与虚拟资产间的骨干和支柱

出品&#xff5c;欧科云链研究院 作者&#xff5c;Jason Jiang 临近6月&#xff0c;香港在虚拟资产与Web3领域愈加活跃。据彭博社报道&#xff0c;香港将宣布散户投资者可以根据其新的行业规则交易加密货币&#xff0c;预计个人投资者从6月开始在适当的保障措施下可以交易BTC…

Go Web下gin框架的模板渲染

〇、前言 Gin框架是一个用于构建Web应用程序的轻量级Web框架&#xff0c;使用Go语言开发。它具有高性能、低内存占用和快速路由匹配的特点&#xff0c;旨在提供简单、快速的方式来开发可扩展的Web应用程序。 Gin框架的设计目标是保持简单和易于使用&#xff0c;同时提供足够的…

利用ChatGPT来学习Power BI

学习Power BI&#xff0c;或者说学习微软的相关产品的时候&#xff0c;最讨厌的就是阅读微软的官方文档&#xff0c;写的真的太硬了&#xff0c;有时候实时是啃不动&#xff0c;只能说不愧是巨硬。 但是&#xff0c;我们现在有AI帮忙了啊&#xff0c;ChatGPT3都通过了谷歌L3工…

达利欧《原则》拆书笔记(二)

什么是原则&#xff1f; 原则是应对现实、实现你人生愿望的方法。 假如没有原则&#xff0c;我们将被迫逐一考虑多种类型的事情&#xff0c;主动去应对&#xff0c;就像第一次经历这些事。相反&#xff0c;假如我们把每件事都看作“同一类型事物的又一个表现”&#xff0c;以…

【SpringCloud组件——Feign(远程调用)】

前言&#xff1a; 我们在使用Nacos和Eureka的时候都需要使用远程调用开关RestTemplate发送http请求&#xff0c;但是这种方式在代码编写层面太不优雅了&#xff0c;因此我们可以采用Feign来代替RestTemplate发送http请求。 注&#xff1a;此小节同样使用订单系统和用户系统作…

VXLAN技术了解

VXLAN是使用隧道技术的封装协议&#xff0c;常用于在物理层之上创建overlay网络&#xff0c;赋能虚拟网络。同时支持数据中心网络的虚拟化&#xff0c;并通过提供必要的分段满足多租户的需求。 优势在于 可伸缩性和灵活性&#xff1a;理论上可以使用1600万xlans&#xff0c;但…

读书笔记——《when breath becomes air》《超越自卑》

为啥要两本书一起写读后感&#xff1f; 读完这两本书本来应该分开来写点东西的&#xff0c;不过我认为这两本书应该写不了太多内容。虽然我也看了几本英文原著&#xff08;也写了点东西&#xff09;&#xff0c;但是我明显低估了《when breath becomes air》的难度&#xff0c…

SpringBoot学习之集成JWT(二十八)

一、什么是JWT WT (全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。 比如我们常见的登录流程如下: 流程描述一下: 用户使用账号、密码登…

机器学习-3 K最近邻算法

K最近邻算法 算法概述分类什么是分类&#xff1f;分类需要什么&#xff1f; k近邻&#xff08;KNN&#xff09;分类 KNN算法关键问题k近邻模型的两个关键问题相似性度量——欧氏距离K值的选取 KNN算法流程算法原理算法步骤 数据标准化离差标准化数据标准差标准化数据小数定标标…