场景
A需要转100元给 B,那么需要A的余额-100元,给B的余额+100元,整个转账要保证,A-100和B+100同时成功,或者同时失败。
数据库事务
假设业务单一,需求可以在单个服务,同一个数据库进行修改,完成转账。可以利用数据库事务,保证转账业务的正确完成。
分布式事务
A需要转100元给B,A、B分属不同银行,如何保证一致性?
- 跨数据库:AB使用同一套系统,但是分属不同的数据库
- 跨服务:AB转账服务使用不同的金流系统,有各自的数据库
- 混合:AB转账服务使用不同的金流系统,转账需操作到不同的数据库
原生方案(XA)和缺点
- 跨数据库XA事务:T0在两个事务提交的时间差查询,结果为A+B+100 (不符合强一致性)
- T0 + XA事务 + 隔离级别设定到 Serializable:结果为A+B (似乎OK)
- T1 + XA事务 + 隔离级别设定到 Serializable:结果为A-100+B
- 查询也使用XA + 排它锁:查询和更新全都上锁,牺牲性能和体验保持一致性
很明显这种方案下的强一致,缺点非常多。互联网应用大多是读多写少,在数据库中,原先的读可以通过多版本技术,不被锁定,快速出结果,而上述这种强一致方案:
- 一方面效率极低,所有有数据交集的数据库读读、读写、写写都必须串行执行。
- 另一方面,开发人员进行数据库多个数据查询时,也可能发生死锁,要么让开发人员小心排好访问顺序,要么接受死锁
最终一致性方案
在分布式事务进行的过程中,一致性是无法得到保证的,但是分布式事务完成之后,一致性是没问题的,严格遵守的。因此我们将分布式事务方案称为最终一致性方案。
DTM和应用
DTM
- RM-资源管理器:RM是一个应用服务,负责管理全局事务中的本地事务,他通常会连接到一个数据库,负责相关数据的修改、提交、回滚、补偿等操作。例如在前面的这个SAGA事务中,步骤2、3中被调用的TransIn,TransOut服务都是RM,业务上负责A、B账户余额的修改
- AP-应用程序:AP是一个应用服务,负责全局事务的编排,他会注册全局事务,注册子事务,调用RM接口。例如在前面的这个SAGA事务中,发起步骤1的是AP,它编排了一个包含TransOut、TransIn的全局事务,然后提交给TM
- TM-事务管理器:TM就是DTM服务,负责全局事务的管理,每个全局事务都注册到TM,每个事务分支也注册到TM。TM会协调所有的RM,将同一个全局事务的不同分支,全部提交或全部回滚。例如在前面的SAGA事务中,TM在步骤2、3中调用了各个RM,在步骤4中,完成这个全局事务
特性
- 跨语言
- 高可用
- 多进程同时轮询
应用
以saga
模式在下单事务中的应用举例
核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作
异常问题
子事务乱序问题
一般情况下,一个TCC回滚时的执行顺序是,先执行完Try,再执行Cancel,但是由于N,则有可能Try的网络延迟大,导致先执行Cancel,再执行Try。
- 空补偿: Cancel执行时,Try未执行,事务分支的Cancel操作需要判断出Try未执行,这时需要忽略Cancel中的业务数据更新,直接返回
- 悬挂: Try执行时,Cancel已执行完成,事务分支的Try操作需要判断出Cancel已执行,这时需要忽略Try中的业务数据更新,直接返回
分布式事务还有一类需要处理的常见问题,就是重复请求
- 幂等: 由于任何一个请求都可能出现网络异常,出现重复请求,所有的分布式事务分支操作,都需要保证幂等性
- 业务处理请求4的时候,Cancel在Try之前执行,需要处理空回滚
- 业务处理请求6的时候,Cancel重复执行,需要幂等
- 业务处理请求8的时候,Try在Cancel后执行,需要处理悬挂
异常处理
子事务屏障
- 开启本地事务
- 对于当前操作op(try|confirm|cancel),insert ignore一条数据gid-branchid-op,如果插入不成功,提交事务返回成功(常见的幂等控制方法)
- 如果当前操作是cancel,那么在 insert ignore 一条数据 gid-branchid-try,如果插入成功(注意是成功),则提交事务返回成功
- 调用屏障内的业务逻辑,如果业务返回成功,则提交事务返回成功;如果业务返回失败,则回滚事务返回失败