分布式事务处理方案大 PK!
说好了写 TienChin 项目的,最近这个分布式事务算是一个支线任务吧,今天是最后一篇,松哥再来一个短篇和小伙伴们总结一下分布式事务。
首先先说一个大原则:分布式事务能不用就不要用,毕竟这个用起来还是有一些麻烦的。当然,不用和不会用可是两码事。
1. 分布式事务基础理论
学习分布式事务,有一些基础理论需要我们先来了解下。
1.1 本地事务
本地事务是指将多条语句作为一个整体进行操作的功能,通过数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败,如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。也就是事务具有原子性,一个事务中的一系列操作要么全部成功,要么全部失败。一般来说,事务具有 4 个属性:
Atomic:原子性,将一个事务中的所有 SQL 作为原子工作单元执行,要么全部执行,要么全部不执行;Consistent:一致性,事务完成后,所有数据的状态都是一致的,以银行转帐为例,如果 A 账户减去了 100,则 B 账户则必定加上了 100;Isolation:隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;Duration:持久性,即事务完成后,对数据库数据的修改被持久化存储。
这四个属性通常称为 ACID 特性。
这块松哥之前专门录过相关的视频,这里就不再赘述了。
https://www.bilibili.com/video/BV1Eq4y1R7Ds
1.2 分布式事务
当我们的项目上了微服务之后,分布式事务就是一个比较常见的问题了,我们也会遇到很多相关的场景。
就拿我们前两天讲的商品下单的分布式事务的案例来说,像下面这样,一共有五个服务,架构如下图:
eureka:这是服务注册中心。account:这是账户服务,可以查询/修改用户的账户信息(主要是账户余额)。order:这是订单服务,可以下订单。storage:这是一个仓储服务,可以查询/修改商品的库存数量。bussiness:这是业务,用户下单操作将在这里完成。
当用户想要下单的时候,调用了 bussiness 中的接口,bussiness 中的接口又调用了它自己的 service,在 service 中,通过 feign 调用 storage 中的接口去扣库存,然后再通过 feign 调用 order 中的接口去创建订单(order 在创建订单的时候,不仅会创建订单,还会扣除用户账户的余额)。
这三个操作,我们希望他们能够同时成功或者同时失败。然而如上图所示,三个微服务都有自己的 DB,这是三个完全不同的 DB,相当于三个不同的本地事务,按照传统的本地事务规则,我们显然是无法实现三个操作同时成功或者同时失败的。
想要实现 storage、order 以及 account 中的操作同时成功或者同时失败,就得考虑分布式事务了。
最后,我们再来看看分布式事务的概念:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于的不同节点之上,数据库的操作执行成功与否,不仅取决于本地 DB 的执行结果,也取决于第三方系统的执行结果。而分布式事务就保证这些操作要么全部成功,要么全部失败。本质上,分布式事务就是为了保证不同数据库的数据一致性。
1.3 CAP
CAP 定理(CAP theorem),有时候又被称作布鲁尔定理(Brewers theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
一致性(Consistency):在分布式系统中的所有数据备份,在同一时刻是否具备同样的值。(等同于所有节点访问同一份最新的数据副本)。
可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)。
分区容错性(Partition tolerance):这个我觉得可能对有的小伙伴来说有点难以理解,我就简单说一下,先来说分区:因为我们是分布式系统,分布式系统中不同的微服务位于不同的网络节点上,当发生网络故障或者节点故障的时候,不同的服务之间就无法通信了,也就是说发生了分区;再来看分区容错性:这是说,当我们的系统中出现分区的时候,系统还要能运行,不能罢工!一般来说,在一个分布式系统中,分区发生的概率还是比较大的,不会发生分区的系统,那就不是分布式系统了,而是单体应用了。
CAP 原则的精髓就是要么 AP,要么 CP,要么 AC,但是不存在 CAP。因为在分布式系统内,P 是必然的发生的,不选 P,一旦发生分区,整个分布式系统就完全无法使用了,这样的系统就太脆弱了。所以对于分布式系统,我们只能能考虑当发生分区错误时,如何选择一致性和可用性(选择一致性,意味着服务在某段时间内不可用,选择了可用性,意味着服务虽然一直可用但是返回的数据却不一致)。
而根据一致性和可用性的选择不同,开源的分布式系统往往又被分为 CP 系统和 AP 系统。
当一套系统在发生分区故障后,客户端的任何请求都被卡死或者超时,但是系统的每个节点总是会返回一致的数据,则这套系统就是 CP 系统,经典的比如 Zookeeper。
如果一套系统发生分区故障后,客户端依然可以访问系统,但是获取的数据有的是新的数据,有的还是老数据,那么这套系统就是 AP 系统,经典的比如 Eureka。
1.4 BASE
因为无法同时满足 CAP,所以又有了 BASE 理论,BASE 理论指的是:
基本可用 Basically Available:分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。软状态 Soft State:允许系统存在中间状态,而该中间状态不会影响系统整体可用性。终一致性 Eventual Consistency:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
BASE 理论的核心思想是即便无法做到强一致性,但应该采用适合的方式保证最终一致性。
BASE 理论本质上是对 CAP 理论的延伸,是对 CAP 中 AP 方案的一个补充。
1.5 刚柔并济
事务有刚性事务和柔性事务之分。
刚性事务(如单数据库中的本地事务)完全遵循 ACID 规范,即数据库事务正确执行的四个基本要素:
原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)
柔性事务,主要就是只分布式事务了,柔性事务为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求,遵守 BASE 理论:
基本业务可用性(Basic Availability)柔性状态(Soft state)最终一致性(Eventual consistency)
当然,柔性事务也部分遵循 ACID 规范:
原子性:严格遵循一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽持久性:严格遵循
柔性事务有不同的分类,不过基本上都可以看作是分布式事务的解决方案:
两阶段型:分布式事务二阶段提交,对应技术上的 XA、JTA/JTS,这是分布式环境下事务处理的典型模式。补偿型:我们之前文章介绍的 TCC,就算是一种补偿型事务,在 Try 成功的情况下,如果事务要回滚,Cancel 将作为一个补偿机制,回滚 Try 操作;TCC 各操作事务本地化,且尽早提交(没有两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为。TCC 是将资源层的二阶段提交协议转换到业务层,成为业务模型中的一部分。异步确保型:将一些有同步冲突的事务操作变为异步操作,避免对数据库事务的争用,如消息事务机制。最大努力通知型:通过通知服务器(消息通知)进行,允许失败,有补充机制。
2. 分布式事务实践
2.1 XA
先来说说 XA。
XA 是一种典型的两阶段提交(2PC,Two-phase commit protocol),而两阶段提交是一种强一致性设计,在两阶段提交中,一般会引入一个事务协调者的角色来协调管理各个事务参与者,例如我们之前文章中使用的 seata-server 其实是就是一个事务协调者。所谓的两阶段分别指的是准备和提交两个阶段。
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
XA 规范描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范的目的是允许多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使