2. 分布式事务理论
需要分布式事务案例:
实例 1
保存 细分商品(sku) 的同时要保存 相应的商品属性信息 (商品微服务)
同时 要增加库存信息(库存微服务);
要增加 优惠套餐及套餐明细(优惠微服务);
实例 2
订单支付确认后, 分别要修改订单状态(订单微服务);
修改商品的库存(库存微服务);
修改客户的积分,并记录积分变化日志(客户微服务).
传统的一个工程内为了保证数据的一致性,使用本地事务。本地事务只能解决同一工程中的事务问题,而现在的场景更加复杂,关系到多个工程模块,怎么保证要么都成功,要么都失败?
分布式事务就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
分布式事务场景:不同应用相同数据库,相同应用不同数据库,不同应用不同数据库。
分布式事务产生的原因:分布式系统异常除了本地事务那些异常之外,还有:机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失…
2.1. 分布式事务基础
数据库的 ACID 四大特性,已经无法满足我们分布式事务.
2.1.1. CAP
分布式存储系统的CAP原理(分布式系统的三个指标):
-
Consistency(一致性):在分布式系统中的所有数据备份,在同一时刻是否同样的值。
对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
-
Availability(可用性):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(要求数据需要备份)
-
Partition tolerance(分区容忍性):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们无法避免的。所以我们只能在一致性和可用性之间进行权衡,没有系统能同时保证这三点。要么选择CP、要么选择AP。
效果演示动画:http://thesecretlivesofdata.com/raft/
2.1.2. BASE
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来看看BASE中的三要素:
-
Basically Available(基本可用)
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果;这里有2个重要的点:
- 时效变化:响应时间可以适当延长;如大促时,响应时间可以适当延长。
- 功能变化:部分功能降级;如给部分用户直接返回一个降级页面,从而缓解服务器压力;但是结果必须是明确的,如降级页面中有明确的结果,不让用户困惑。
-
Soft state(软状态)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
用户在商城下单时,因网络超时等因素,订单处于“支付中”的状态,待数据最终一致后状态将变更为“成功”状态。
-
Eventually consistent(最终一致性)
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。因此所有客户端对系统的数据访问最终都能够获取到最新的值,而这个时间期限取决于网络延时,系统负载,数据复制方案等因素。
例如,银行系统中的非实时转账操作,允许 24 小时内用户账户的状态在转账前后是不一致的,但 24 小时后账户数据必须正确。
BASE模型是传统ACID模型的反面,不同于ACID,BASE强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。
2.2. 分布式事务解决方案
分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎可以说是无法避免。
主流的解决方案如下:
- 基于XA协议的两阶段提交(2PC)
- TCC编程模式
- 消息事务+最终一致性
2.2.1. 两阶段提交(2PC)
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。
第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
第二阶段:事务协调器要求每个数据库提交数据。
其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息。
目前主流数据库均支持2PC【2 Phase Commit】
XA 是一个两阶段提交协议,又叫做 XA Transactions。
MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。
- 两阶段提交涉及多次节点间的网络通信,通信时间太长!
- 事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多。
- XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换会导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
对应的开源框架:atomikos
2.2.2. TCC补偿式事务
是一种编程式分布式事务解决方案。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。
- Try:主要是对业务系统做检测及资源预留
- Confirm:真正执行业务,不作任何业务检查;只使用Try阶段预留的业务资源;Confirm操作满足幂等性。
- Cancel:释放Try阶段预留的业务资源;Cancel操作满足幂等性。
整个TCC业务分成两个阶段完成:
第一阶段:主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败,进入第二阶段。
第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功,则活动管理器调用所有从业务活动的confirm操作。否则调用所有从业务服务的cancel操作。
举个例子,假如 A账户 要向 B账户 转账 30 元,思路大概是:
我们有一个本地方法,里面依次调用
-
首先在 Try 阶段,要先检查 A 账户 的钱是否充足,并把这 30元锁住,B 账户也冻结起来。
-
在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
-
如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
缺点:
- Canfirm和Cancel的幂等性很难保证。
- 这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚Cancel,而且依赖的服务也非常少的情况。
- 这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。
不少大公司里,其实都是自己研发 TCC 分布式事务框架的,专门在公司内部使用。
国内开源出去的:ByteTCC , TCC-transaction , Himly。
2.2.3.最大努力通知型方案
按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接口进行核对。这种方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种方案也是结合MQ进行实现,例如:通过MQ发送http请求,设置最大通知次数。达到通知次数后即不再通知。
支付的流程是APP要求选择支付宝或者微信支付,当在微信平台上付完之后,回调我方系统。比如创建一个订单,如果这个订单收到钱了,则支付宝或微信回调一下,告诉我方系统把这个订单的状态改成已支付。订单状态原来是未支付,我方系统通过主动回调,主动查询第三方充值结果,这就是最大努力通知方案。
这里会涉及重复回调,为什么呢?因为我方支付系统和支付宝系统是两家公司开发的,没法把他们放到一个注册中心里,所以只能通过接口调用,只要是接口的方式就可能有调不到的情况,其解决方案,第一重复调用,调用一次后看是否返回成功。如果反馈的结果是失败,那么隔一段时间再次调用。第二点保证是,我方因有长时间未支付订单,我方主动去第三方查,看是不是钱已经到支付宝了,而支付宝没有回答我,或者支付宝回答我但出错了,这样的话,通过敌我双方都各尽最大努力,通过支付宝重复通知以及我方主动查询,通过这两种方案就能保证双方通知达成。
简单来说就是支付宝未通知到则重复通知,我方实在等不到就主动去查,这就是最大努力通知方案,即:你最大努力通知我,我也尽我最大努力去你那里查。
案例:银行通知、商户通知等(各大交易业务平台间的商户通知:多次通知、查询校对、对账文件),支付宝的支付成功异步回调
2.2.4.消息事务+最终一致性
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。
虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。
适用于高并发最终一致
低并发基本一致:二阶段提交
高并发强一致:没有解决方案