Spring Cloud Alibaba系列(三):深入理解分布式事务(原理篇)

314 阅读14分钟

作为目前最热门的分布式事务解决方案,Seata已经逐渐收到越来越多开发者的关注。不少企业已经Seata作为分布式事务的解决方案应用到生产中,本文将抛开原理,从实践出发来了解Seata到底是怎么用的。

Seata官网:seata.io/zh-cn/docs/…

1. 初识分布式事务

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下,提供高性能和简单易用的分布式事务服务(官方网站介绍)。从中我们可以提取出关键词分布式事务高性能简单易用等几个特点。

1.1 什么是分布式事务

在传统的单体应用架构中,各个业务模块会共用同一套数据源,同一数据源的事务可以通过数据库的事务机制来实现,就像在本地实现事务一样,因此也被称为本地事务。

image.png

随着业务增长,功能模块会被划分成多个垂直应用,也从原来的单体应用架构升级为垂直应用架构,此时不同的应用开始使用不同的数据源。那么存在服务之间的相互调用,每个服务对应一个分支事务,每个事务之间彼此隔离,那么如和让这多个事务当成一个整体事务来进行处理呢?因此引入分布式事务的概念和问题,如果保证在分布式系统中保证数据的一致性。

image.png

1.2 分布式事务的痛点

分布式事务由多个本地事务组成,传统的本地事务支持ACID属性:

  • 原子性(Atomicity):是指事务作为一个整体,要么全部成功,要么全部失败。
  • 一致性(Consistency):是指数据库的数据最终达到一致性,这个一致性主要是业务上的一致性,比如转钱场景中,A账号余额1000,B账户余额200,A给B转了500,那么最终一致性数据是A账号余额500,B账户余额700。不能存在A账号余额500,B账户余额200的情况。
  • 隔离性(Isolation):指事物之间的内部数据是隔离的,互不影响
  • 持久性(Durability):是指事物的操作能够被持久保存下来,后续的其他操作不会改变事物的结果

从中可以看出原子性、隔离性、持久性都是为了保证一致性的。所以在分布式事务场景中,我们最终也是考虑的如何保证一致性就行了。分布式事务的一致性又分为:

  • 强一致性:数据更新成功后,所有的副本的数据都是一致的
  • 软一致性:数据更新成功后,不能保证所有的副本都能读到数据,经过一定的时间窗口后,数据会达到一致性
  • 最终一致性:软一致性的一种情况,数据更新成功后,经过一段时间,数据会达到一致性。

可见强一致性是一种很强硬的操作,每个子操作都必须同步才能继续后续操作,因此性能会很慢。 最终一致性则是处理过程不做强制同步,等到一段时间后,达到最终一致,因此适用于一致性要求不高的场景。

2. 常见的分布式解决方案

分布式事务的方案主要就围绕如何实现一致性来提出不同的解决方案。因此有的解决方案实现强一致性,有的解决方案实现最终一致性,不同的方案便可适用于不同的业务场景中。

2.1 2PC-两阶段提交

2PC两阶段提交是基于X/A协议来实现的,它将整个事务划分成两个阶段:准备阶段,提交阶段,并以角色为维度定义了事务协调者和事务参与者。它是强一致性的解决方案。

2.1.1 角色
  • 事务的协调者:事务的协调者主要是收集各个参与者的事务状态并发起决议是提交还是回滚,因此占据重要位置
  • 事务的参与者:事务的参与者是指各个分支事务,事务的参与者会根据事务协调者的决议在本地进行提交和回滚
2.1.2 步骤
  • 准备阶段:事务的协调者向所有的参与者发送准备消息,参与者在本地执行事务,但是不会提交,因此会锁住所需资源。所有的参与者向事务的协调者反馈准备成功或者失败结果。
  • 提交阶段:事务的协者整根据事务的参与者反馈的准备结果来决议,如果存在一个参与者准备失败的反馈,则决议回滚,如果全部成功,则决议提交。因此提交阶段,事务的协调者向参与者发送提交和回滚消息,事务的参与者根据决议来提交或者回滚本地事务。

image.png

  • 在第一阶段,事务协调者是同步阻塞等待所有参与者的反馈,如果有个参与者因为网络或者宕机了,此时会有协调者超时机制,向其它的参与者发送决议回滚
  • 在第二阶段,事务的参与者收到提交或者回滚执行失败了该怎么办,可以才用失败重试的方法,超过一定次数就得人工介入了。
2.1.3 优点
  • 最终一致性:两阶段提交需要等到所有的节点都准备好了才执行提交事务,因此保证了分布式事务的最终一致性
  • 协调者:两阶段提交引入了协调者的角色,使得各个参与者不需要互相感知彼此的状态,状态统一由协调者维护
2.1.4 缺点
  • 单点故障:由于协调者的重要性,因此当协调者宕机后,可能会出现数据不一致性的问题。可以采用集群部署或者主备切换的方式部署协调者。
  • 事务阻塞:第一阶段需要等待所有的参与者准备好,才会进行第二阶段,因此第一阶段所有的参与者需要对资源进行锁定,第三方事务就不能访问被锁住的资源,并发性低。
  • 数据脑裂:在第二阶段中,由于协调者向参与者发送了提交和回滚决议,由于网络抖动导致或者分区故障导致部分参与者未收到决议,则一直处于阻塞状态,因此会造成数据不一致。
  • 事务状态不确定:如果协调者和参与者都宕机了,那么事务的状态是不确定的。
2.1.5 使用场景
  • Seata的AT模式是基于二阶段提交来进行改进的

2.2 3PC-三阶段提交

三阶段提交相对于二阶段提交,引入一个预提交阶段,主要引入以下两点改进:

  • 事务的协调者和参与者都引入了超时机制
  • 在准备阶段和提交阶段插入了一个预提交阶段,从而保证提交前,所有的参与者都处于一致的状态
2.2.1 步骤
  • 准备阶段(canCommit):协调者向所有的参与者发送commit请求,如果参与者可以提交,则返回yes,否则返回no

  • 预提交阶段(preCommit):协调者收集到参与者的反馈后,如果有一个参与者返回no或者超时,则执行事务中断,否则进行到下一步提交阶段

  • 提交阶段(doCommit):协调者向所有的参与者发送commit请求,参与者收到请求后在本底执行事务的提交,并向协调者反馈执行结果,协调者根据执行结果进行完成事务。

2.2.2 优点
  • 参与者引入了超时机制:如果参与者长时间未收到协调者的决议后,不会一直阻塞,会直接提交事务
  • 预提交保证了参与者处于一致的状态:在2PC的第一阶段中,事务协调者一上来就发送commit请求,还不知道有没有参与者准备好了吗,所以导致一部分参与者提前锁住同步资源。因此3PC引入的can commit阶段保证了所有的参与者都处于一致的状态,才进行commit请求,在本底执行事务。
2.2.3 缺点
  • 数据不一致性的问题依然存在:如果参与者长时间未收到协调者的请求,则默认是提交事务,但是协调者实际想发的是回滚决议呢?那就会导致数据不一致性的问题
  • 流程复杂:引入了新的操作步骤,会导致整体流程更加复杂,性能也会降低。
  • 理论性强:虽然相比2PC引入了超时机制和预提交阶段,但是也不能彻底解决数据一致性的问题,因此还是处理理论阶段

2.3 事务消息

可以通过一些中间件来实现分布式事务,RocketMQ支持事务消息。RocketMQ有半消息的概念,所谓半消息是指生产者投递给MQ的消息不会被立马被消费者消费,需要二次确认才可以被消费者消费,根据这个特点,可以借助RocketMQ的半消息来实现分布式事务的最终一致性。

2.3.1 概念
  • 半消息:消息生产者发送消息给MQ Server,此消息不会被消费者消费到。
  • 事务回查:事务的发起方在执行本地事务后,长时间未向MQ Server发送响应,此时MQ Server会主动去查询事务发起方执行的状态。
2.3.2 步骤

以订单服务和库存服务为例,来讲述下Rocket MQ事务消息的实现步骤

  • 首先订单服务先向MQ Server发送一条半消息,此消息不会被库存服务消费到
  • MQ Server接收到半消息后,会向订单服务发送半消息发送成功的通知
  • 如果半消息发送成功,那么订单服务回执行本地事务,并根据执行结果决定是提交还是回滚
  • 订单服务执行完本地事务向MQ Server发送提交或者回滚请求
  • 如果订单服务执行时间较长,MQ Server长时间未收到订单服务的响应,会通过回调接口去查询事务的执行状态,因此需要一张记录事务执行的日志表来记录事务的状态
  • MQ Server根据提交/回滚响应来处理半消息的状态,如果是提交则将半消息移至目标队列,被库存服务进行消费。如果是回滚操作,则将半消息删除
  • 库存服务消费到提交事务的消息后,会在本地执行事务,如果事务执行失败需要不断重试,超过一段时间后需要记录到错误日志表中,需要人工干预。

image.png

2.3.3 优点
  • 最终一致性:事务消息保证的是最终一致性,因此订单服务下单成功,库存服务会不断重试去扣减库存,最终会保证库存被扣减。
  • 异步回查:如果事务的发起放长时间未向MQ Server确认消息时,会通过回查接口主动去查询事务发起放方的执行状态,不会一直阻塞。
2.3.4 缺点
  • 业务耦合度高:RocketMQ的事务消息需要大量的业务代码开发,需要实现业务回查接口,并且执行流程也比较复杂

2.4 本地消息表-最大努力通知型

本地消息表的核心是将分布式事务步骤通过日志来异步执行,日志可以存储在本地文件,一般是数据库。本地消息表需要额外新增一张表,记录事务的执行状态,然后通过定时任务不断重试失败的记录或者人工介入。

2.4.1 步骤
  • 新增一张消息日志表,记录事务的执行结果。事务的发起方需要在消息表中记录一条日志,并和业务逻辑在同一个事务中。
  • 事务发起方向MQ中间件中发送一条下单消息,下游服务消费到这条消息后,实现业务操作,完成本地事务,如果失败则进行重试。并将结果发送到MQ中。
  • 事务的发起放消费到下游事务的执行结果,修改本地消息表的状态为已完成。
  • 后台定时任务不断扫描日志表中未完成的事务日志,重试发送消息到MQ中,因此下游业务需要保证接口的幂等性
2.4.2 优点
  • 最终一致性:不能保证数据的实时一致性,只能保证最终一致性
2.4.3 缺点
  • 代码侵入性高:需要新增额外的消息表,并且和业务代码耦合在一起
  • 重试的幂等性:重试接口需要保证幂等性

2.5 TCC-补偿型

TCC补偿型是一种补偿型解决方案,主要是分为三个阶段: try, commit, cancel阶段。每个业务操作都需要实现提交接口和回滚接口。类似于2pc阶段提交,需要参者达到一致的状态时,才会进行事务的提交。

2.5.1 阶段
  • 尝试阶段(Try):对资源进行检测和预留,但是不提交事务,比如进行余额校验,资金的冻结
  • 提交阶段(commit):主要是对业务进行确认,一般来说,try阶段提交成功,confirm必定成功
  • 回滚阶段(cancel):如果confirm阶段失败,则需要调用cancel阶段来进行回滚补偿操作,并进行预留资源的释放

image.png

2.5.2 优点
  • 强一致性:保证分布式数据的强一致性
2.5.2 缺点
  • 业务耦合度高:每个分支事务都必须要实现三个接口,对业务侵入性强
  • 需要保证幂等性:confirm和cancel阶段可能会执行失败重试,因此需要保证接口的幂等性

3 Seata提供的分布式事务

Seata 是一款开源的分布式事务框架,它提供AT、TCC、Saga、XA不同的事务模式,为用户提供一站式的分布式事务解决方案。

3.1 AT 模式

AT模式是基于两阶段提交的进一步优化:

  • 一阶段:业务数据和回滚日志在同一事务中提交,释放本地锁和资源
  • 二阶段:提交操作是异步化,异步删除回滚日志,速度非常快。
    回滚是反向补偿操作,通过回滚日志恢复到事务前的状态

3.2 TCC 模式

TCC模式是把自定义的分支事务纳入全局事务的管理,也是基于两阶段提交进行改进的,每个分支事务都必须实现try、confirm、cancel接口。具体详情查看上面的介绍。

image.png

3.3 Saga 模式

Saga模式是Seata提供的长事务解决方案,业务流程中的每个参与都提交本地事务,如果出现一个参与者提交失败,则回滚之前执行成功的参与者。一阶段的正向和补偿逻辑都是由业务来实现。适用于业务流程长,业务逻辑多的场景。

image.png

4. 总结

  • 分布式事务是为了解决多个分支事务的数据一致性问题的。
  • 一致性包含强一致性,弱一致性和最终一致性,大部分业务场景满足最终一致性就可以
  • 分布式解决方案包含:两阶段提交、三阶段提交、事务消息、本地消息表、TCC模式等五大模式,其中事务消息、本地消息表、TCC模式保证了最终一致性,两阶段提交、三阶段提交保证了强一致性
  • Seata是一款开源的分布式解决方案,支持AT、TCC、Saga以及XA模式