Spring中的事务&分布式事务

事务的基本概念

1、事务是一系列的动作,一旦其中有一个动作出现错误,必须全部回滚,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态,避免出现由于数据不一致而导致的接下来一系列的错误。事务的出现是为了确保数据的完整性和一致性。

2、事务有四大特性

  • 原子性

要么全部完成,要么完全不起作用。

  • 一致性

事务在完成时,必须是所有的数据都保持一致状态。

  • 隔离性

并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。

  • 持久性

一旦事务完成,数据库的改变必须是持久化的。

3、事务并发所可能存在的问题

  • 脏读:一个事务读到另一个事务未提交的更新数据。
  • 不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
  • 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
  • 丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

Spring中的事务

1、Spring事务的本质其实就是数据库对事务的支持

2、Spring并不直接管理事务,而是提供了多种事务管理器。Spring将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。我们再也无需去处理获得连接、关闭连接、事务提交和回滚等这些操作,使得我们把更多的精力放在处理业务上。

3、Spring事务管理的核心接口是PlatformTransactionManager

事务管理器接口通过getTransaction(TransactionDefinition definition)方法根据指定的传播行为返回当前活动的事务或创建一个新的事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

TransactionDefinition接口中定义了它自己的传播行为和隔离级别

主要的方法有

java int getIsolationLevel();// 返回事务的隔离级别 String getName();// 返回事务的名称 int getPropagationBehavior();// 返回事务的传播行为 int getTimeout(); // 返回事务必须在多少秒内完成 boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的

4、Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员可以直接使用Spring的事务管理机制,不用了解底层。

Spring支持编程式事务管理和声明式事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚

缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用
  • 将事务管理代码从业务方方法中分离出来,以生命的方式来实现事务管理
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的
  • 事务管理器是Spring的核心事务管理抽象,管理封装了一组独立于技术的方法

5、@Transactional 注解的事务管理。

  • 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
  • @Transactional 注解只能应用到 public 方法才有效。
  • 使用mybatis , spring boot 会自动配置一个 DataSourceTransactionManager,只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了。

使用方法:

只需在方法加上 @Transactional 注解就可以了。

如下有一个保存用户的方法,加入 @Transactional 注解,使用默认配置,抛出异常之后,事务会自动回滚,数据不会插入到数据库。

```java @Transactional @Override public void save() { User user = new User("服部半藏"); userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

} ```

6、@Transactional 注解的属性介绍

value 和 transactionManager 属性: 它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

propagation 属性: 事务的传播行为,默认值为 Propagation.REQUIRED。 可选的值有:

  • Propagation.REQUIRED

如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

  • Propagation.SUPPORTS

如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY

如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW

重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

  • Propagation.NOT_SUPPORTED

以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER

以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED

和 Propagation.REQUIRED 效果一样。

Spring中的事务&分布式事务_第1张图片

事务的隔离级别: 默认值为 Isolation.DEFAULT。可选的值有:

  • Isolation.DEFAULT

使用底层数据库默认的隔离级别。

  • Isolation.READ_UNCOMMITTED

  • Isolation.READ_COMMITTED

  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

Spring中的事务&分布式事务_第2张图片

timeout 属性: 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor 属性 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

7、@Transactional 事务实现机制

在应用系统调用声明了 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor来使用拦截,在TransactionInterceptor拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager操作数据源 DataSource提交或回滚事务。

Spring AOP代理有 CglibAopProxyJdkDynamicAopProxy两种,以CglibAopProxy为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于JdkDynamicAopProxy,需要调用其invoke方法。

事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager管理 JDBC 的 Connection。

8、Sprign 动态代理机制

Spirng的AOP的动态代理实现机制有两种,分别是:

  • JDK动态代理:

具体实现原理:

1、通过实现InvocationHandlet接口创建自己的调用处理器

2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,

Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

  • CGLib动态代理

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展Java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

两者对比:

JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。

使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)

如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

9、为什么spring的事务注解@Transaction只能用在public方法上

spring中很多东西的实现都是依靠AOP,本质上也是依靠代理来实现。事务在spring中的实现其实就是生成bean对象的代理对象。在bean进行创建出实例时, 如果是有事务注解的方法,就会被进行增强,最终形成代理类。

在spring中,有两种动态代理的方式,一种是jdk,它是将原始对象放入代理对象内部,通过调用内含的原始对象来实现原始的业务逻辑,这是一种装饰器模式;而另一种是cglib,它是通过生成原始对象的子类,子类复写父类的方法,从而实现对父类的增强。

jdk中,如果是private的方法,显然是无法访问的,而在cglib中,也是同样,private方法也无法访问。于是原始对象的业务逻辑就丢失了,那么怎么进行增强呢?于是这个问题实际在于,被AOP增强的方法都应该是public的,而不能是private的

分布式事务

事务的概念

单数据源事务也可以叫做单机事务,或者本地事务。

分布式场景下,一个系统由多个子系统构成,每个子系统有独立的数据源。一个电商系统可能由购物微服务、库存微服务、订单微服务等组成。用户购物的业务场景中是调用多数据源来组合而成的。分布式事务机制就是针对多数据源操作提出的

参与者:每个数据库就是一个事务参与者

协调者:访问多个数据源的程序。比如下单服务

二将军问题和幂等性

网络二将军问题的存在使得消息的发送者往往要重复发送消息,直到收到接收者的确认才认为发送成功,但这往往又会导致消息的重复发送。例如电商系统中订单模块调用支付模块扣款的时候,如果网络故障导致二将军问题出现,扣款请求重复发送,产生的重复扣款结果显然是不能被接受的。因此要保证一次事务中的扣款请求无论被发送多少次,接收方有且只执行一次扣款动作,这种保证机制叫做接收方的幂等性。

2PC、3PC

2PC 是一种实现分布式事务的模型,这两个阶段是:

1)准备阶段:事务协调者向各个事务参与者发起询问请求:“我要执行全局事务了,这个事务涉及到的资源分布在你们这些数据源中,分别是……,你们准备好各自的资源(即各自执行本地事务到待提交阶段)”。各个参与者协调者回复 yes(表示已准备好,允许提交全局事务)或 no(表示本参与者无法拿到全局事务所需的本地资源,因为它被其他本地事务锁住了)或超时。

2)提交阶段:如果各个参与者回复的都是 yes,则协调者向所有参与者发起事务提交操作,然后所有参与者收到后各自执行本地事务提交操作并向协调者发送 ACK;如果任何一个参与者回复 no 或者超时,则协调者向所有参与者发起事务回滚操作,然后所有参与者收到后各自执行本地事务回滚操作并向协调者发送 ACK。

存在的问题

  • 性能差,准备阶段要等所有的参与者返回才可以进入提交阶段,有可能导致部分微服务的本地事务阻塞
  • 准备阶段完成后如果协调者宕机那么所有参与者都收不到提交或者回滚指令
  • 使用场景局限,要求所有参与者都实现XA协议,但是多个系统服务利用API互相调用,不遵守XA协议,这样用不了2PC

3PC把两阶段变成3阶段,分别是

  • 询问阶段
  • 准备阶段
  • 提交或回滚阶段

主要区别在于利用超时机制解决了2PC的同步阻塞问题,避免资源被锁定,但是同样无法解决宕机问题

TCC方案

TCC是一种解决多个微服务之间的分布式事务问题的方案。TCC 由支付宝团队提出,被广泛应用于金融系统中。TCC是Try、Confirm、Cancel 的缩写

本质是一个应用层面上的 2PC,同样分为两个阶段:

  • 准备阶段

协调者调用每个微服务的try接口,将涉及到的资源锁定住,锁定成功返回yes

  • 提交阶段

所有微服务都返回yes就进入提交阶段,调用confirm接口,每个服务提交事务,如果有任何一个服务的try接口在准备阶段返回no或者超时就调用cancel接口

TCC解决了2PC无法解决的宕机问题,TCC用不断重试的办法,因为try操作锁住了所有资源,所以无论是confirm失败或者cancel失败都可以不断重试

在不断重试 confirm 和 cancel 的过程中有可能重复进行了 confirm 或 cancel,因此还要再保证 confirm 和 cancel 操作具有幂等性,保证每个参与者只进行一次 confirm 或者 cancel。

实现 confirm 和 cancel 操作的幂等性,有很多解决方案,例如每个参与者可以维护一个去重表记录每个全局事务是否进行过 confirm 或 cancel 操作

事务状态表方案

这个方案需要维护一张事务状态表

Spring中的事务&分布式事务_第3张图片

初始状态为 1,每成功调用一个服务则更新一次状态,最后所有的服务调用成功,状态更新到 3。

有了这张表,就可以启动一个后台任务,扫描这张表中事务的状态,如果一个分布式事务一直未到状态 3,说明这条事务没有成功执行,于是可以重新调用 repo-service 扣减库存、调用 order-service 生成订单。直至所有的调用成功,事务状态到 3。

如果多次重试仍未使得状态到 3,可以将事务状态置为 error,通过人工介入进行干预。

由于存在服务的调用重试,因此每个服务的接口要根据全局的分布式事务 ID 做幂等

基于消息中间件的最终一致性事务方案

无论是 2PC & 3PC 还是 TCC、事务状态表,基本都遵守 XA 协议的思想

这些方案本质上都是事务协调者协调各个事务参与者的本地事务的进度,使所有本地事务共同提交或回滚,最终达成一种全局的 ACID 特性。

这些全局事务方案由于操作繁琐、时间跨度大,或者在全局事务期间会排他地锁住相关资源,使得整个分布式系统的全局事务的并发度不会太高。很难满足电商等高并发场景对事务吞吐量的要求

分布式事务框架 Seata

学习中~


Spring中的事务&分布式事务_第4张图片

你可能感兴趣的:(分布式,数据库,spring,java,aop)