柔性事务TCC
以常见的下单时使用优惠券的场景为例,涉及三个应用:订单服务、库存服务、优惠券服务:
1、用户提交下单请求
2、锁定商品库存
3、锁定优惠券
4、订单落库
Try阶段(用户下单):
依次同步调用锁定商品库存、锁定优惠券
Confirm阶段(用户支付完成):
更新订单状态为已支付
调用库存扣减、优惠券核销。可通过本地任务表或消息队列保证最终处理成功。
Cancel阶段:
场景一:锁定商品库存成功,锁定优惠券业务处理失败。整个业务操作失败,释放前一步锁定的商品库存。
场景二:库存和优惠券都锁定成功了,但是订单超时未支付自动关闭,或者用户主动取消。释放对应的库存和优惠券。
TCC使用了加锁粒度较小的柔性事务。如上面的流程,锁定库存、锁定优惠券、订单落库三个操作,并没有遵循ACID的原则包在一个大的事务中整体进行原子性的提交。而是变成各自独立应用处理的小事务分开处理。因此也无法保证在同一时刻各个数据源的数据是对应的(强一致性),某些时刻会出现锁定了库存但是订单还没有落库。TCC追求的是最终一致性,根据业务最终的成功与否,变更参与者的最终状态和业务状态一致。
看到这,了解分布式中BASE理论的会想起软状态和最终一致性,TCC算是BASE理论的一种体现。
BASE理论
基本可用:保证核心功能可用。牺牲边缘功能和部分响应时间。比如电商中,核心业务为下单,物流查询、商品评论可适当降级处理。
软状态:因为延迟等因素导致的各个节点在某时刻不一致的状态。
最终一致:最终保持各个节点的数据一致。如上述场景。
2PC和3PC
然后我们又听到2PC的概念,也是分为两阶段,先预留资源再提交,这不和TCC一样吗。的确,二者的两阶段提交的思想确实是一样的。
2PC和TCC的两阶段补偿的区别
但我们说的2PC指的是基于XA规范的两阶段提交。而XA规范定义的DTP分布式事务模型中TM和RM的交互。
由此总结
2PC是针对是资源层面的(这里的资源包括数据库、消息队列等)事务操作,他的协调者和参与者分别是事务管理器和资源管理器。关注的是多个数据源和数据副本之间的同步,为了保证强一致性,在整个两阶段包在一个大事务中,会一直持有资源的锁。典型的例子如Mysql的先写Redo日志再写BinLog就是两阶段的提交。而像Springboot开发者只需要加个@Transactional注解即可,无需关心两阶段提交的细节。
TCC是针对业务应用程序层的,协调者是应用程序,参与者也是应用程序。关注的是应用之间数据的协调。对应的锁定释放逻辑包括幂等逻辑都需要开发者实现。
3PC
但是两阶段提交是完美的么,答案是否定的。
这里我们先不分什么TCC的两阶段提交还是基于XA的2PC了,再去想想刚才的下单场景有什么问题:
假如锁定了库存之后,应用或者说协调者崩溃了,后续的工作都没完成。前期被锁定的库存没有人来释放了,最终一致性出现问题了。
3PC即是在这种背景下产生的
3PC中增加了超时机制,来避免上述资源状态永远无法实现最终一致的问题,然而超时了到底应该是回滚释放还是提交确认呢?
先看看三个阶段干了什么
阶段1:查询资源是否可用,注意只查询不锁定。所有参与者都可提交再进行下一段,降低预留资源时才发现部分参与者不可提交产生回滚的概率。
阶段2:锁定资源
阶段3:提交确认
参与者收到PreCommit后返回超时,释放预留资源,使整个事务在进入阶段3之前完全回滚。
参与者收到DoCommit后返回超时,仍然提交确认。因为能进到阶段3说明协调者已经完成了阶段2对所有参与者的资源预留锁定。虽然大概率整个事务会成功,但如此毕竟不是完全严谨的,脑裂问题仍然存在,仍然会出现某个参与者提交其他参与者返回失败这样数据不一致的问题。
针对上述问题,Paxos算法提供了解决方案,每次处理经过分布式节点中的参与者投票决议是否允许提交,得到超过半数投票者的同意后提交。对Paxos的细节本文不做赘述了。