分布式事务 分布式事务及解决方案
事务是保证业务操作完整性的定义,要么都成功,要么都失败。但是在微服务场景下,譬如用户在线下单业务会在订单系统(生成订单并扣款)和库存系统(扣库存并生成发货单)中依次执行,如何保证多个系统中业务操作的完整性?这就需要用到分布式事务。
目前常见的分布式事务解决方案分为以下几种:
2阶段提交(2PC)/XA协议
3阶段提交(3PC)
TCC
SAGA
事务消息或本地消息表
2阶段提交(2PC)
2PC将系统分为资源管理器RM和事务管理器TM,通过事务管理器统一处理成功和失败的结果
1)准备阶段
① 商城系统向协调者TM发起全局事务请求
② 协调者询问所有参与者RM是否可以提交事务,并等待结果
③ 所有参与者将执行本地事务并锁住需要的资源,记录redo log与undo log,但不提交,并根据执行结果回复ACK。
2)提交阶段/回滚阶段
① 如果所有参与者都回复OK,则通知各参与者提交本地事务,并释放事务锁定的资源
如果存在参与者回复NO,则通知各参与者回滚本地事务,并释放事务锁定的资源
② 参与者完成事务提交后,向协调者节点发送ACK消息
优点:尽可能的保证了强一致性,容易理解
缺点:①锁定资源的时间较长,TM需要等待所有参与者的执行结果才能统计本次事务是否成功或者失败。
②可能会出现数据不一致的情况,比如在提交阶段,如果因为网络原因,只有部分参与者接收到commit请求,则只有部分参与者提交了事务;而其他参与者将继续占用资源无法提交事务。
③可靠性问题,由于需要强依赖协调者,所以一旦协调者发生故障,可能所有的参与者都将出现资源一直锁定的状态。
3阶段提交(3PC)
3PC是在二阶段提交的基础上增加了询问是否可以提交的阶段,而且每个阶段都引入了超时机制。
1)准备阶段
① 协调者向所有者参与者询问是否可以发起事务请求
② 参与者根据自身数据库情况反馈结果
2)预提交阶段
① 协调者收集参与发来的准备阶段的ACK结果,如果都是OK,则发起预提交事务请求;如果存在非OK或者等待结果超时,发起取消事务执行请求
② 所有参与者将执行预提交操作,执行本地事务并锁住需要的资源,记录redo log与undo log,但不提交,并根据执行结果回复ACK。
3)提交阶段
① 如果所有参与者都回复OK,则通知各参与者提交本地事务,并释放事务锁定的资源
如果存在参与者回复NO或者等待结果超时,则通知各参与者回滚本地事务,并释放事务锁定的资源
② 参与者完成事务提交后,向协调者节点发送ACK消息
优点:由于TM和RM都存在超时机制,降低了阻塞时间,解决了单点故障问题;而且如果二阶段预提交成功,不影响三阶段的提交过程,因为三阶段RM会自动执行提交。
缺点: 数据不一致问题依然存在,如果某些参与在预提交过程失败,则协调者在向所有参与者发送取消事务提交请求时,可能会出现因为网络原因,导致部分参与者无法收到取消事务消息,而超时自动提交。
TCC
TCC是应用层的XA协议,与XA不同的是需要开发人员自己实现Try-Confirm-Cancel过程,所以TCC 模式不依赖于底层数据资源的事务支持
1)一阶段
Try 过程完成业务的资源锁定,包括数据库加锁。
2)二阶段 根据一阶段的结果判断执行Confirm或者Cancel过程
Confirm 过程完成事务的提交并释放锁,默认情况Try过程成功,则Confirm一定成功
Cancel 过程释放Try过程的锁
TCC模式存在的问题
①允许空回滚
空回滚是指如果TM发送Try消息超时或丢包,导致TM判定整个事务执行失败,从而发送Cancel消息。那么就会存在某些节点没有收到Try消息而直接收到Cancel消息。所以RM要允许只接收到回滚消息时允许操作。
②防悬挂
悬挂是指由于网络原因Try消息超时,导致TM判定整个事务执行失败,从而发送Cancel消息。但RM在接收到回滚消息之后才收到Try消息,这时应该判断该Try消息作废。
③幂等性
分布式环境中因为网络原因可能导致发送重复消息,这时需要废弃接收到的重复消息。
优点:
TCC模式不依赖与数据库的锁,所以锁定资源的范围较小。取消了事务协调者,有业务方发起请求,减少了单点故障的问题。通过Canfirm和Cancel的幂等操作,保证了数据一致性。
缺点:
TCC模式需要我们编写事务程序代码,保证分布式事务的数据一致性,增加了编码复杂性。一般金融支付相关的领域会使用TCC模式
SAGA
Saga 是指将长事务拆分为多个本地短事务并依次提交,如果所有短事务都执行成功,那么分布式事务提交;如果某个参与者执行本地事务失败,则调用补偿操作。补偿操作包括前向恢复和后向回滚。
向后回滚
向后回滚会按照执行顺序的反序依次执行回滚
前向恢复
前向恢复会不断重试,直至成功并继续往下执行
优点:saga模式比较适合将长任务分为若干个子任务运行的结构。而且并发度高,不需要长期锁定资源。
缺点:saga模式的需要定义补偿和重试策略,开发成本较高,而且saga模式并没有预处理阶段的锁定资源操作,可能会存在脏读的问题。
事务消息
事务消息其实是本地消息表的一种变体,本地消息表大致思路是将
①业务数据和本地消息数据合并成一个事务进行提交
②向消息队列发送一条消息
③其他业务系统订阅消息,在接收到消息后就会将消息写入本地消息表,如果已经执行过了,则放弃该消息
④其他业务系统执行事务,如果执行成功就会更新本系统的消息表状态和请求方系统的消息状态
如果执行失败,就不会修改请求方系统的消息状态;这时请求方会定时扫描消息表状态是否成功,如果消息未处理,就继续发送消息给消息队列。
这种方式非常依赖本地消息的处理,耦合性过高;而且消息服务性能会受到关系型数据库并发性能的局限
所以更多的情况下,普遍会采用MQ事务消息来替代本地消息表
正常流程:
①生产者先发送half消息,MQ将消息持久化后,返回ack给生成者,如果此步失败,则事务直接失败
②生成者执行本地事务,并根据执行结果发送Commit/Rollback消息
③MQ接收上一步的结果,如果是Commit则投递消息,消费者可以进行消费;如果是Rollback则删除消息
④消费者接收到消息后执行本地事务,如果失败进行重试;如果多次失败都未成功,则需要通知生成者回滚或者添加补偿方案
异常流程
①如果上述步骤②因为网络或者服务重启没有接收到Commit/Rollback消息,MQ会间隔回查生产者该业务消息的状态,所以生产者需要保存此次业务消息的最终状态
②MQ根据回查结果执行投递或者删除
优点
事务消息通过异步执行,并发性高,降低了各服务之间的耦合,而且相比于本地消息表方式降低了业务逻辑复杂性
缺点
需要添加回查接口以及重试失败之后的补偿操作
具体实现
①AT模式
seata的AT模式是一种类似与2PC的实现方案
①AT模式在一阶段就完成了事物的提交操作。它通过解析业务sql,生成sql执行前后的数据镜像“before image”与“after image”,并生成回滚sql与业务sql一起执行提交事务。
②全局事务完成,通知所有RM删除undo_log日志,释放锁资源
全局事务失败,通知所有RM。RM利用①步骤生活的镜像校验当前数据是否被修改,如果没有则直接利用undo_log回滚; 如果出现脏写需要手动处理。
注意:这里的undo_log与mysql本身的undo_log是两个东西,这里的undo_log主要是为了回滚使用的普通表。
使用seata需要解决回滚过程中脏写与脏读问题
脏数据需手动处理,根据日志提示修正数据或者将对应undo删除(可自定义实现FailureHandler做邮件通知或其他)
NOTE:seata的性能其实比较差.但是对于全局事务的实现比较方便.在不考虑大批量新增更新操作的情况下是个很好的解决方案
参照:分布式事务七种解决方案,最后一种经典了!
七种常见分布式事务详解(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)