分布式事务 详解

目录

一、分布式事务产生的原因

1.1、单体架构-多数据源

1.2、分布式事务

二、分布式事务解决方案

三、LCN模式解决分布式事务问题

四、Seata解决分布式事务问题

五、MQ解决分布式事务问题


一、分布式事务产生的原因

我们的项目分为单体架构和分布式架构,这两种都会产生分布式事务问题。

第一种是单体架构-多数据源,第二种是分布式架构。

1.1、单体架构-多数据源

分布式事务 详解_第1张图片

用户支付完,我们先修改支付状态(1号数据源),然后操作积分表(2号数据源),到第三步报错,这时事务进行回滚,但回滚的是1号数据源,2号数据源并没有回滚。

解决办法:jta + Atomikos

1.2、分布式事务

跨JVM调用接口,一定会发生分布式事务问题。

在RPC接口调用的过程中,A调用B服务接口之后,当A接口报错,无法回滚B接口的事务,导致最终A接口事务回滚了,B接口事务没有回滚,需要解决分布式事务问题。

二、分布式事务解决方案

1、单体项目多数据源,可以使用jta + Atomikos。

2、基于RabbitMQ的形式解决,最终一致性的思想。

3、基于RocketMQ解决,采用事务消息。

4、LCN采用LCN模式,假关闭连接(目前已经被淘汰)。

5、Alibaba的Seata背景非常强大,已经成为了主流,但是性能一般。如果你想要解决分布式事务问题,又想接口快速响应,就不要用Seata,Seata会导致接口响应变慢,就会发生超时。如果项目是追求快速响应,建议使用MQ最终一致性方案。

三、LCN模式解决分布式事务问题

分布式事务 详解_第2张图片

1、发起方和参与方与我们的LCN管理器全局事务协调者一直保持长连接。

2、发起方在调用接口之前会使用AOP生成一个全局的事务分组ID。

3、发起方在调用接口的时候会在请求头中传递该全局事务分组ID。

4、参与方会从请求头中获取该事务分组ID,当前业务执行完毕之后不会提交该事务,则会使用假关闭。

5、发起方调用接口完成之后,如果出现异常的情况下,会通知事务协调者回滚该事务,协调者再通知参与方回滚事务,这样两个服务都发生了事务回滚。

该模式存在的缺陷:

LCN基于数据源假关闭,事务如果不提交,有可能会导致Mysql innoDB存储引擎的行锁机制,因为你事务没提交,行锁就不会释放。而且积分服务不提交事务,其它项目查询积分根本查不到你未提交的数据。

四、Seata解决分布式事务问题

Seata为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式分布式事务解决方案。

其实Seata和LCN差不多。

分布式事务 详解_第3张图片

当发起方调用参与方接口,支付服务首先更新状态为已支付,那么这时undolog日志会生成前置镜像和后置镜像,前置镜像就是修改前的状态。这时调用积分服务,积分服务insert一条积分数据,undolog日志生成前置镜像和后置镜像。

那么调用已完成,这时支付服务方报错了,那么通过undolog日志会进行逆向回滚,回滚到之前的状态,然后通知事务协调者,告诉积分服务也要回滚,那么积分服务的undolog日志也进行了逆向回滚,insert的逆向就是delete,这样两个服务就都回滚成功了。

那么Seata与LCN最大的区别就是,在支付服务调用积分服务接口时,积分服务insert一条数据,LCN是不提交事务的,这样就导致别人无法查询到这条新insert的数据。但Seata提交了事务,所以可以查到。

但存在脏读问题,

该模式存在的缺陷:

有可能存在短暂脏读问题。首先我们知道本地事务的脏读是:一个事务没有提交的数据被另外一个事务读到,而全局事务是多个本地事务的集合,如果在全局事务下,某个本地事务提交了,如果没有全局控制,那么这个提交的事务也有可能被其他事务读到,也是一种脏写。这个问题的本质是:全局事务中的某个本地事务完成不代表全局事务也完成。

比如上面的例子,我们支付服务调用积分服务,积分服务insert后提交事务,再相应给支付服务,这时支付服务报错了,打算回滚自己和积分服务,但这时其它的服务是可以查到积分服务刚insert的这条数据。但这时又回滚了,再查询发现刚查到的这条数据又没了......

五、MQ解决分布式事务问题

分布式事务 详解_第4张图片

看结构是很简单的,就是支付服务更新状态为已完成,然后发送消息到MQ,积分服务获取到消息再insert一条数据,看起来很简单,但有以下几个问题。

问题1:生产者必须确保消息投递到MQ成功

使用ACK确认机制。

如果生产者投递消息失败的情况下,则通过日志记录下来,后期通过定时任务自动补偿。

问题2:MQ服务端需要将消息持久化,避免MQ宕机之后消息丢失

MQ服务端在默认的情况下,都会对队列中的消息实现持久化,持久化硬盘。刷盘同步(严格意义上保证消息不丢失),或者是异步,有可能会丢失。

生产者发送消息到MQ服务端,服务端持久化到硬盘上,再回执给生产者,告诉它消息投递成功。

问题3:消费者必须确保消息消费成功(同时注意幂等性问题)

在RabbitMQ情况下:必须要将消息消费成功之后,才会将该消息从MQ服务器端移除。

在Kafka情况下:不管是消费成功还是消费失败,该消息都不会立即从MQ服务器端移除。

如果消费者消费失败的情况下则MQ会采用间隔的形式不断重试,重试过程中需要解决幂等性问题。

注意:说了这么多,MQ这种方式如何回滚啊?直接说答案,MQ没有回滚这一说,只有补偿。如果你支付服务方出错,这时积分服务已经insert并提交事务,那是你系统的问题,积分增加就增加了,或者之后再扣除。如果你积分服务出错了,那后续补偿再把积分补上。

你可能感兴趣的:(Java,分布式,分布式事务,Seata,LCN,MQ,事务)