在单应用或者传统的强一致解决方案中,A账户转给B账户100元,一个简单的事务就可以保证
但是现今互联网界,分布式系统和微服务架构盛行,一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。
在互联网金融等一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。
基于水平扩容能力和成本考虑,传统的强一致的解决方案(e.g.单机事务)纷纷被抛弃。其理论依据就是响当当的CAP原理。
我们往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。大部分业务场景下,我们是可以接受短暂的不一致的。
下面就总结一下分布式事务中对于这种问题的一般解决思路,以参考和学习。
两阶段提交
两阶段提交需要一个全局事务管理器,或者叫协调者,来协调两个或者多个操作之间的流程。
其关键的是参与者需要实现两阶段提交协议。
- PreCommit阶段需要锁住相关资源
- Commit进行实际的提交
- Cancel或者Rollback释放资源
比如A账户转给B账户100元,流程如下:
那么执行到提交阶段,调用A的commit接口超时了,协调者该如何做?
- 况协调者只能选择继续重试。这也就要求下游接口必须实现幂等
- 如果下游故障,也需要有定时去处理中间状态的逻辑。
- 可以交由支持重试的MQ来继续重试
往往在实际的应该中,这几种方法会配合一起使用,来保证数据的最终一致性
事务消息
只要把消息扔到MQ,那么这个消息肯定会被消费成功。生产方不用担心消息发送失败,也不用担心消息会丢失。
回到现实,消费方,如果消息处理失败了,还有机会继续消费,直到成功为止。
但遗憾的是市面上大部分MQ都不支持事务消息,其中包括看起来可以一统江湖的kafka。
RocketMQ号称支持,但是还没开源。
事务消息,关键一点是把上小节中繁琐的消息状态和重发等用中间件形式封装了。
RMQ的事务消息相对于普通MQ,相当于提供了2PC的提交接口。
比如A账户转给B账户100元,流程如下:
- A锁定相关资源(Prepared) 发送B+100的消息给RMQ(这里B可以消费该消息,并对相关资源进行锁定)
- A执行A-100的操作, 如果失败,则调用RMQ的 cancel接口
- 2成功后,提交A-100操作。
- 如果3失败(超时),RMQ会要求你实现一个check的接口,告知RMQ自己本地事务是否执行成。RMQ也会定时轮训所有处于pre状态的消息,并调用对应的check接口,以决定此消息是否可以提交。
- B账户执行B+100的操作。期间RMQ需要支持重试,直至操作成功。
补偿交易
A:-100(补偿为A:+100), B:+100。那么如果B:+100失败后就需要执行A:+100
跟2PC比,他的核心价值应该是少了锁资源的代价。流程也相对简单一点。但实际操作中,补偿操作不太好定义,其中间状态处理也会比较棘手