vlambda博客
学习文章列表

抛砖引玉之 - 分布式事务


点击上方蓝色“一点代码”,选择“设为星标”
更方便获取学习资源!


不同于本地JDBC支持的事务功能,分布式事务将复杂度提升到了多个独立进程中,我们无法使用以往简单的方式去保证数据的一致性。
而出现问题则解决问题,在前人无数的文献之下,诞生了以下几种分布式事务的实现方案



CAP 原则

CAP原则又称为CAP定理,指在一个分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)三者不可兼得。
  • 一致性(Consistency):意思是,写操作之后的读操作,必须返回该值。数据都必须处于一致的状态。 

  • 可用性(Availability):意思是只要收到用户的请求,服务器就必须给出回应。
  
  • 分区容错性(Partition tolerance):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。
    分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。
    CAP 定理告诉我们,剩下的 C 和 A 无法同时做到
 




市面上的分布式事务具体实现方案中,一般采用被称为两阶段提交或基于两阶段提交的优化版本:三阶段提交来实现的。
那么下面我们来了解一下所谓的每一个“阶段”到底处理了一些什么事情?

在了解两阶段/三阶段提交协议之前,我们必须引入一个称之为事务协调器的组件,在一个事务跨越多个节点时,为了保持事务的ACID特性,因此需要它来统一掌握所有节点的操作结果,并最终指示这些节点是否真正提交或回滚。



两阶段提交协议

二阶段提交的思路可以概括为:参与者将操作成败通知协调器,再由协调器根据所有参与者的反馈决定各参与者是提交操作还是回滚操作。

主要是以下两个阶段:
  1. Prepare(准备阶段):事务协调器(事务管理器)给每个参与者都发送Prepare消息,每个参与者要么直接返回失败,要么在本地执行事务,写本地的redo和undo日志但不提交。
  2.    
  3. Commit(提交阶段):如果协调器收到所有参与者的失败消息或者超时,则直接给每个参与者都发送回滚消息,否则发送提交消息。参与者根据事务管理器的指令执行提交或者回滚操作,并释放在所有事务处理过程中使用的锁资源。

两阶段提交的缺点:
  • 同步阻塞问题:在执行过程中,所有参与者的任务都是阻塞执行的。
  • 单点故障:所有请求都需要经过协调器,在协调器发生故障时,所有参与者都会被阻塞。
  • 数据不一致:在二阶段提交的第2阶段,在协调器向参与者发送提交请求后发生了局部网络异常,或发送提交消息时部分参与者发生故障,则只有一部分参与者收到了提交消息,于是整个分布式系统出现了数据不一致的现象。


三阶段提交协议

三阶段提交是二阶段提交协议的改进版本,具体改进如下:
  • 引入超时机制:在协调器和参与者中引入了超时机制,如果协调器长时间接受不到参与者的反馈,则认为参与者执行失败。
  • 在第1阶段和第2阶段都加入了一个预准备阶段,以保证在最后的任务提交之前各个参与者节点的状态是一致的,也就是说,除了引入超时机制,三阶段提交协议(3PC)把两阶段提交协议(2PC)的准备阶段再次一分为二,这样三阶段提交就有了CanCommit、PreCommit、DoCommit三个阶段。

具体三阶段如下:
  • CanCommit阶段:协调器向参与者发送Commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

  • PreCommit阶段:协调器根据参与者的反应来决定是否继续进行,有以下两种可能:
    • 假如协调器从所有参与者那里获得的反馈都是Yes响应,则预执行事务。
    • 假如有任意参与者向协调器发送了No响应,或者在等待超时之后协调器都没有接收到参与者的响应,则执行事务的中断。

  • DoCommit阶段:该阶段进行真正的事务提交,主要包括:协调器发送提交请求,参与者提交事务,参与者响应反馈(在事务提交完成之后向协调器发送Ack响应),协调器确认完成事务。这个阶段如果有参与者未返回响应或返回的非Ack响应,则认为有部分节点提交失败或宕机。此时协调器会向所有参与者发送abort请求,参与者收到此请求后会利用在第2阶段写入的undo日志来执行事务回滚。

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,而不会一直持有事务资源并处于阻塞状态。

但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作,这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。


分布式事务 - 传统事务

传统事务遵循ACID原则,即原子性、一致性、隔离性和永久性。这部分大家都熟透了的内容,这里就只是稍微做简单描述。
  • 原子性(Atomicity) :事务是一个完整操作,参与事务的逻辑单元要么都执行,要么都不执行。

  • 一致性(Consistency) :在事务执行完毕时,数据都必须处于一致的状态。

  • 隔离性(Isolation) :对数据进行修改的所有并发事务都是彼此隔离的,它不应该以任何方式依赖或影响其他事务。
     
  • 永久性(Durability) :在事务操作完成后,对数据库的修改将被持久化到永久性存储中。


分布式事务 - 柔性事务

在分布式数据库领域,基于CAP理论以及BASE理论,阿里巴巴提出了柔性事务的概念。

BASE理论是CAP理论的延伸,包括基本可用(Basically Available)、柔性状态(Soft State)、最终一致性(Eventual Consistency)三个原则,并基于这三个原则设计出了柔性事务。

一般说的柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型。

一. 两阶段型事务
指分布式事务的两阶段提交,对应技术上的XA和JTA/JTS,是分布式环境下事务处理的典型方式。

二. TCC(Try、Confirm、Cancel)补偿型事务
是一种基于补偿的事务处理模型:服务器A发起事务,服务器B参与事务,如果服务器A的事务和B的事务都顺利执行并完成提交,则整个事务执行完成。但是如果事务B的提交失败,这是事务A已经提交,所以需要一个补偿操作,即将已经提交的事务A执行的操作进行反操作,回复到未执行A事务之前的状态。


需要注意的是,发起提交的一般是主业务服务,而补偿状态的一般是业务活动管理者,因为活动日志被存储在业务活动管理中,补偿需要日志进行恢复。
TCC事务模型牺牲了一定的隔离性和一致性,但提高了事务的可用性。

三. 异步确保型事务
指将一系列同步的事务操作修改为基于消息队列异步执行的操作,来避免分布式事务中同步阻塞带来的数据操作性能的下降。

四. 最大努力通知型事务: 也是通过消息中间件来实现的,与前面的异步确保型事务操作不同的是:在消息由MQ发送到消费者之后,允许在达到最大重试次数之后正常结束事务,因此无法保障消息的最终一致性。


不论是本地事务还是分布式事务,事务的引进必然会造成程序的实现复杂度上升。 并且,事务最大的弊端则是带来性能上的损耗。 因此,是否使用事务、使用的事务类型都需要按照实际场景去做权衡。
而针对数据实时一致性要求不是特别高的场景,则可以使用柔性事物的特性(最终一致性)来降低对程序性能的开销。

如何更好、更合理的使用事务一般才是最值得我们关心的地方,希望在看了上面的内容后,能解决你开发过程中所遇到的问题和疑点。







如果觉得本文给您提供了帮助,请点击转发并关注获取更多内容