1.两阶段提交协议(2PC)
1.1 两阶段提交协议
事务发起阶段:
事务的发起者提出一个request(比如用户下单购买某个商品),要求其依赖的服务(事务的执行者)本地执行业务逻辑。执行成功本地事务不提交但要告诉发起者本地已经执行成功;执行失败执行者告诉发起者本地作业执行失败
事务提交/回滚阶段:
事务发起者根据事务发起阶段收集到的信息决定提交/回滚
如果全部执行者都反馈成功那么发起者通知所有执行者提交事务
如果存在执行者反馈失败,则发起者通知所有参与者取消事务
综上可见,无论事务发起阶段还是提交/回滚阶段,都是通过发起者向执行者发送相应指令来控制执行者作相应操作
流程图如下:
1.2 两阶段提交——超时处理
- 对执行者而言:如果没有收到第二步事务,该如何处理?此时的执行者会一直锁定资源等待第二步事务。
- 对发起者而言:如果第一步中没有收到回复,该如果处理?此时的发起者无法得知是否所有执行者都成功锁定了资源。
- 对发起者而言:如果第二步中没有收到回复,该如果处理?此时的发起者无法得知是否所有执行者都成功确认了事务。
依次回答这三个问题:
- 执行者没有收到第二步事务,有三种处理方案:第一种是一直锁定资源等待,第二种是超时 confirm 事务,第三种是超时 cancel 事务。对于两阶段提交而言,其他执行者在第一步是有可能返回失败的,所以显然强行 confirm 会有风险,第三种更为合理。此处有“应当 confirm 但因为网络或其他问题而没有收到,最终执行了超时 cancel”的风险,会导致数据不一致。
- 发起者第一步中没有收到回复,也存在两种策略:要么超时重试(再次提出事务),要么超时后当做返回失败处理。这两种可以组合使用,即多次超时重试后仍无回复则当做返回失败处理。
- 发起者第二步中没有收到回复,和问题 2 的处理策略类似,多次超时重试后仍无返回说明出现了异常,但不同的是这个异常是一个无法回滚的异常,意味着系统中可能出现了数据不一致,可能需要其他(很可能是人工)方式修复数据。
1.3 两阶段提交——异常重试
当执行过程中发生异常(比如宕机),事务应当可以重试。
- 对发起者而言:如果在第一步发生异常:部分执行者锁定了资源,而另一部分从未收到过事务请求。由于执行者会默认超时 cancel,所以发起者发起 cancel 后(或不处理,直接等待超时)重新发起新事务即可。
- 对发起者而言:如果在第二步发生异常:如果执行的是 cancel,则无需重试,当做成功即可(当然也可以重试)。如果执行的是 confirm,则可能发生部分机器成功 confirm,部分机器由于没有收到 confirm,默认超时 cancel 请求,从而数据不一致的风险。
- 对执行者而言:如果在第一步发生异常:尽量返回失败即可,超时发起者会重试/cancel 请求。不会有什么风险。
- 对执行者而言:如果在第二步发生异常:尽量重试并保证成功。如果执行的是 confirm,说明第一步的锁定返回了成功,所以第二步的确认只能是成功。如果是 cancel,则更应当自行重试保证资源释放。
2.三阶段提交协议(3PC)
CanCommit(询问阶段):
事务协调者向参与者发送事务执行请求,询问是否可以完成指令,参与者只需要回答是不是即可,不需要做正真的事务操作,这个阶段会有超时终止机制。
PreCommit(准备阶段):
事务协调者会根据参与者的反馈结果决定是否继续执行,如果在询问阶段所有参与者都返回可以执行操作,则事务协调者会向所有参与者发送PreCommit请求,参与者收到请求后会写redo和undo日志,执行事务操作但是不提交事务,然后返回ACK响应等待事务协调者的下一步通知。如果询问阶段任意参与者返回不能执行操作的结果,那么事务协调者会向所有参与者发送事务中断请求。
DoCommit(提交或回滚阶段):
这个阶段也会存在两种结果,根据上一步骤的执行结果来决定DoCommit的执行方式。如果每个参与者在PreCommit阶段都返回成功,那么事务协调者会向所有的参与者发起事务提交指令。反之,如果参与者中的任一个参与者返回失败,那么事务协调者就会发起中转指令来回滚事务。
三阶段提交是较二阶段提交在事务发起阶段前 先进行查询,确认是否可提交。
以用户下单购买某个商品”为例。对于这个场景,第 0 步会检查向优惠券服务检查优惠券是否可用、向支付业务检查账户余额是否足够、向仓储服务检查库存是否足够等。
只有当第 0 步全部返回成功时,才会执行第一步的锁定资源。这时的第一步也几乎可以全部返回成功(只有并发情况下会失败)。
因此,对于执行者而言,如果一直没有收到第二步(实际上的第三步)的事务,超时可以默认执行 confirm 操作。大多数情况下都会成功避免数据不一致。(只有并发竞争情况下有可能失败)
简而言之,三阶段提交相比两阶段提交多了第 0 步检查是否可提交。执行者的默认超时行为从 cancel 改为 confirm。
三阶段提交流程图
3. TCC transaction
Tcc-transaction是一款开源的微服务架构下的TCC型分布式事务解决方案,致力于提高性能和简单易用的分布式事务服务
try: 尝试执行业务,完成所有业务检查
confirm:提交事务,不做任何业务检查,只使用try阶段预留的业务资源,满足幂等性
cancel:取消事务,释放try阶段预留的业务资源,满足幂等性
比如退款场景:需要从各分账方退回平台收益户(退分账),再退还给付款方。其中退分账阶段, 涉及从多个分账方(商家1收益户,商家2收益户,商家3收益户,平台手续费账户)扣款,这些账户分布在不同数据库, 比如商家3收益户扣款失败,其他成功扣款需要回滚,这里需要分布式事务保证一致性。
如何解决上面退分账中分布式事务问题呢? 选择使用tcc-transaction框架,执行流程如下:
- Try:
商家1收益户->冻结分账金额
商家2收益户->冻结分账金额
商家3收益户->冻结分账金额
平台手续费->冻结手续费 - Try成功 => Confirm:
商家1收益户->扣除分账金额
商家2收益户->扣除分账金额
商家3收益户->扣除分账金额
平台手续费->扣除手续费
平台收益户-> 增加金额(总分账金额+手续费) - Try失败 => Cancel:
商家1收益户->解冻分账金额
商家2收益户->解冻分账金额
商家3收益户->解冻分账金额
平台手续费->解冻手续费
参考文献:
https://blog.csdn.net/u014635374/article/details/105948330
https://zhuanlan.zhihu.com/p/163864897
https://github.com/changmingxie/tcc-transaction