1、基础
1.1、事务四大特性ACID
- 原子性(Atomic):一个事务中的操作,要么一起执行,要么不执行
- 一致性(Consistency):事务执行后,数据库的一致性约束没有被破坏,比如转账中账户扣除款项后必须给另一个账户加上款项
- 隔离性(Isolation):两个并发事务互不干扰
- 持久性(Durability):事务完成后,事务操作的数据会持久化存储
1.2、事务分类
- 本地事务:数据库和应用程序在同一台服务器中,依靠关系数据库来保证事务的执行
- 分布式事务:应用程序分为多个服务,需要通过网络协作完成事务执行
1.3、分布式事务应用场景
- 微服务架构,即服务跨JVM进程
- 单体系统(单服务)访问多个数据库实例
- 多服务访问同一个数据库实例
1.4、CPA理论
1.4.1、CPA
- 一致性Consistency,写操作之后的读操作,能读取到最新的数据(由于数据存在同步,写操作有一定延迟)
- 可用性Availability,任何事物操作都可以得到响应,且不会出现响应超时或响应错误(数据同步不应影响操作)
- 分区容忍性Partition tolerance,不因为网络问题导致节点之间通信失败,数据同步失败不影响读写操作,一个结点宕机不影响其它结点继续对外服务
1.4.2、CPA组合
- AP,牺牲一致性,满足可用性和分区容忍性,用的比较多,比如订单退款,不需要立即到款
- CP,牺牲可用性,满足一致性和分区容忍性,比如跨行转账中,在一定时间(30S)内不再满足新的转账需求,直至完成转账事务(Zookeeper)
- CP,牺牲分许容忍性,满足一致性和可用性,不考虑网络不通或者节点宕机,此时系统不是标准分布式系统
- 一般开发中,选择AP,牺牲一致性,仅保证最终一致性
1.5、BASE理论
1.5.1、强一致性和最终一致性
- 强一致性,即CPA理论中的一致性,每个时间点数据都必须一致,任何时候都能读取到最新数据
- 最终一致性,允许一段时间内数据不一致,但是经过一段时间后每个节点数据必须一致
1.5.2、BASE理论三个特征,满足三个特征,即为柔性事务
- 基本可用Basically Available,分布式系统出现故障时,允许损失部分功能,但是核心功能继续可用,比如电商网站中交易付款出问题,依旧可以浏览商品、添加购物车
- 软状态Soft state,不要求强一致性,所以系统中数据存在中间状态(软状态),此时状态不影响系统可用性,比如订单处于“支付中”
- 最终一致性Eventually consistent,经过一段时间后,所有节点数据将一致,比如“支付中”的订单最终必然“支付成功”或“支付失败”
2、分布式解决方案之2PC两阶段提交
2.1、两阶段
- 准备阶段,事务管理器给每个参与者发送Prepare消息,每个参与者在本地执行事务,并写入Undo/Redo日志,此时尚未提交
- 提交阶段,如果事务管理器收到参与者执行失败或者超时消息,那么给参与者发送回滚消息;否则发送提交消息。事务参与者在收到回滚或提交消息后,释放事务处理中使用的锁资源(最后阶段释放)
两阶段成功:
两阶段失败:
2.2、XA两阶段解决方案
- 2PC传统方案是在数据库层面实现分布式事务,为了统一处理模型和接口标准XA,国际组织定义了分布式事务处理模型DTP
2.2.1、DTP角色
- AP应用程序;
- RM资源管理器,控制分支事务,理解为一个数据库实例,执行实际业务操作,不提交事务,整个全局事务时锁定资源;
- TM事务管理器,控制全局事务,通过XA接口通知RM事务开始、结束、回滚、提交,提交阶段结束释放资源锁
- DTP规定TM和RM之间的通讯接口规范为XA
2.2.2、执行流程-用户注册送积分
- AP有两个数据库,用户库和积分库
- AP通过TM通知用户库RM新增用户,同时通知积分库RM增加积分,此时RM未提交事务,用户和积分资源锁定
- TM收到执行回复,有失败或超时,向两个RM发送回滚事务请求,回滚结束则资源锁释放
- TM收到执行回复,全部成功,像两个RM发送事务提交请求,提交完毕则资源锁释放
2.2.3、方案问题
- 需要本地数据库支持XA协议
- 资源锁需要等提交阶段后释放,性能差
3、分布式事务解决方案之TCC
3.1、三个操作
- Try,负责业务检查、资源预留,仅是一个初步操作,与后续Confirm一起构成完整业务逻辑
- Confirm,负责业务确认操作,Try阶段所有分支事务执行结束后执行Confirm,认为只要Try成功,Confirm便成功,如果Confirm不成功,要引入重试机制或人工处理
- Cancel,实现与Try相反的操作,即回滚,认为Cancel一定成功,如果不成功,要引入重试机制或人工处理
3.2、执行流程
- TM发起所有分支事务Try操作,任何一个分支Try失败,TM将发起所有分支事务Cancel操作
- 如果全部分支事务Try成功,TM将发起所有分支事务Confirm操作
- 如果Cancel或Confirm操作执行失败,那么TM会重试
分支事务(Try)执行成功:
分支事务(Try)执行失败:
3.3、TCC解决方案
- tcc-transaction、Hmily、ByteTCC、EasyTransaction等
3.3.1、处理三种异常(均可在数据库中增加相应表格来记录,记录全局事务ID以及相关操作是否已经执行),需要自己手写这三个方法,所以代码侵入性很强
- 空回滚,在没有调用Try时,调用Cancel方法,那么需要识别这是一个空回滚。出现原因是TM发送Try时分支事务宕机或网络异常,分支事务没有执行Try,然后分支事务收到TM发送的Cancel(可以加一个表,在执行Cancel前差是否执行了了Try)
- 幂等,幂等是指多次发送请求,结果都一样,仅执行一次,Confirm和Cancel会重试,所以需要保证幂等性(可以加一个表,在执行Confirm和Cancel前查是否已经执行过,执行过会在表中插入一个记录)
- 悬挂,Cancel接口比Try接口先执行。出现原因是RPC调用分支事务Try时,先注册分支事务,再执行RPC调用,如果此时RPC调用的网络发生拥堵超时,那么TM通知RM回滚事务,回滚完成后RPC请求来到,此时若是Try执行了,后面便没有了Cancel。(可以加一个表,在执行Try前查看是否已经执行Cancel)
3.3.2、A向B转账例子
- 优化前:
账号A:(如果Try没有执行,如果执行了cancel,便增加了30元)
账号B:(如果在Try中增加了30元,便可以花掉,如果再执行cancel,没有钱扣掉了咋整)
- TCC优化后
账号A:
账号B:(将增加前放置在Confirm中,最后执行,不再需要cancel,其实我觉得不够完美,总感觉有些问题,B账户confirm执行失败咋整呢,哦,多次执行,再有问题人工介入)
3.4、TCC总结
3.4.1、2PC和TCC对比
- 2PC,利用数据库DB实现事务处理,使用在跨库层面
- TCC,利用代码业务逻辑实现,可以自己定义数据操作粗细粒度,降低锁冲突,提高吞吐量
3.4.2、TCC弱点:
- 自己实现Try、Confirm、Cancel操作,代码入侵性强,实现难度大
- 考虑网络状态、系统故障来判断实现不同回滚策略
4、分布式事务解决方案之可靠消息最终一致性
- 可靠消息最终一致性指:事务发起方(生产者)执行完本地事务后发出一条消息,事务参与方(消费者)最终一定能收到消息并处理成功
4.1、消息最终一致性几个问题
- 本地事务与消息发送的原子性问题:事务发起方在本地执行成功后必须将消息发送出去,否则便要丢弃消息,要么都成功,要么都失败
- 事务参与方接收消息的可靠性:事务参与方必须从消息中间件中接收到消息,如果接收失败可多次接收
- 消息重复性消费问题:事务参与方接受消息失败,多次发送,可能导致重复性消费,需要满足幂等性
4.2、本地消息表方案
以注册送积分为例:
- 保证原子性:用户服务在本地新增用户和新增积分消息日志,通过本地事务保证其一致,利用数据库操作与消息日志操作满足原子性
- 保证消息发送:启动定时任务线程,定时将消息日志中的消息发送到MQ,如果收到MQ返回成功信息,便将该日志删除;否则在下次定时任务中重试
- 保证消费者消费:利用ACK(消息确认)机制,消费者(积分服务)监听MQ,消费者接收并消费后像MQ发送ACK,那么MQ便不再向其发送消息,否则MQ会不断发送
4.3、Rocket MQ事务消息方案
4.3.1、执行流程
- MQ发送方将事务消息发送至MQServer,MQ将事务标记为Prepared状态(不可消费)
- MQServer回复MQ发送方,消息已经发送成功
- MQ发送方通知执行本地事务
- 若本地事务执行成功,MQ发送方像MQServer发送提交信息,MQServer收到后将事务标记为可消费状态;如果本地事务执行失败,MQ发送方像MQServer发送回滚信息,MQServer收到后将删除事务消息
- MQ订阅方消费成功后,像MQServer回应ACK,否则MQServer重复发送消息
- 事务回查:如果执行本地事务过程中,执行出错或者超时,那么MQServer会不断询问同组其它生产者来获取事务执行状态
4.3.2、RocketMQ总结
- 解决了本地事务与消息发送的原子性问题;保证了参与方接受消息的可靠性
- 适用于执行周期长,对实时性要求不高的场景
5、分布式事务解决方案之最大努力通知
5.1、最大努力通知
- 即发起方通过一定机制尽最大努力将业务处理结果通知接收方
5.1.1、措施包括
- 具有消息重复通知机制:多次向接收方发送通知
- 消息校对机制:尽最大努力后发送方也没有收到消息,或者接收方消费消息之后需要重复消费,那么此时接收方可以主动向通知放查询消息
5.2、最大努力通知和可靠消息一致性区别
5.2.1、解决思想
- 可靠消息一致性保证将消息发送出去,并且将消息发送给接收方,由发起方保证
- 最大努力通知,发起方尽力将消息发送给通知方,如果收不到,那么接收方可以自己来查,由接收方来保证
5.2.2、业务场景不同
- 可靠消息一致性关注交易过程事务一致,以异步方式完成
- 最大努力通知关注交易后的事务通知,将交易结果可靠地通知出去
5.2.3、技术解决方案不同
- 可靠消息一致性解决消息从发送到接收的一致性,整个过程要一致
- 最大努力通知无法保证从发送到接收的一致性,仅保证接收消息的可靠性,即当收不到消息时,接收方自己主动去查
5.3、最大努力通知解决方案,利用ACK机制
5.3.1、方案一流程
- 发起方将通知发送给MQ(普通消息机制),如果消息没有发送出去,那么可以由接收通知方主动请求通知方查询
- 接收通知方监听MQ
- 接收通知方接收消息,业务处理完成后回应ACK
- 接收通知方如果没有回应ACK机制,那么MQ会重复通知(要求收到通知方的ACK),会按照1min、5min、10min...等增大时间间隔进行重复通知
- 接收通知方可以通过消息校对接口直接请求发起通知方,验证消息一致性
5.3.2、方案二流程
- 发起通知方将通知发送给MQ,使用可靠消息一致性方案中的事务消息保证本地事务与消息的原子性
- 通知程序监听MQ,接收MQ消息(方案一中接收通知方直接监听MQ,方案二中使用通知陈旭监听MQ。方案二中,如果通知程序没有回应ACK程序给MQ,那么MQ会重复通知)
- 通知程序通过互联网接口协议(HTTP)调用接收通知方案
- 通知接收方可以通过消息校对接口与发起通知方进行消息一致性校对
5.3.3、两方案对比
- 方案一中,接收方监听MQ,可用于内部应用之间的通知
- 方案二中,通知程序监听MQ,然后通知程序利用HTTP对外进行消息通知,可用于外部应用之间的通知(不会允许外部系统监听MQ,如微信支付)
5.4、最大努力通知总结
- 对一致性的要求最低,适用于处理最终一致性时间不敏感的业务
- 需要实现消息重复通知机制、消息校对机制
6、方案总结