一篇打通所有事务理论知识--乐字节java

事务概念

事务就是对数据库的一次操作,要么全部成功,要么全部失败。事务是最小的逻辑执行单元,也是数据库并发控制的基本单位。其作用就是确保数据的准确性。

事务四大特性

原子性(Atomicity)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。保证事务内的操作是不可分割的。

一致性(Consistency)

一致性是指事务执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。比如转账,假设用户A和用户B两者的钱加起来一共是2000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是2000,不可能出现其他情况。

隔离性 (Isolation)

隔离性是指并发执行的事务之间不能相互影响。比如写一条update语句和delete语句,而且同时操作一条记录,如果不加控制那就会出现各种问题。

持久性 (Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。比如执行insert语句后,就必须要有数据写入磁盘。

总之,这四个特性是事务管理的基石,其中,原子性是基础,隔离性是手段,持久性是目的,真正的老大就是一致性。其中最难的是隔离性,因为事务是原子操作,但事务与事务之间可以并发执行,因此要达到一致性就要求事务与事务之间进行隔离,那么事务如果不隔离直接并发执行会造成什么后果呢?

事务并发问题

脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

时间 事务 A(存款) 事务 B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(500 元)
T4 取出 500 元(余额 0 元)
T5 查询余额(0 元)
T6 撤销事务(余额恢复为 500 元)
T7 存入 100 元(余额 100 元)
T8 提交事务

余额应该为 600 元才对!请看 T5 时间点,事务 A 此时查询余额为 0 元,这个数据就是脏数据,它是事务 B 造成的,明显事务没有进行隔离,渗过来了,乱套了。

不可重复读

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据结果,这是由于在查询间隔,被另一个事务修改并提交了。

时间 事务 A(存款) 事务 B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(500 元)
T4 查询余额(500 元)
T5 取出 500 元(余额 0 元)
T6 提交事务
T7 查询余额(0 元)

事务 A 其实除了查询了两次以外,其他什么事情都没有做,结果钱就从 1000 变成 0 了,这就是重复读了。可想而知,这是别人干的,不是我干的。其实这样也是合理的,毕竟事务 B 提交了事务,数据库将结果进行了持久化,所以事务 A 再次读取自然就发生了变化。这种现象也正常,但在某些场景下确实不允许的(比如电商中扣减库存)。

虚读(幻读)

幻读是事务非独立执行时发生的一种现象,即在一个事务读的过程中,另外一个事务可能插入了新数据记录,影响了该事务读的结果。

时间 事务 A(统计总存款) 事务 B(存款)
T1 开始事务
T2 开始事务
T3 统计总存款(1000 元)
T4 存入 100 元
T5 提交事务
T6 统计总存款(1100 元)

归纳一下,以上提到了事务并发所引起的跟读取数据有关的问题,各用一句话来描述一下:

  1. 脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
  2. 不可重复读:事务 A 读取了事务 B 已提交的更改数据。
  3. 幻读:事务 A 读取了事务 B 已提交的新增数据。

第一条是坚决抵制的,后两条在大多数情况下可不作考虑。正因为事务并发时会出现问题,因此数据库专家们就提出了解决方案即事务的隔离级别。

事务隔离级别

不同数据库的事务隔离级别不尽相同。MySQL数据库支持下面的四种隔离级别,并且5.5以后默认为 Repeatable read 级别,之前是 Read committed ;而在Oracle数据库中,只支持Serializable 级别和 Read committed 这两种级别,并且默认为 Read committed 级别。MySQL数据库为我们提供了四种隔离级别,分别为:

事务隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED 允许 允许 允许
READ_COMMITTED 禁止 允许 允许
REPEATABLE_READ 禁止 禁止 允许
SERIALIZABLE 禁止 禁止 禁止
# 查询隔离级别
select @@tx_isolation; 

# 设置隔离级别
set [glogal | session] transaction isolation level 隔离级别名称;

本地事务管理

JDBC事务管理

如果我们的事务处理都在同一个数据库中,那简称为本地事务。在Java中,可以使用JDBC对设置数据库事务的隔离级别。设置数据库的隔离级别一定要是在开启事务之前。使用JDBC对数据库的事务设置隔离级别时,我们应该在调用Connection对象的setAutoCommit(false)方法之前调用Connection对象的setTransactionIsolation(level)去设置当前Connection的隔离级别如下所示:

至于参数level,可以使用Connection接口的字段,如以下代码所示:

这种设置方式只对当前connection有效。JDBC 只是连接 Java 程序与数据库的桥梁而已,那么数据库又是怎样隔离事务的呢?其实它就是“锁”这个东西。当插入数据时,就锁定表,这叫“锁表”;当更新数据时,就锁定行,这叫“锁行”。

Spring事务解决方案

Spring框架提供了一套事务传播行为(Transaction Propagation Behavior)机制来解决本地事务管理,其实它是对 JDBC 的一个补充或扩展。Spring一共有七种事务传播行为:

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。默认
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

配置demo如下:


    



    
            
         
        
        
        
        
             
    



    
    

或者使用@Transactional注解,里面也可以配置隔离级别:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    // 事务管理器别名
    @AliasFor("transactionManager")
    String value() default "";

    // 事务管理器
    @AliasFor("value")
    String transactionManager() default "";

    // 事务传播机制
    Propagation propagation() default Propagation.REQUIRED;

    // 隔离级别
    Isolation isolation() default Isolation.DEFAULT;

    // 事务超时时间
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    // 是否为只读事务
    boolean readOnly() default false;

    // 事务回滚异常类
    Class[] rollbackFor() default {};

    // 事务回滚异常类名
    String[] rollbackForClassName() default {};

    // 事务不回滚异常类
    Class[] noRollbackFor() default {};

    // 事务不回滚异常类名
    String[] noRollbackForClassName() default {};

}

全新Java资料加入群:10803-55292,输入暗号13,即可领取

Sprin

g 给我们带来了事务传播行为,这确实是一个非常强大而又实用的功能。除此以外,也提供了一些小的附加功能,比如:

  1. 事务超时(Transaction Timeout):为了解决事务时间太长,消耗太多的资源,所以故意给事务设置一个最大时长,如果超过了,就回滚事务。
  2. 只读事务(Readonly Transaction):为了忽略那些不需要事务的方法,比如读取数据,这样可以有效地提高一些性能。

分布式事务

有的情况下,我们的事务并不一定在同一个数据库或者同一个线程中,比如:跨库事务、分表事务、分布式应用下不同应用之间的事务,那么这些都通叫分布式事务。

全新Java资料加入群:10803-55292,输入暗号13,即可领取

分布式理论

聊分布式事务之间我们先了解下分布式理论。

CAP定理

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

Consistency 一致性

一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,所以,一致性,说的就是数据一致性。分布式的一致性

对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

三种一致性策略

对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。

如果能容忍后续的部分或者全部访问不到,则是弱一致性。

如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

CAP中说,不可能同时满足的这个一致性指的是强一致性。

Availability 可用性

可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。

对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。所以,一般我们在衡量一个系统的可用性的时候,都是通过停机时间来计算的。

可用性分类 可用水平(%) 年可容忍停机时间
容错可用性 99.9999 <1 min
极高可用性 99.999 <5 min
具有故障自动恢复能力的可用性 99.99 <53 min
高可用性 99.9 <8.8h
商品可用性 99 <43.8 min

通常我们描述一个系统的可用性时,我们说淘宝的系统可用性可以达到5个9,意思就是说他的可用水平是99.999%,即全年停机时间不超过 (1-0.99999)*365*24*60 = 5.256 min,这是一个极高的要求。

好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。一个分布式系统,上下游设计很多系统如负载均衡、WEB服务器、应用代码、数据库服务器等,任何一个节点的不稳定都可以影响可用性。

Partition Tolerance分区容错性

分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔未独立的几个部分,各个部分还能维持分布式系统的运作,这样就具有好的分区容错性。

简单点说,就是在网络中断,消息丢失的情况下,系统如果还能正常工作,就是有比较好的分区容错性。

CAP的证明

如上图,是我们证明CAP的基本场景,网络中有两个节点N1和N2,可以简单的理解N1和N2分别是两台计算机,他们之间网络可以连通,N1中有一个应用程序A,和一个数据库V,N2也有一个应用程序B2和一个数据库V。现在,A和B是分布式系统的两个部分,V是分布式系统的数据存储的两个子数据库。

在满足一致性的时候,N1和N2中的数据是一样的,V0=V0。在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应。在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作。

如上图,是分布式系统正常运转的流程,用户向N1机器请求数据更新,程序A更新数据库Vo为V1,分布式系统将数据进行同步操作M,将V1同步的N2中V0,使得N2中的数据V0也更新为V1,N2中的数据再响应N2的请求。

这里,可以定义N1和N2的数据库V之间的数据是否一样为一致性;外部对N1和N2的请求响应为可用行;N1和N2之间的网络环境为分区容错性。这是正常运作的场景,也是理想的场景,然而现实是残酷的,当错误发生的时候,一致性和可用性还有分区容错性,是否能同时满足,还是说要进行取舍呢?

作为一个分布式系统,它和单机系统的最大区别,就在于网络,现在假设一种极端情况,N1和N2之间的网络断开了,我们要支持这种网络异常,相当于要满足分区容错性,能不能同时满足一致性和响应性呢?还是说要对他们进行取舍。

假设在N1和N2之间网络断开的时候,有用户向N1发送数据更新请求,那N1中的数据V0将被更新为V1,由于网络是断开的,所以分布式系统同步操作M,所以N2中的数据依旧是V0;这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,怎么办呢?

有二种选择,第一,牺牲数据一致性,保证可用性。响应旧的数据V0给用户;

第二,牺牲可用性,保证数据一致性。阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。

这个过程,证明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。

CAP权衡

通过CAP理论及前面的证明,我们知道无法同时满足一致性、可用性和分区容错性这三个特性,那要舍弃哪个呢?

我们分三种情况来阐述一下。

CA without P

这种情况在分布式系统中几乎是不存在的。首先在分布式环境下,网络分区是一个自然的事实。因为分区是必然的,所以如果舍弃P,意味着要舍弃分布式系统。那也就没有必要再讨论CAP理论了。这也是为什么在前面的CAP证明中,我们以系统满足P为前提论述了无法同时满足C和A。

比如我们熟知的关系型数据库,如My Sql和Oracle就是保证了可用性和数据一致性,但是他并不是个分布式系统。一旦关系型数据库要考虑主备同步、集群部署等就必须要把P也考虑进来。

其实,在CAP理论中。C,A,P三者并不是平等的,CAP之父在《Spanner,真时,CAP理论》一文中写到:

如果说Spanner真有什么特别之处,那就是谷歌的广域网。Google通过建立私有网络以及强大的网络工程能力来保证P,在多年运营改进的基础上,在生产环境中可以最大程度的减少分区发生,从而实现高可用性。

从Google的经验中可以得到的结论是,无法通过降低CA来提升P。要想提升系统的分区容错性,需要通过提升基础设施的稳定性来保障。

所以,对于一个分布式系统来说。P是一个基本要求,CAP三者中,只能在CA两者之间做权衡,并且要想尽办法提升P。

CP without A

如果一个分布式系统不要求强的可用性,即容许系统停机或者长时间无响应的话,就可以在CAP三者中保障CP而舍弃A。

一个保证了CP而一个舍弃了A的分布式系统,一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。

设计成CP的系统其实也不少,其中最典型的就是很多分布式数据库,他们都是设计成CP的。在发生极端情况时,优先保证数据的强一致性,代价就是舍弃系统的可用性。如Redis、HBase等,还有分布式系统中常用的Zookeeper也是在CAP三者之中选择优先保证CP的。

无论是像Redis、HBase这种分布式存储系统,还是像Zookeeper这种分布式协调组件。数据的一致性是他们最最基本的要求。一个连数据一致性都保证不了的分布式存储要他有何用?

比如Zookeeper关于CAP的思考:

ZooKeeper是个CP(一致性+分区容错性)的,即任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性。但是它不能保证每次服务请求的可用性,也就是在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。ZooKeeper是分布式协调服务,它的职责是保证数据在其管辖下的所有服务之间保持同步、一致。所以就不难理解为什么ZooKeeper被设计成CP而不是AP特性的了。

AP wihtout C

要高可用并允许分区,则需放弃一致性。一旦网络问题发生,节点之间可能会失去联系。为了保证高可用,需要在用户访问时可以马上得到返回,则每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。

这种舍弃强一致性而保证系统的分区容错性和可用性的场景和案例非常多。前面我们介绍可用性的时候说到过,很多系统在可用性方面会做很多事情来保证系统的全年可用性可以达到N个9,所以,对于很多业务系统来说,比如淘宝的购物,12306的买票。都是在可用性和一致性之间舍弃了一致性而选择可用性。

你在12306买票的时候肯定遇到过这种场景,当你购买的时候提示你是有票的(但是可能实际已经没票了),你也正常的去输入验证码,下单了。但是过了一会系统提示你下单失败,余票不足。这其实就是先在可用性方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,会影响一些用户体验,但是也不至于造成用户流程的严重阻塞。

但是,我们说很多网站牺牲了一致性,选择了可用性,这其实也不准确的。就比如上面的买票的例子,其实舍弃的只是强一致性。退而求其次保证了最终一致性。也就是说,虽然下单的瞬间,关于车票的库存可能存在数据不一致的情况,但是过了一段时间,还是要保证最终一致性的。

对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。

适合的才是最好的

上面介绍了如何CAP中权衡及取舍以及典型的案例。孰优孰略,没有定论,只能根据场景定夺,适合的才是最好的。

对于涉及到钱财这样不能有一丝让步的场景,C必须保证。网络发生故障宁可停止服务,这是保证CP,舍弃A。比如前几年支付宝光缆被挖断的事件,在网络出现故障的时候,支付宝就在可用性和数据一致性之间选择了数据一致性,用户感受到的是支付宝系统长时间宕机,但是其实背后是无数的工程师在恢复数据,保证数数据的一致性。

BASE理论

在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:

  • 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。
  • 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。
  • 最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

分布式事务解决方案

强一致性

两阶段提交(2PC)

2PC协议:一种协议,在分布式系统保证事务的原子提交

XA:分布式事务规范

AP:Application 应用,TM:Transaction Manager事务管理器,RM:Resource Manager资源管理器。xid是一个分布式事务标识符。

XA {START|BEGIN} xid [JOIN|RESUME]   //开启XA事务,如果使用的是XA START而不是XA BEGIN,那么不支持[JOIN|RESUME],xid是一个唯一值,表示事务分支标识符

XA END xid [SUSPEND [FOR MIGRATE]]   // 结束一个XA事务,不支持[SUSPEND [FOR MIGRATE]]

XA PREPARE xid 准备提交

XA COMMIT xid [ONE PHASE] //提交,如果使用了ONE PHASE,则表示使用一阶段提交。两阶段提交协议中,如果只有一个RM参与,那么可以优化为一阶段提交

XA ROLLBACK xid  //回滚

XA RECOVER [CONVERT XID]  //列出所有处于PREPARE阶段的XA事务

案例:

mysql> XA START 'xatest’;  //其中'xatest’就是xid的值
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into user(name) values("abc");
Query OK, 1 row affected (0.00 sec)
 
mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)
 
mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.01 sec)
 
mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.01 sec)

两阶段提交是处理分布式事务的经典方法。MySQL从5.0版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。主要通过增加事务协调者,对事务进行全局管理。

  • 第一个阶段:所有分支被预备好,即他们被TM告知要准备提交。发出“准备”命令,所有事务参与者接受指令后进行资源准备,锁准备,undo log准备。如果都返回“准备成功”,如果不能执行,返回终止。
  • 第二个阶段:TM告知RM是否要提交或回滚,如果在预备分支时,所有的分支指示能够提交,则所有的分支被告知要提交。否则,有任何分支提示它将不能提交,那么所有分支被告知回滚。

优点:较强的一致性,适合于对数据一致性要求比较高对场景。
缺点:

  • 整个过程耗时过程,锁定资源时间过长,同步阻塞(准备阶段回复后,一直等待协调者调用commit 或者rollback),CAP中达到了CP,牺牲了可用性,不适合高并发场景
  • 协调者可能存在单点故障
  • Commit阶段可能存在部分成功,部分失败情况,并没有提及是否rollback

其中JAVA中的JTA(Java Transaction API)就是这种实现

三阶段提交(3PC)

3PC三阶段提交(非阻塞,引入超时和准备阶段)。进入阶段3之后,如果协调者或者执行者因为网络等问题,接受不到docommit请求,超时后默认都执行doCommit请求。

优点:降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。

缺陷:在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

补偿事务(TCC)

TCC是一种编程模式,Try-Confirm-Cancel。本质也是2PC,只是TCC在应用层控制,数据库只是负责第一个阶段。XA在数据库层控制两阶段提交。

三个阶段如下:

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

严格遵守ACID的分布式事务我们称为刚性事务,而遵循BASE理论(基本可用:在故障出现时保证核心功能可用,软状态:允许中间状态出现,最终一致性:不要求分布式事务打成中时间点数据都是一致性的,但是保证达到某个时间点后,数据就处于了一致性了)的事务我们称为柔性事务,其中TCC编程模式就属于柔性事务。

TCC是对二阶段的一个改进,try阶段通过预留资源的方式避免了同步阻塞资源的情况,但是TCC编程需要业务自己实现try,confirm,cancle方法,对业务入侵比较大,但是实现比较容易。

分布式事务的基本原理本质上都是两阶段提交协议(2PC),TCC (try-confirm-cancel)其实也是一种 2PC,只不过 TCC 规定了在服务层面实现的具体细节,即参与分布式事务的服务方和调用方至少要实现三个方法:try 方法、confirm 方法、cancel 方法。

比如下面一段代码是下单的伪代码,如果生成主订单和子订单是两个不同的服务,那么子订单生成失败时,要对主订单进行手动回滚,这就是一个TCC过程。TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

// 远程调用生成主订单
Integer orderId = OrderServer.addOrder(...);
if (orderId == null) {
    return;
}
try{
   // 生成子订单
    OrderItemServer.addItem(...);
} catch(Exception e) {
    // 手动删除主订单信息
    OrderServer.deleteOrder(orderId);
}

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

Sagas 事务模型

该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。

比如我们一次关于购买旅游套餐业务操作涉及到三个操作,他们分别是预定车辆,预定宾馆,预定机票,他们分别属于三个不同的远程接口。可能从我们程序的角度来说他们不属于一个事务,但是从业务角度来说是属于同一个事务的。

实现框架:https://github.com/eventuate-tram/eventuate-tram-sagas

最终一致性

除了Saga模式,我们也有折中的方法,就是同步提交一个事务,异步通知其它数据源更新数据。这里我们放弃了事务参与者要么不执行,要么全部执行,采用异步方式达到最终一致性。

本地消息表(异步确保)

其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

  • 写业务逻辑和插入本地消息,必需在同一个事务
  • 可以把本地消息数据发送到MQ或者利用quartz定时任务,发rest调用处理另外的事务,达到最终一致性
  • 如果利用MQ,也是需要轮询处理失败的消息。也直接插入消息表,然后利用quartz推送

你可能感兴趣的:(一篇打通所有事务理论知识--乐字节java)