分布式事务解决方案

服务层拆分

服务层拆分也就是业务的服务化,系统架构的演进是从集中式到分布式,业务功能之间越来越解耦合。

比如电商网站系统,业务初期可能是一个单体工程支撑整套服务,但随着系统规模进一步变大,参考康威定律,大多数公司都会将核心业务抽取出来,以作为独立的服务。商品、订单、库存、账号信息都提供了各自领域的服务,业务逻辑的执行散落在不同的服务器上。

用户如果在某网站上进行一个下单操作,那么会同时依赖订单服务、库存服务、支付扣款服务,这几个操作如果有一个失败,那下单操作也就完不成,这就需要分布式事务来保证了。



分布式事务解决方案

分布式事务的解决方案,典型的有两阶段和三阶段提交协议、 TCC 分段提交,和基于消息队列的最终一致性设计。

2PC 两阶段提交

两阶段提交(2PC,Two-phase Commit Protocol)是非常经典的强一致性、中心化的原子提交协议,在各种事务和一致性的解决方案中,都能看到两阶段提交的应用。两阶段指的是commit-request阶段和commit阶段。


两阶段提交存在的问题:

1、资源被同步阻塞

在执行过程中,所有参与节点都是事务独占状态,当参与者占有公共资源时,那么第三方节点访问公共资源会被阻塞。

2、协调者可能出现单点故障

一旦协调者发生故障,参与者会一直阻塞下去。

3、在 Commit 阶段出现数据不一致

在第二阶段中,假设协调者发出了事务 Commit 的通知,但是由于网络问题该通知仅被一部分参与者所收到并执行 Commit,其余的参与者没有收到通知,一直处于阻塞状态,那么,这段时间就产生了数据的不一致性。


3PC 三阶段提交

三阶段提交协议(3PC,Three-phase_commit_protocol)是在 2PC 之上扩展的提交协议,主要是为了解决两阶段提交协议的阻塞问题,从原来的两个阶段扩展为三个阶段,增加了超时机制。

三阶段中的 Three Phase 分别为 CanCommit、PreCommit、DoCommit 阶段。

三阶段提交协议存在的问题

三阶段提交协议同样存在问题,具体表现为,在阶段三中,如果参与者接收到了 PreCommit 消息后,出现了不能与协调者正常通信的问题,在这种情况下,参与者依然会进行事务的提交,这就出现了数据的不一致性。


TCC 分段提交

TCC 是一个分布式事务的处理模型,将事务过程拆分为 Try、Confirm、Cancel 三个步骤,在保证强一致性的同时,最大限度提高系统的可伸缩性与可用性。

TCC 的具体流程如上图所示:

Try 阶段:调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。

Confirm 或 Cancel 阶段:两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。

Confirm 操作:对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。

Cancel 操作:在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。

Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?

TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。


应用 TCC 的优缺点

实际开发中,TCC 的本质是把数据库的二阶段提交上升到微服务来实现,从而避免数据库二阶段中长事务引起的低性能风险。

所以说,TCC 解决了跨服务的业务操作原子性问题,比如下订单减库存,多渠道组合支付等场景,通过 TCC 对业务进行拆解,可以让应用自己定义数据库操作的粒度,可以降低锁冲突,提高系统的业务吞吐量。

TCC 的不足主要体现在对微服务的侵入性强,TCC 需要对业务系统进行改造,业务逻辑的每个分支都需要实现 try、Confirm、Cancel 三个操作,并且 Confirm、Cancel 必须保证幂等。

另外 TCC 的事务管理器要记录事务日志,也会损耗一定的性能。


从真实业务场景分析 TCC

下面以一个电商中的支付业务来演示,用户在支付以后,需要进行更新订单状态、扣减账户余额、增加账户积分和扣减商品操作。

在实际业务中为了防止超卖,有下单减库存和付款减库存的区别,支付除了账户余额,还有各种第三方支付等,这里我们为了描述方便,统一使用扣款减库存,扣款来源是用户账户余额。

业务逻辑拆解

我们把订单业务拆解为以下几个步骤:

1、订单更新为支付完成状态

2、扣减用户账户余额

3、增加用户账户积分

4、扣减当前商品的库存

如果不使用事务,上面的几个步骤都可能出现失败,最终会造成大量的数据不一致,比如订单状态更新失败,扣款却成功了;或者扣款失败,库存却扣减了等情况,这个在业务上是不能接受的,会出现大量的客诉。

如果直接应用事务,不使用分布式事务,比如在代码中添加 Spring 的声明式事务 @Transactional 注解,这样做实际上是在事务中嵌套了远程服务调用,一旦服务调用出现超时,事务无法提交,就会导致数据库连接被占用,出现大量的阻塞和失败,会导致服务宕机。另一方面,如果没有定义额外的回滚操作,比如遇到异常,非 DB 的服务调用失败时,则无法正确执行回滚。


业务系统改造

下面应用 TCC 事务,需要对业务代码改造,抽象 Try、Confirm 和 Cancel 阶段。

Try 操作

Try 操作一般都是锁定某个资源,设置一个预备的状态,冻结部分数据。比如,订单服务添加一个预备状态,修改为 UPDATING,也就是更新中的意思,冻结当前订单的操作,而不是直接修改为支付成功。

库存服务设置冻结库存,可以扩展字段,也可以额外添加新的库存冻结表。积分服务和库存一样,添加一个预增加积分,比如本次订单积分是 100,添加一个额外的存储表示等待增加的积分,账户余额服务等也是一样的操作。

Confirm 操作

Confirm 操作就是把前边的 Try 操作锁定的资源提交,类比数据库事务中的 Commit 操作。在支付的场景中,包括订单状态从准备中更新为支付成功;库存数据扣减冻结库存,积分数据增加预增加积分。

Cancel 操作

Cancel 操作执行的是业务上的回滚处理,类比数据库事务中的 Rollback 操作。首先订单服务,撤销预备状态,还原为待支付状态或者已取消状态,库存服务删除冻结库存,添加到可销售库存中,积分服务也是一样,将预增加积分扣减掉。


执行业务操作

下面来分析业务的实际执行操作,首先业务请求过来,开始执行 Try 操作,如果 TCC 分布式事务框架感知到各个服务的 Try 阶段都成功了以后,就会执行各个服务的 Confirm 逻辑。

如果 Try 阶段有操作不能正确执行,比如订单失效、库存不足等,就会执行 Cancel 的逻辑,取消事务提交。


TCC 分布式服务组件

在业务中引入 TCC 一般是依赖单独的 TCC 事务框架,可以选择自研或者应用开源组件。TCC 框架扮演了资源管理器的角色,常用的 TCC 开源组件有 Tcc-transaction、ByteTCC、Spring-cloud-rest-tcc 等。

前面介绍过的 Seata,可以选择 TCC 事务模式,也支持了 AT 模式及 Saga 模式。

以 Tcc-transaction 为例,源码托管在 Github-tcc-transaction,提供了对 Spring 和 Dubbo 的适配,感兴趣的话可以查看 tcc-transaction-tutorial-sample 学习。


基于消息补偿的最终一致性

异步化在分布式系统设计中随处可见,基于消息队列的最终一致性就是一种异步事务机制,在业务中广泛应用。

在具体实现上,基于消息补偿的一致性主要有本地消息表和第三方可靠消息队列等。

下面介绍一下本地消息表,本地消息表的方案最初是由 ebay 的工程师提出,核心思想是将分布式事务拆分成本地事务进行处理,通过消息日志的方式来异步执行。

本地消息表是一种业务耦合的设计,消息生产方需要额外建一个事务消息表,并记录消息发送状态,消息消费方需要处理这个消息,并完成自己的业务逻辑,另外会有一个异步机制来定期扫描未完成的消息,确保最终一致性。

下面我们用下单减库存业务来简单模拟本地消息表的实现过程:

(1)系统收到下单请求,将订单业务数据存入到订单库中,并且同时存储该订单对应的消息数据,比如购买商品的 ID 和数量,消息数据与订单库为同一库,更新订单和存储消息为一个本地事务,要么都成功,要么都失败。

(2)库存服务通过消息中间件收到库存更新消息,调用库存服务进行业务操作,同时返回业务处理结果。

(3)消息生产方,也就是订单服务收到处理结果后,将本地消息表的数据删除或者设置为已完成。

(4)设置异步任务,定时去扫描本地消息表,发现有未完成的任务则重试,保证最终一致性。

以上就是基于本地消息表一致性的主流程,在具体实践中,还有许多分支情况,比如消息发送失败、下游业务方处理失败等,感兴趣的同学可以思考下。


不要求最终一致性的柔性事务

除了上述几种,还有一种不保证最终一致性的柔性事务,也称为尽最大努力通知,这种方式适合可以接受部分不一致的业务场景。

分布式事务有哪些开源组件

分布式事务开源组件应用比较广泛的是蚂蚁金服开源的 Seata,也就是 Fescar,前身是阿里中间件团队发布的 TXC(Taobao Transaction Constructor)和升级后的 GTS(Global Transaction Service)。

Seata 的设计思想是把一个分布式事务拆分成一个包含了若干分支事务(Branch Transaction)的全局事务(Global Transaction)。分支事务本身就是一个满足 ACID 的 本地事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。

在 Seata 中,全局事务对分支事务的协调基于两阶段提交协议,类似数据库中的 XA 规范,XA 规范定义了三个组件来协调分布式事务,分别是 AP 应用程序、TM 事务管理器、RM 资源管理器、CRM 通信资源管理器。

你可能感兴趣的:(分布式事务解决方案)