阅读 197

江湖论道:分布式事务

什么是事务

指访问并可能更新数据库中各种数据项的一个程序执行单元。组成事务的所有操作,只有在都执行成功的情况下才能提交,只要有一个操作执行失败,都将导致事务回滚。

本地事务

在计算机系统中,通过关系型数据库来控制事务,单体应用中数据库通常和应用在同一服务器上,因此成为本地事务。

数据库事务的四大特性:

  • 原子性(Atomicity) : 事务包含的所有操作要么全部成功,要么全部失败回滚。
  • 一致性(Consistency) :一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  • 隔离性(Isolation) : 一个事务不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  • 持久性(Durability) :事务一旦提交,对数据库中数据的改变就应该是永久性的。

分布式事务

分布式事务就是由多个本地事务组合而成的事务,一般在分布式场景下才会出现。 分布式事务用于解决分布式环境下,跨系统应用的一致性问题。

分布式事务基础理论

CAP

  • 数据一致性(consistency):一致性指的是所有节点在同一时间的数据完全一致。 如果系统对一个写操作返回成功,那么之后的读请求都必须读到这个新数据;如果返回失败,那么之后的读操作都不能读到这个数据,对调用者而言,数据具有强一致性。
  • 服务可用性(availability): 只要收到用户的请求,服务器就必须给出回应。
  • 分区容错性(partition-tolerance): 在网络分区的情况下,被分隔的节点仍能正常对外服务。一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。

CAP无法同时满足 如果CAP三者同时满足,由于允许P的存在,则一定存在节点之间丢包的情况,而不能满足C。 因此,P是在分布式系统中,无法避免的,在分布式系统中进行写操作,如果满足一致性,则会出现所有节点都失败的情况,而不满足可用性。因此,分布式系统只能选择CP或者AP。

BASE

CAP理论告诉我们分布式系统中C(一致性),A(可用性)和P(分区容错性),最多满足两项。

BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。

  • BA (Basically Available) 基本可用 分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • S (Soft State )软状态 允许系统存在中间状态(软状态),而该中间状态不会影响系统整体可用性。如“创建中”,“正在同步”等状态,等到数据最终一致后,改为“成功”的状态。这里的中间状态就是 CAP 理论中的数据不一致。
  • E (Eventual Consistency) 最终一致性 系统中的所有节点数据经过一定时间后,最终能够达到一致的状态。比如订单的"支付中",最终会变成"支付成功"或者"支付失败"。

BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个扩展,通过牺牲强一致性来获得可用性。当出现故障时,允许部分不可用,要保证核心功能可用,允许数据在一段时间内不一致,但最终达到一致。

分布式事务解决方案

XA模式

XA是由Tuxedo提出的一个分布式事务协议。大致分为两部分:事务管理器本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

2PC(Two-phase Commit两阶段提交)

在二阶段提交中有两个角色:

  • 事务协调者:分布式事务的大脑,负责指挥协调各个业务系统提交/回滚事务
  • 事务参与者:本地资源管理器,即事务的执行者,也就是各个业务系统。

两阶段提交协议的具体步骤:

  • 准备阶段(Prepare phase): 事务管理器(TM)给每个参与者发送Prepare消息,每个数据库参与者在本地资讯事务,此时事务没有提交。记录日志(undo log 记录修改前的数据,用于数据回滚/ redo log记录修改后的数据,用于事务提交后写入数据文件)

  • 提交阶段(Commit phase): 如果事务管理器(TM)收到了参数者的执行失败或超时的消息时,直接给每个参与者发送回滚(Rollback) 消息;否则,发送提交消息(commit); 参与者根据事务管理器的指令执行提交或回滚。并释放事务处理中使用的锁资源。

    gPCxeA.md.png

XA两阶段提交究竟有哪些不足呢?

  1. 协调者单点故障问题

事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直阻塞下去,处于中间状态无法完成事务。

  1. 丢失消息导致的不一致问题。

协调者发出请求可能由于网络原因得不到响应。在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

  1. 同步阻塞

    资源锁需要等待两阶段结束后才能释放,性能较差。

3PC(三阶段提交)

把两阶段提交的准备阶段一分为二,加入了一个预提交的缓冲阶段。同时在协调者和参与者中引入超时机制。

三阶段提交将二阶段的准备阶段拆分为两个阶段 。

三阶段提交流程

  • 阶段一:canCommit 协调者向参与者发送commit请求, 参与者如果可以提交,就返回yes,并进入预备状态,否则返回no。参与者不执行事务。

  • 阶段二:preCommit 协调者根据上阶段 canCommit参与者的反应来确定是否可以基于事务的preCommit操作。如果一阶段所以参与者都返回yes,参与者预执行事务,如果有任意一个参与者反馈no,或者等待超时,协调者无法收到参与者的反馈,就中断事务。

  • 阶段三:doCommit 与2PC的提交阶段差不多。

    优点:相比较2PC而言,3PC对于协调者和参与者都设置了超时时间,避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。

    缺点:依然不能解决数据一致性问题。

TCC模式

理论

TCC又称补偿事务。 实际上是服务化的两阶段提交协议,开发者需要编码实现 Try、Confirm、Cancel这三个服务接口。其核心思想是:"针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)"

TCC对应 Try、Confirm、Cancel 三种操作

  1. 预处理 Try : 主要是做业务检查和资源预留

  2. 确认 Confirm: 确认提交,Try阶段所有的分支事务执行成功后,开始执行Confirm, 和Commit类似。

  3. Cancel阶段:Cancel则是当Try操作中涉及的所有应用没有全部成功,需要将已成功的应用进行Rollback回滚

    gPJlLj.md.png

TCC需要注意的三种异常处理

  • 空回滚

    Q: 分支事务所在的服务器宕机或者网络异常,此时没有执行Try, 当故障恢复后,会调用Cancel接口进行回滚。那么,

    如何判断是空回滚还是正常回滚?

    A: 增加一张分支事务表,其中包含全局事务ID和分支事务ID,在第一阶段Try方法里插入一条记录,表示一阶段执行了,Cancel阶段接口执行读取该记录,如果存在记录,则正常回滚,不存在则空回滚。

  • 悬挂

    Q: 通过RPC调用Try方法时,如果发生网络拥堵,通常RPC调用有超时机制的,超时后,TM就会通知RM回滚资源,回滚完成后,Try方法才执行,锁住了资源,无法释放。

    Cancel发生在Try之前,业务资源预留了以后,没办法处理怎么办?

    A: 分支事务记录表中如果记录了Cancel阶段已经执行过,则Try方法不再执行。

  • 幂等

    Q: Confirm和Cancel执行失败后需要进行重试,如何保证幂等?

    A: 分支事务记录表中维护执行状态,每次执行前都查询一下该状态。

    事务管理器(TM)可以实现为独立的服务,也可让全局事务发起方作为事务管理器。TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链路,用来记录事务上下文,追踪和记录状态,由于Confirm和Cancel失败都要进行重试,因此要实现幂等。

TCC与XA比较

XA是资源层面的分布式事务,TCC业务层面的分布式事务。

Saga

Saga模型又称长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出。Saga模型将分布式事务分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块,任和一个本地事务出错时,都可以通过调用相关的补充方法实现事务的最终一致性。

gPvH9e.png

Saga适合于长事务的应用场景。

缺点:由于Saga模型没有准备阶段,因此事务没有实现隔离。如果两个事务同时操作同一资源则会产生并发读写问题,这时就需要在应用层面加入资源锁定的逻辑了。

可靠消息最终一致性

什么是可靠消息最终一致性

事务发起者执行本地事务后,发出一条消息,事务参与者(消息消费者)一定能接受到消息并处理成功。

此方案利用消息中间件完成,事务发起方(消息生产者)将消息发送给消息中间件,事务参与方从消息中间件接受消息,事务发起方和消息中间件之间,事务参与者和消息中间件之间都是通过网络通信。所以会因为网络问题存在分布式事务的问题。

需要解决的问题

  1. 本地事务与消息发送的原子性问题

    本地事务和发送消息,要么都成功,要么都失败。

    1. 如果先发送消息,再操作数据库。无法保证数据库操作和发送消息的原子性,可能发送消息成功,但是数据库操作失败。
    2. 先操作数据库,在发送消息。如果消息发送失败,抛出异常,从而数据库回滚。看似没问题,但是如果超时异常,数据库回滚,但是消息其实已经发出了,同样会有上述问题。
  2. 事务参与者接收消息的可靠性

    事务参与者必须能从消息队列接收到消息,如果接收消息失败可以重复接收。

  3. 消息重复消费

    要解决消费重复的问题,要实现事务参与者的方法幂等性。

可靠消息最终一致性实现方案

本地消息表

消息方式放在信息入库的同时,维护一张消息记录表,添加一条消息记录到数据库表,通过本地事务保证业务操作和消息一致性。然后通过定时任务将消息发送到消息中间件,等待确认消息发送给消费方时,将消息表中数据删除。

缺点:定时任务轮询扫描数据库消息表,会影响数据库性能。

RocketMQ事务消息

gCkR5q.md.png

RocketMQ事务消息发送步骤:

  1. 发送方将半事务消息发送至消息队列RocketMQ版服务端。半事务消息不会被订阅方消费。
  2. MQ Server 将消息持久化成功之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向MQ 服务端提交二次确认(Commit或是Rollback),服务端收到Commit状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半事务消息,订阅方将不会接受该消息。

Q: 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端怎么办?

RocketMQ服务端会对该消息发起消息进行回查。发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果, 发送方根据检查得到本地事务的最终状态再次提交二次确认,服务器再按照步骤4对半事务消息进行操作。

最大努力通知

当本地事务执行完成后发送消息到MQ,MQ通知另一个系统执行事务,如果失败了则重试,多次尝试后还是不成功则放弃。 允许少数事务失败,一般用在对分布式事务要求不严格的情况下,比如记录日志或者状态等。

Seata

Seata是阿里中间件团队发起的开源项目Fescar,后更名Seata,是一个开源分布式事务框架。

Seata 中有三大模块,分别是 TM、RM 和 TC。

gPW6rd.png

  • Transaction Coordinator (TC) : 事务协调器,它是独立的中间件,需要独立部署运行。它维护全局事务的运行状态,接收TM的指令发起全局事务的提交或回滚。负责与RM通信协调各个分支事务的提交或回滚。
  • Transaction Manager (TM) : 事务管理器, TM是嵌入应用中工作,负责开启一个全局事务并在最终向TC发起全局提交或全局回滚的指令。
  • Resource Manager (RM) : 控制分支事务,负责分支注册,状态汇报,并接受事务协调器TC的指令,驱动分支事务的提交和回滚。

Seata执行流程

gPWjiV.md.png

  1. TM要求TC开始一个新的全局事务。TC生成一个表示全局事务的XID。

  2. XID通过微服务的调用链传播。

  3. RM将本地事务作为对应的XID全局事务的分支注册到TC。

  4. TM请求TC提交或回滚相应的XID全局事务。

  5. TC驱动XID对应全局事务下的所有分支事务,以完成分支提交或回滚。

Seata 提供了 4 种分布式事务解决方案, AT 模式、TCC 模式、Saga 模式和 XA 模式。

AT模式是Seata实现的2PC。解决了XA模式的2PC需要依赖数据库支持XA协议的弊端,又对业务零侵入。

在 AT 模式下,开发者可以只用关心自己的业务 SQL,将业务 SQL的执行作为一阶段,Seata 会自动生成事务的二阶段提交或回滚。

写在最后

欢迎多多批评更正。

文章分类
后端
文章标签