分布式事务的实现(理论篇)

开发过程避免不了使用事务,在单体应用架构时事务被 spring 封装到如此简单,在微服务大肆发展的今天难免会碰到多服务间的协调问题,事务便是解决这个问题的办法。

本地事务和分布式事务究竟有什么区别?

本地事务主要解决在单数据源情况下解决数据的一致性问题;分布式事务自然时为了解决跨服务操作多数据源的数据一致性问题。

一致性又分为:强一致性和弱一致性。
如果要保证全局数据时刻一致此为强一致性。
如果能容忍数据短暂不一致,但是一段时间后要求数据完全一致,此为弱一致性,也可以叫做最终一致性。

分布式事务的理论实现

两阶段提交(2PC)

角色:协调者(coordinator,Transaction Manager),参与者(participants,Resource Manager)
两阶段提交是XA规范的标准实现,该实现将分布式事务拆分为两个阶段:

  1. parpare:在此阶段需要等待所有参与的子事务完成本地事务的执行(执行并不提交)并返回结果给事务发起方。

  2. commit/rollback:当parpare阶段的所有子事务均执行成功并返回正确结果后,此阶段会对所有子事务进行通知提交事务,如果上一阶段未全部成功则此阶段进行通知子事务回滚。

    缺点:
    1、两阶段提交的略势则为所有子事务需要互相等待执行,数据库锁长期持有,在高并发情况下性能很差。
    2、当程序执行过程中协调者发生故障时,正在执行的参与者信息便会丢失,此时参与者可能会持续阻塞,导致事务无法执行。
    3、当程序执行到commit阶段,如果发生网络异常等导致个别参与者没有接受到commit请求时,数据可能会发生不一致的情况。

三阶段提交(3PC)

为了解决两阶段提交的部分缺点,三阶段引入超时机制,并增加了一个阶段询问操作。

  1. canCommit:询问事务参与者是否可以执行。

  2. preCommit:类似2PC的prepare操作。

  3. doCommit:类似2PC的commmit/rollback操作。

    3PC 引入超时机制,一旦无法接受到服务端的commit/rollback 信息时,会默认执行commit操作,而不会一致阻塞等待。
    引入超时机制并增加一个询问阶段,提高了事务的可用性,但是依然无法解决数据一致性问题,当doCommit阶段等待超时后多个服务之间执行不同的第三阶段操作会导致数据不一致。

XA规范主要是定义了TM(Transaction Manager)和RM(Resource Manager)之间的通信协议。

补偿事务(TCC)try-confirm-cancel

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模型是把锁的粒度完全交给业务处理。它分为三个阶段:
1.try:try阶段主要是对业务系统做检测及资源预留。
2.confirm:此阶段是进行数据提交,当所有子事务try成功后执行。
3.cancel:当事务执行过程中出现失败时所有子事务统一执行cancel。

TCC模式主要的缺点在于需要手工编写实现相关的接口,对代码侵入性较大。

Saga模式(长事务解决方案)

saga 是一种基于补偿的由消息进行驱动执行的长事务解决方案。目的是为了确保系统高可用情况下尽量确保数据一致性。
一般常见的事务处理均是为了数据强一致性,但是如果由业务需要多服务跨企业长时间去执行,甚至过程中需要人工去处理,事务的执行时长不可控,如果按照常见ACID的原则去进行设计程序的使用情况将无法接受。
于是,为了最大保证数据的最终一致性和应用的高可用性,我们需要Saga这种长事务的的解决方案。saga的执行流程如下:

本地事务表(异步确保)

本地消息表核心思想是将分布式的事务拆分为多个服务的本地事务,通过消息的流转进行事务的执行与回滚。

Seata AT 模式

AT 模式是阿里开源的seata中支持的一种模式。此处提及其确实有可取之处,在java环境中基于JDBC开箱即用十分简单。
AT模式感觉更像是 本地事务表、TCC、两阶段提交 的结合。通过基于本地库的 undo_log 日志表操作,由seata server进行协调,基本执行流程如下:

  1. parpare
    1.1 子事务解析需执行的sql对应生成执行前镜像。
    1.2 执行业务sql,再获取执行后镜像。
    1.3 使用执行后镜像和执行前镜像对比,将自动生成的回滚信息保存到当前库的 undo_log 表中,用于执行失败进行事务回滚。
    1.4 提交事务,向TC(事务协调者)注册分支:申请对应表中涉及修改的记录的全局锁 。
    1.5 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
    1.6 将本地事务提交的结果上报给 TC。

  2. 2-1 回滚:
    2.1.1 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
    2.1.2 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
    2.1.3 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
    2.1.4 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句并执行。
    2.1.5 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

    2-2 提交:
    2.2.1 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
    2.2.2 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

优势:
代码无侵入,使用简单。