背景:
在 OLTP 系统领域,我们在很多业务场景下都会面临事务一致性方面的需求。
经典问题:A给B转账,需要扣A再加B。其中任何一步出现问题都需要回滚。
再现实业务场景中可能还涉及三个甚至更多业务模块交互。
相关知识:CAP定律,BASE理论等。
思路,不存在绝对的一致性,需要在业务允许的范围内,通过最终一致性解决。
方法一:二段(三段)提交
总结:这种方式实现难度不算太高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况。但分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。
方法二:提供回滚接口
顾名思义,由依赖模块方提供回滚接口。
总结:这种方式缺点比较多,通常在复杂场景下是不推荐使用的,除非是非常简单的场景,非常容易提供回滚,而且依赖的服务也非常少的情况。
这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务(A->B->C)很多,回滚的成本实在太高。
方法三:本地消息表
ebay的经典解决思路,通过本地消息表存储,通过MQ通知依赖方,依赖方自己保证数据正常消费(幂等,补偿)
总结:这种方式比较常见,如果 MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。不过在没有充分测试的情况下,不建议在交易业务中直接使用。
方法四:MQ(事务消息)
下面以阿里巴巴的 RocketMQ 中间件为例,分析下其设计和实现思路。
RocketMQ 第一阶段发送 Prepared 消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。细心的读者可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ 会定期扫描消息集群中的事物消息,这时候发现了 Prepared 消息,它会向消息发送者确认,Bob 的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。如下图:
总结:据笔者的了解,各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源 MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发或者新造轮子。比较遗憾的是,RocketMQ 事务消息部分的代码也并未开源,需要自己去实现。
其他补偿方法
加日志 》》扫描日志 》》程序补偿 》》人工补偿
参考地址: