一、分布式事务
事务是程序执行的逻辑单元,一组操作要么全部成功,要么全部失败
分布式环境下,对系统进行划分,比如电商项目,会将系统拆分成不同的服务,比如订单服务,积分服务,库存服务,寻源服务等等。
订单生成流程:
- 修改支付状态为已支付
- 调用积分服务扣减使用的积分
- 调用库存服务扣减库存
- 调用仓储系统生成物流信息
订单生成流程中如果任一步骤失败,由于在该服务之前其它服务已经完成,就会造成数据不一致。明明没有扣减库存,却使用了积分,并且支付亦显示完成。
二、两阶段提交
两阶段: 准备阶段和提交阶段
角色: 事务的发起者称协调者,事务的执行者称参与者
思路:
Prepare阶段:
- 协调者首先向所有参与者发送事务请求
- 每个参与者执行相关事务
- 执行成功不提交事务,向协调者返回信息
- 执行失败,也向协调者返回信息
Commit阶段:
- 协调者收到success请求,向所有参与者发送提交请求,并释放事务期间占用的资源,各参与者提交结束返回ack信息,当协调者收到所有参与者的ack,完成本次事务提交
- 协调者收到FAIL请求,向所有参与者发送回滚请求,参与者通过数据的undo Log进行回滚,并释放事务期间占用的资源,各个参与者向协调者反馈ack信息,当协调者收到所有参与者的ack,完成本次事务提交
两阶段提交实现起来非常简单,但是:
在分布式网络通信过程中,很容易造成消息丢失,延迟
2PC方案缺点:
- 性能问题。执行过程中,各个参与者处于阻塞状态,事务运行期间大量占用数据库资源,占用时间也比较长
- 协调者单点故障问题。协调者是2PC的核心,一旦协调者挂掉,会导致参与者收不到提交或者回滚的通知,从而导致参与者处于事务无法完成状态
- 数据一致性问题。第二阶段如果发生局部网络问题,一部分参与者收到提交信息,一部分没收到,导致数据不一致
三、三阶段提交
在2PC基础上,增加一个检查所有参与者健康状态阶段。
canCommit:
- 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
- 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
preCommit:
阶段 1 所有参与者均反馈 yes,参与者预执行事务
-
协调者向所有参与者发出 preCommit 请求,进入准备阶段
-
参与者接收preCommit请求,开始执行事务,但不提交事务
-
参与者向协调者反馈信息,成功或者失败
阶段 1 任何一个参与者反馈 no 或者没有收到任何反馈(超时或者消息丢失),中断事务
- 协调者向所有参与者发出 abort 请求。
- 无论是否收到协调者发出的 abort 请求,只要在等待协调者请求过程中出现超时,参与者均会中断事务
do Commit:
阶段2所有参与者反馈成功信息
- 协调者向所有参与者发送提交请求
- 参与者收到commit请求后会提交事务,释放数据库资源
- 所有参与者反馈ack信息
- 协调者接收所有参与者信息后完成事务提交
阶段2任一参与者反馈No信息
- 协调者向所有参与者发送回滚请求
- 参与者进行回滚操作,释放事务占据的资源
- 所有参与者均向协调者反馈信息
- 协调者接收所有参与者信息后完成事务中断
四、TCC事务
补偿事务: 针对每个操作都需要注册一个与其对应的回滚(补偿)操作
-
Try阶段:主要是对业务系统做检测及资源预锁
-
Confirm阶段:确认执行业务操作
-
Cancel阶段:取消执行业务操作
TCC事务是基于二阶段的编程模型,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现,代码侵入性很强
在订单生成流程中,使用TCC事务:
try阶段:
- 将订单状态改为支付中
- 调用积分服务占用积分
- 调用库存服务预锁库存
- 调用仓储信息生成预发货单
Try阶段如果全部调用成功,进入Confirm阶段进行正式调用,如果有一个服务调用失败,前面其它服务进行回滚。
注:在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm 一定能完整正确的提交。所以Confirm 和 Cancel 操作必须满足幂等性,如果失败的会进行不断重试直到成功为止
使用TCC分布式框架感知三个阶段状态,我们只需要编写三个状态的逻辑代码。如果提供服务节点发生宕机,TCC事务框架会记录一些分布式事务的活动日志的,可以在磁盘上的日志文件里记录,也可以在数据库里记录,保存下来分布式事务运行的各个阶段和状态,节点重启后会重新进行原来的操作