分布式事务介绍

1.什么是事务?

2.换个角度看事务

3.Java中的事务

4.啥又是分布式事务?

5.分布式事务的几种实现思路

6.总结

写在前面

在分布式、微服务大行其道的今天,相信大家对这些名词都不会陌生。而说到使用分布式,或者拆分微服务的好处,你肯定能想到一大堆。

比如每个人只需要维护自己单独的服务,没有了以前的各种代码冲突。自己想测试、想发布、想升级,只需要care自己写的代码就OK了,很方便很贴心!

然而事物都有两面性,但是它也同时也会带来的一些问题,今天的文章谈的就是分布式系统架构带来的其中一个棘手的问题:分布式事务

1、什么是事务?

首先抛出来一个问题:什么是事务?

有人会说事务就是一系列操作,要么同时成功,要么同时失败;然后会从事务的ACID特性(原子性、一致性、隔离性、持久性)展开叙述。

确实如此,事务就是为了保证一系列操作可以正常执行,它必须同时满足ACID特性。

但是今天我们换个角度思考下,我们不仅要知道what(比如什么是事务),更要知道事务的why(比如为什么会有事务这个概念?事务是为了解决什么问题)。

有时候,换个角度说不定有不一样的收获。

2、换个角度看事务

就像经典的文学作品均来自于生活,却又高于生活,事务的概念同样来自于生活,引入“事务”肯定是为了解决某种问题,不然,谁又愿意干这么无聊的事情呢?

最简单最经典的例子:银行转账,我们要从A账户转1000块到B账户。

正常情况下如果从A转出1000到B账户之后,A账户余额减1000(这个操作我们用action1代表),B账户余额加1000(这个操作我们用action2代表)

首先我们要明确一点,action1和action2是两个操作。既然是两个操作那么就一定会存在执行的先后顺序。那么就可能会出现action1执行完刚准备去执行action2的时候出问题了(比如数据库负载过大暂时拒绝访问)。

类比到我们生活中,那就是我给朋友转了1000块钱,然后我卡里的余额少了1000,但是我朋友确没有收到钱。

为解决这种“money去哪儿了”的问题,引入了“事务”的概念。也就是说,既然我转账的时候你保证不了100%能成功,比如银行系统只能保证99.99%的高可用,那么在那0.01%的时间里如果出现了上述问题,银行系统直接回滚action1操作?(即把1000块钱再加回余额中去)

对于银行系统来说,可能在0.01%的时间里我保证不了action1和action2同时成功,那么在出问题的时候,我保证它俩同时失败。(事务的原子性)

通过这个例子,就已经回答了刚开始提出的2个问题(为什么会有事务?事务是为了解决什么问题?)

总结一下:事务就是通过它的ACID特性,保证一系列的操作在任何情况下都可以安全正确的执行。

3、Java中的事务

搞清楚了事务之后,我们来看点眼熟的,java中的事务是怎么玩的?

Java中我们平时用的最多的就是在service层的增删改方法上添加@Transactional注解,让spring去帮我们管理事务。

它底层会给我们的service组件生成一个对应的proxy动态代理,这样所有对service组件的方法都由它对应的proxy来接管

当proxy在调用对应业务方法比如add()时,proxy就会基于AOP的思想在调用真正的业务方法前执行setAutoCommit(false)打开事务。

然后在业务方法执行完后执行commit提交事务,当在执行业务方法的过程中发生异常时就会执行rollback来回滚事务。

当然@Transactional注解具体的实现细节这里不再展开,这个不是本篇文章的重点,本文的topic是“分布式事务”,关于@Transactional注解大家有兴趣的话,可以自己打断点debug源码研究下,源码出真知。

4、啥又是分布式事务?

铺垫了辣么久,终于到了本篇的第一个重点!

首先大家想过没:既然有了事务,并且使用spring的@Transactional注解来控制事务是如此的方便,那为啥还要搞一个分布式事务的概念出来啊?

更进一步,分布式事务和普通事务到底是啥关系?有什么区别?分布式事务又是为了解决什么问题出现的?

各种疑问接踵而至,别着急,带着这些思考,咱们接下来就详细聊聊分布式事务。

既然叫分布式事务,那么必然和分布式有点关系啦!简单来说,分布式事务指的就是分布式系统中的事务。

至于什么是分布式系统?可以参考石杉老师之前的文章:

好,那咱们继续,首先来看看下面的图:

如上图所示,一个单块系统有3个模块:员工模块、财务模块和请假模块。我们现在有一个操作需要按顺序去调用完成这3个模块中的接口。

这个操作是一个整体,包含在一个事务中,要么同时成功要么同时失败回滚。不成功便成仁,这个都没有问题。

但是当我们把单块系统拆分成分布式系统或者微服务架构,事务就不是上面那么玩儿了。

首先我们来看看拆分成分布式系统之后的架构图,如下所示:

上图是同一个操作在分布式系统中的执行情况。员工模块、财务模块和请假模块分别给拆分成员工系统、财务系统和请假系统。

比如一个用户进行一个操作,这个操作需要先调用员工系统预先处理一下,然后通过http或者rpc的方式分别调用财务系统和请假系统的接口做进一步的处理,它们的操作都需要分别落地到数据库中。

这3个系统的一系列操作其实是需要全部被包裹在同一个分布式事务中的,此时这3个系统的操作,要么同时成功要么同时失败。

分布式系统中完成一个操作通常需要多个系统间协同调用和通信,比如上面的例子。

三个子系统:员工系统、财务系统、请假系统之间就通过http或者rpc进行通信,而不再是一个单块系统中不同模块之间的调用,这就是分布式系统和单块系统最大的区别。

一些平时不太关注分布式架构的同学,看到这里可能会说:我直接用spring的@Transactional注解就OK了啊,管那么多干嘛!

但是这里极其重要的一点:单块系统是运行在同一个JVM进程中的,但是分布式系统中的各个系统运行在各自的JVM进程中

因此你直接加@Transactional注解是不行的,因为它只能控制同一个JVM进程中的事务,但是对于这种跨多个JVM进程的事务无能无力

5、分布式事务的几种实现思路

搞清楚了啥是分布式事务,那么分布式事务到底是怎么玩儿的呢?

下边就来给大家介绍几种分布式事务的实现方案。

4.1 可靠消息最终一致性方案

整个流程图如下所示:

我们来解释一下这个方案的大概流程:

A系统先发送一个prepared消息到mq,如果这个prepared消息发送失败那么就直接取消操作别执行了,后续操作都不再执行如果这个消息发送成功过了,那么接着执行A系统的本地事务,如果执行失败就告诉mq回滚消息,后续操作都不再执行如果A系统本地事务执行成功,就告诉mq发送确认消息那如果A系统迟迟不发送确认消息呢?此时mq会自动定时轮询所有prepared消息,然后调用A系统事先提供的接口,通过这个接口反查A系统的上次本地事务是否执行成功如果成功,就发送确认消息给mq;失败则告诉mq回滚消息(后续操作都不再执行)此时B系统会接收到确认消息,然后执行本地的事务,如果本地事务执行成功则事务正常完成如果系统B的本地事务执行失败了咋办?基于mq重试咯,mq会自动不断重试直到成功,如果实在是不行,可以发送报警由人工来手工回滚和补偿这种方案的要点就是可以基于mq来进行不断重试,最终一定会执行成功的。
U2FsdGVkX1+vdz3WwgdjSIL/nAPdW6IKRV2ON8vMlHTjSMw4wu8FQpI3e0CceDCn
ecQWZlBsDc+Wx+x9+gLlW/fYXp9f/vONzF19C/vJFOJCaArPIWpgzGT/4h1NFkSV
yuwtnW2AsyjrNWr7m0hBlzfOuHc3eOTHWJAQGUXmOg+sM+7oFzq9kZaQY0OYtlzd
siCassMZySA45uhvGIOHVCYATA6AWPCGK1btPRtinUN+egqIWPifdHuJlXiW8VwD
9yTlGtCgBC7t4nyTwNTtltW5ANHvPN39PhFAlNXORNqMVz10WC6ZkEesarEXeNhE
gy7FNe8W/bugp68pUNutd0BlaS6aVOt6XidFWUZVdQtxBYI4xJLCUxIKlWRdxABz
106vUQMwkw7t3gcJYO6WiuM41Fx0RtSRVlbUoLKaX/ZZ1BOez0jykuXH7jGC8ve5
SC2bhLYiVg6D6Y2tSrXPhw==
因为一般执行失败的原因是网络抖动或者数据库瞬间负载太高,都是暂时性问题。

通过这种方案,99.9%的情况都是可以保证数据最终一致性的,剩下的0.1%出问题的时候,就人工修复数据呗。

你可能感兴趣的:(笔记)