分布式事务理论与解决方案
一 与本地事务的区别
描述场景:支付订单的时候需要完成两步操作
- 修改订单表订单状态为已支付
- 修改库存表减去订单对应库存数量
本地事务:
只有唯一的数据库,订单表和库存表都位于同一个数据库,直接使用spring进行事务管理
分布式事务:跨库事务
随着系统用户量增长,业务量庞大,一个数据库无法支撑我们业务的访问压力,以及数据库表的容量无法满足我们的需求的时候。我们考虑将数据库按照业务进行拆分,将订单表单独放到订单库,将库存表单独放到库存库...
这样在一个支付方法中我们会操作两个数据库,这时候使用spring进行事务管理已经不会起作用了
跨库事务如果出现问题一般都是第一步成功了,第二步失败了(第一步如果就失败了,直接退出了):
即订单修改成功,库存数量没变(超卖),可能原因:库存不够减,库存库挂了等情况
二分布式事务基础理论
2.1 CAP理论
2.1.1 CAP
对照如下的商品服务调用主库写入数据,调用存库读取数据,主库需要向从库同步数据的场景解释下CAP
C:Consistency 一致性
一致性指的是写操作后的读操作可以读取到最新的数据状态,上图中,商品信息的写入和读取要满足一致性就是要实现:商品服务写入主库成功,在从库中也能查询到这条最新写入的数据
如何实现一致性:
- 写入主库后需要将写入的数据同步到从库
- 写入主库后,在向从库同步期间要将从库锁定,等到同步完成后再释放锁,防止数据写入后,没同步完成从而在从库查询到旧数据
分布式系统一致性的特点:
- 由于存在数据同步的延迟,因此写操作的响应会有一定的延迟,因为是同步的!!你写不只是写到主库成功还要加上从主库同步到从库的时间
2 . 为了保证数据一致性会对资源暂时锁定,等待数据同步完成释放锁定资源,因此允许出现返回错误或者响应超时
A:Avaliability 可用性
可用性指的是任何事务操作都可以得到响应结果,且不会出现响应超时或者响应错误,上图中,商品信息读取满足可用性即需要实现:从库接收到数据查询的请求能够立即响应数据查询结果,且不会出现响应超时或者响应错误
如何实现可用性:
- 写入数据哭后要将写入的数据同步到从库
- 由于要保证从库的可用性,因此不可将从库的资源锁定
- 即使数据还没有同步过来,从库也要返回查询到的数据,哪怕是旧数据,但不能返回错误或响应超时
分布式系统可用性特点:
所有请求都有响应,不会出现响应超时或响应错误
P:partition tolerance 分区容错性
分区容错性是指分布式系统的各个节点可能部署在不同的子网(不同机房等),这就是网络分区,相互之前需要进行利用网络进行通信,不可避免的会出现网络问题导致的通信失败,这时候仍然可以对外提供服务,这就是分区容忍性
上图中,商品信息写入和读取满足分区容忍性就是要实现:
- 主库向从库同步数据失败不影响对从库的读和主库的写操作
- 其中一个节点挂了不影响另外一个节点对外提供服务
如何实现分区容忍性:
- 尽量使用异步而非同步操作,例如使用异步方式将数据从主库同步到从库
- 添加从库节点,防止一个从节点挂了其他从节点还可以提供服务
分布式系统分区容忍性特点:
分布式系统具备的基本能力
2.1.2 CAP组合方式
在所有分布式事务场景中不会同时具备CAP三个特性,因为在具备了P的前提下C和A是不能共存的
对于大多数互联网应用场景,节点众多,部署分散,集群规模庞大,因此节点故障,网络故障都是常态,而且要保证服务可用性达到N个9(99.999999%),并能达到良好的响应性能来提高用户体验,因此一般会做出如下选择:保证P和A,舍弃C强一致性,保证最终一致性
-
AP:
放弃一致性,追求分区容忍性和可用性,这是很多分布式系统设计时的选择。通常AP都会保证最终一致性,BASE理论基于AP,一些业务场景比如订单退款,今日显示退款成功,明日账户才能到账,只要用户可以接受在一段时间内到账即可
如上面的商品写入,查询,完全可以实现AP,只要用户能接受所查询到的数据在一定时间内不是最新数据即可 -
CP
放弃可用性,追求分区容错性和一致性,zookeeper即是CP,但是只是理想达到,但是zk实际上实现的还是最终一致性。CP要求一次转账请求必须要等待双方银行系统都完成整个事务才算完成 -
CA
放弃分区容忍性,mysql主从变成只有一个主mysql,不考虑由于网络不通或者节点挂掉的问题,则可以实现CA,同时这样的系统不再是一个标准的分布式系统,我们最常用的关系型数据库就满足了CA
2.1.3 BASE理论
- 强一致性 VS 最终一致性
CAP理论告诉我们一个分布式系统最多只能同时一致性,可用性,分区容忍性三项中的两项。在实际应用各种,舍弃一致性,保证可用性和分区容忍性的AP使用最为广泛。
但是在实际生产中很多场景都要实现一致性,比如前边举例的主库向从库同步数据,即使不要一致性,但是最终也要将数据同步成功来保证数据一致性,这种一致性和CAP中的一致性不同,CAP中的一致性是要求在任何时间点查询每个节点数据都必须一致,它强调的是强一致性!但是最终一致性是允许在一段时间内每个节点上的数据不一样,但是经过一段时间每个节点上的数据必须一致 - BASE理论
Eventually [ɪˈventʃuəli] 最终的
BASE是Basically Available(基本可用)、 Soft state(软状态) 、Eventually [ɪˈventʃuəli] consistent(最终一致性)的缩写。BASE理论是对CAP中的AP的扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但是需要保证核心功能可用,允许数据在一段时间内是不一致的,但是最终达到一致状态,满足BASE理论的事务,我们称之为"柔性事务"
- 基本可用:分布式系统出现故障的时候,允许损失部分可用功能,保证核心功能可用,如电商网站交易付款出现问题了,商品仍然可以正常浏览
- 软状态:由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的支付中,数据同步中等状态,等到数据最终一致后状态更改为‘成功’状态
- 最终一致:最终一致性是指经过一段时间后,所有节点数据都会达到一致,如订单的支付中状态,最终会变为支付成功或者支付失败,使得订单状态与交易结果打成一致,但是需要一定时间的延迟、等待
三 分布式事务解决方案之两阶段提交协议(2PC)
以分布式事务理论为基础,针对不同的分布式场景业界常见的解决方案有2PC,TCC,可靠消息最终一致性,最大努力通知这几种
3.1 什么是2PC
2PC就是两阶段提交协议,是将整个事务流程分为两个阶段:准备阶段(prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是提交阶段
在部分关系数据库如MySQL和Oracle都支持两阶段提交协议
- 准备阶段(prepare phase):事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事务,并写本地的Undo/Redo日志,此时事务没有提交(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数据文件)
- 提交阶段(commit phase):如果事务管理器收到了参与者的执行失败或者超市消息的时候,就给每个参与者发送回滚(rollback)消息,否则,发送提交(commit)消息。参与者根据事务管理器的指令进行提交或者回滚操作,并释放事务处理过程中使用的锁资源。注意:必须在最后阶段释放锁资源
下图展示了2PC的两个阶段,分成功和失败两个情况说明
成功情况:
失败情况:
image.png第一阶段(预提交)
- 在订单库修改订单状态sql执行以后,不直接进行事务提交(订单库查该订单状态并没有修改,即将要修改的订单的那条记录锁住了),而是将sql执行结果即成功与否通知事务管理器
- 然后在库存库减库存sql执行以后,同样不直接进行事务提交,同样将sql执行结果通知给事务管理器
第二阶段(commit/rollback)
事务管理器如果收到每个库的sql执行结果都是成功,则事务管理器将会正常通知订单库和库存库,让这两个库提交事务
如果事务管理器收到第一个库sql执行成功,第二个执行失败,则会执行数据库的rollback事务回滚操作
疑问:
如果事务管理器通知订单库提交事务成功,然而在通知库存库提交事务的时候因为网络问题失败了怎么处理?
答:这种情况事务管理器会进行重试提交操作,如果多次尝试不成功,可能去采取记日志,发送告警消息给人工进行数据补偿的操作
3.2 解决方案
3.2.1 XA方案
2PC的传统方案是在数据库层面实现的,如Mysql支持2PC协议,为了统一标准减少行业的对接成本,需要指定标准化的处理模型以及接口标准,国际开放组织Open Group定义了分布式事务处理模型DTP(Distributed Transaction Processing Refrerence Model)
以新用户注册送积分为例
执行流程:
DTP模型定义如下角色:
总结:
注意:
,只可能尽量提高成功概率最大99.99%,2pc即是提高成功概率,2pc两个阶段之间的过程很短,可能在几十ms
使用2pc的分布式事务有中间件atomikos,有开源版本和商业版本
3.2.2 Seata方案
Seata是一个开源的分布式事务框架。传统的2PC的问题(需支持XA协议,资源锁在两个阶段结束后才释放,性能较差)在Seata中得到了解决,它通过在本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层(这样就不需要数据库实现XA协议)的中间件,主要优点是性能较好,且不会长时间占用连接资源,它以高效且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(2PC)以及TCC模式的分布式事务解决方案
下面讲解的是AT模式的Seata原理
与传统2PC的模型类似,Seata定义了3个组件来协调分布式事务的处理过程
- Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交和回滚,负责与RM通信协调各分支事务的提交或回滚
- Transaction Manager(TM):事务管理器,TM需要嵌入到应用程序中工作(就是一个jar包),它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令
- Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚
用上面的新用户注册送积分举例Seata的分布式事务过程
具体的执行流程如下:(注意:区别于传统的xA方案,这里的下面的过程的第2步分支事务会直接提交!至于已经提交了怎么回滚(反向操作,删除))
Seata实现2PC与传统2PC的差别
四 分布式事务解决方案之TCC
4.1 什么是TCC事务
TCC是Try,Confirm,Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理try,确认Confirm,撤销Cancel。Try操作做业务检查以及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM就会发起所有分支事务的cancel操作。若Try操作全部成功(则认为confirm一定会成功),TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试,甚至人工介入
分支事务成功的情况:
分支事务失败的情况:
TCC分为三个阶段
目前市面上的TCC框架有Seata,tcc-transaction,Hmily,ByteTCC,EasyTransaction
TCC模式
TCC(Try-Confirm-Cancel)两阶段补偿型方案
第一阶段,调用每个服务的try api,并没有真正的去执行相关操作,而是有一个字段来记录被冻结的记录,实际库存字段并没有修改
第二阶段,调用每个服务的confirm api,这时候上一步记录被冻结记录的字段将会清零,然后实际库存字段进行-1操作
如果第一阶段订单服务调用库存服务的冻结库存操作成功,调用积分服务的预加积分操作失败,则第二阶段会执行cancel操作,即调用每个服务的cancel(回滚资源接口)api,即上一步记录被冻结记录的字段直接清零,实际库存字段不进行操作
如果在第一阶段成功,在第二阶段某个环节失败,如果因为网络问题,直接进行重试,多次不成功采用定时任务等记录事务日志+人工处理补偿
TCC方式事务管理器中间件:
tcc-transaction,byteTcc,seata