分布式事务
分布式事务产生场景
现在大部分互联网公司都会使用分布式架构,往往会将传统单体架构的项目拆分为多个子系统,便于集群的搭建扩展。
如果系统足够大,已经使用了分库操作,业务相互关联的两个系统,却使用了两个不同的数据库,在这种情况下,是会产生分布式事务问题的。
如果是在同一个数据库中,对应的是同一个数据库连接,直接使用Spring的声明式事务就可以解决事务一致性问题。但是当操作的是两个不同的数据库时,数据库连接已经不是同一个,单个系统事务回滚无法确保另外一个系统的事务也会回滚,此时Spring的声明式事务就会显得捉襟见肘,无法应对了。
如下图所示:现在有一个业务场景,我们在某外卖平台点了外卖,并且已经支付成功,现在需要调用运单系统安排外卖小哥进行外卖派送。此时就是两个系统的跨系统调用,而且两个系统用的是不同的数据库,此时就会出现分布式事务问题。
下单成功后,需要将用户信息,例如:电话号码,地址,下单商品,订单价格等信息传给运单系统,但是两个系统之间现在是无法知道对方是否成功,所以就会产生分布式事务问题:
1.接口调用成功,订单系统数据库事务提交失败,但是运单系统的数据没有回滚,导致用户下单失败,但是却收到了外卖;
2.接口调用失败,订单系统未回滚事务,导致用户下单成功,却收不到外卖;
这两种情况都没有保证事务的最终一致性。
分布式事务解决方案
以下是分布式事务的解决方案。
基于数据库XA/JTA协议
需要数据厂商支持
异步校对方式
异步拉取对账单形式
基于可靠消息(MQ)解决方案
异步场景;通用性较强;扩展性较高
RabbitMQ特性
整体设计思路
出现分布式事务的不一致最根本的原因是两个系统的事务无法明确的知道对方是否提交或者回滚。所以需要一个中间容器,它要具有以下特性:
1.能够可靠的存储两个系统交互的信息;
2.这个容器两头一个存,一个取,存必须保证成功,取了消息,并且要消费消息之后让存消息的一方知道
正好RabbitMQ满足这两个特性,下面具体介绍下RabbitMQ的解决方案设计思路。
- 可靠的消息生产–消息发送
由事务的发起方(一般为业务首先开始的一方)维护消息表,同时业务操作要和消息表的插入在同一个事务中,确保业务操作和插入消息表记录同时成功。
通过Spring集成RabbitMq,配置开启消息发送确认机制。不然消息发送之后不回产生回执消息,系统无法保证消息一定发送成功。
- 可靠消息生产–消息发送成功修改状态
在消息发送成功并收到确认回执后,需要修改数据库中消息表的状态。
- 可靠的消息处理–正常状态
RabbitMQ默认为开启手动ACK模式,即exchange转发消息后,会自动删除Queue中的数据,需要手动开启ACK模式。
开启后当运单中心从Queue中获取到消息并且消费成功,业务处理完成后,会发送回执消息,此时消息才会从Queue中删除。
因为运单中心会定时扫表,没有发送的消息会重复进行发送,所以有可能会出现同一条消息多次发送的情况。此时运单中心需要进行一个幂等处理。
- 可靠消息处理
a 消息重发
b 消息丢弃
代码示例
优缺点
优点:
1.通用性强:很多业务场景适用
2.扩展性强:MQ,provider,consumer都可以做集群,易于扩展
3.方案成熟:是现在主流的解决方案
缺点:
1.由于使用了消息中间件,所以只能够用于异步场景,无法确保及时返回consumer消费状态
2.消息队列可能会有延迟,只能够做到事务的最终一致性
综上所述:在我们设计的时候,尽量要避免分布式事务的发生,使用到了分布式事务,会增加系统复杂度,同时还会降低性能。
TCC编程式解决方案
各个公司自己封装DTX
扩展
分布式事务是有很多理论基础的,想要了解更多,可以看看下面的理论。
CAP
BASE
2PC协议
3PC协议
Paxos算法
Raft一致性协议