分布式事务使用场景
添加商品看库存够不够。库存扣减,扣完给订单服务一个响应,如果新加商品出问题了怎么回滚。
分布式事务概念
XA规范
XA规范:总之一句话:
就X/Open DTP 定义的 事务协调者与数据库之间的接口规范(即接口函数),事务协调者用它来通知数据库事务的开始、结束以及提交、回滚等。
XA 接口函数由数据库厂商提供
XA规范的实现:
分布式集群的情况下,一般加代理层来充当TM的角色,实现对事务的支持。
二阶提交协议(2PC)和三阶提交协议(3PC)就是根据这一思想衍生出来的。
两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做)
2PC 两阶段提交协议
2PC(Two-phase commit protocol),中文叫两阶段提交, 两阶段提交是一种强一致性设计,2PC 引入一个 事务协调者((Coordinator))的角色来 协调管理各参与者(也可称之为各本地资源)的行为( 提交和回滚),并最终决定这些参与者是否要真正执行事务.
两阶段是将整个事务流程分为两个阶段:分别指的是 准备阶段(Prepare phase)和 提交阶段(commitphase)两个阶段, 2是指两个阶段, P是指准备阶段, C是指提交阶段。 注意:这只是协议或者说是理论指导,只阐述了大方向,具体落地还是有会有差异的
准备阶段(Prepare phase):事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事务,并写本地的Undo/Redo日志,此时事务没有提交。(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数据文件)
提交阶段(commit phase):如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操作,并释放事务处理过程中使用的锁资源。
注意:必须在最后阶段释放锁资源
4. 第二阶段提交失败的情况
(1).如果在第二阶段执行的是回滚事务操作
那么答案是 不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直 阻塞着
(2).如果第二阶段执行的是提交事务操作
那么答案也是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是头铁往前冲, 不断的重试,直到提交成功,到最后真的不行只能 人工介入处理
首先协调者是一个 单点 ,存在 单点故障 问题
(1). 假设协调者在发送准备命令之前挂了,这时事务还没开始,没啥毛病,假设协调者在发送准备命令之后挂了,这就出问题了,因为有些参与者执行了处于事务资源锁定的状态,不仅事务执行不下去,还会因为锁定了一些公共资源而阻塞系统其它操作。
(2). 假设协调者在发送回滚事务命令之前挂了,那么事务也是执行不下去,且在第一阶段那些准备成功参与者都阻塞着。
(3). 假设协调者在发送回滚事务命令之后挂了,这个还行,至少命令发出去了,很大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞着。
(4). 假设协调者在发送提交事务命令之前挂了,这个不行,这下是所有资源都阻塞着。
(5). 假设协调者在发送提交事务命令之后挂了,这个还行,也是至少命令发出去了,很大概率都会提交成功,然后释放资源,但是如果出现网络分区问题某些参与者将因为收不到命令而阻塞着。
解决2pc的问题
实现2PC的方案有以下几种: XA方案,Seata方案
3pc提交协议
3PC 的出现是为 了解决 2PC 的一些问题,相比于 2PC 它在 参与者中也引入了 超时机制,并且 新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。3PC 包含了三个阶段:分别是 准备阶段、预提交阶段和提交阶段,对应的英文就是: CanCommit、 PreCommit 和 DoCommit
看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与者的自身状况,比如你现在还好吗?负载重不重?这类的,而预提交阶段就是和 2PC 的准备阶段一样,除了事务的提交该做的都做了,提交阶段和 2PC 的一样
首先准备阶段的变更成不会直接执行事务,而是会先去询问此时的参与者是否有条件接这个事务,因此不会一来就干活直接锁资源,使得在某些资源不可用的情况下所有参与者都阻塞着。而预提交阶段的引入起到了一个统一状态的作用,它像一道栅栏,表明在预提交阶段前所有参与者其实还未都回应,在预处理阶段表明所有参与者都已经回应了。
假如你是一位参与者,你知道自己进入了预提交状态那你就可以推断出来其他参与者也都进入了预提交状态。但是多引入一个阶段也多一个交互,因此性能会差一些,而且绝大部分的情况下资源应该都是可用的,这样等于每次明知可用执行还得询问一次。
2PC 是同步阻塞的,上面已经分析了协调者挂在了提交请求还未发出去的时候,所有参与者都已经锁定资源并且阻塞等待着。那么引入了超时机制,参与者就不会傻等了,如果是等待提交命令超时,那么参与者就会提交事务了,因为都到了这一阶段了大概率是提交的,如果是等待预提交命令超时,那该干啥就干啥了,反正本来啥也没干。然而超时机制也会带来数据不一致的问题,比如在等待提交命令时候超时了,参与者默认执行的是提交事务操作,但是有可能执行的是回滚操作,这样一来数据就不一致了。
3PC 的引入是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者不知道当前应该提交还是回滚的问题。新协调者来的时候发现有一个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认了,所以此时执行的就是提交命令。
所以说 3PC 就是通过引入预提交阶段来使得参与者之间的状态得到统一,也就是留了一个阶段让大家同步一下。但是这也只能让协调者知道该如果做,但不能保证这样做一定对,这其实和上面 2PC 分析一致,因为挂了的参与者到底有没有执行事务无法断定。所以说 3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据一致,除非挂了的那个参与者恢复。
总结:
3PC 相对于 2PC 做了一定的改进:引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。
所以 2PC 和 3PC 都不能保证数据100%一致,因此一般都需要有定时扫描补偿机制。
tcc解决方案
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了。TCC 是 Try、Confirm、Cancel 三个词语的缩写,TCC 要求每个分支事务实现三个操作: 预处理 Try、 确认 Confirm、 撤销 Cancel
Try 指的是预留,即资源的预留和锁定, 注意是预留
Confirm 指的是业务确认操作,这一步其实就是真正的执行了
Cancel 指的是撤销回滚操作,可以理解为把预留阶段的动作撤销了
步骤:
TM 首先发起所有的分支事务的 Try 操作,任何一个分支事务的Try操作执行失败,TM 将会发起所有分支事务的 Cancel 操作,若 Try 操作全部成功,TM 将会发起所有分支事务的 Confirm 操作,其中 Confirm/Cancel 操作若执行失败,TM 会进行重试
(1). Try 阶段是做完业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的 Confirm 一起才能真正构成一个完整的业务逻辑
(2). Confirm 阶段是做确认提交,Try 阶段所有分支事务执行成功后开始执行 Confirm。通常情况下,采用 TCC 则认为 Confirm 阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。若 Confirm 阶段真的出错了,需引入重试机制或人工处理
(3). Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采用 TCC 则认为 Cancel 阶段也是一定成功的。若 Cancel 阶段真的出错了,需引入重试机制或人工处理