一、分布式事务与本地事务
分布式事务:简单理解就是一个业务需要调用多个远程微服务操作数据库所产生的问题。
本地事务:单个服务对自己本身的数据库所做的操作(增删改)。
事务的四大特性(ACID)
- 原子性(Atomicity):
如果在一个业务中对一个数据库进行增加和删除操作,必须保证增加和删除操作同时成功或者同时失败
- 一致性(Consistency):
操作后的数据总和不变;例如:转账,小明有200,小花有100,小明给小花转了100,小明100,小花200;那么无论怎么转,必须得保证数据的总和是300;也类似与原子性(能量守恒定律)
- 隔离性(Isolation):
多个事务同时执行时,一个事务的执行不应影响其他事务的执行;类似于加锁,一个线程进去了,其它线程想要进去需要排队等候
- 持久性(Durability):
完成的事务会永久的保存在数据库中,实现永久存储,事务执行完,此操作不可以回滚;比如:想数据库中添加一条数据,如果该业务正常提交,那么在提交的一瞬间,数据就实现的永久化的存储,且该事务不可逆;
二、分布式相关的理论
首先了解一下分布式相关的理论:
1. CAP定理
分布式系统的三个指标,也就是分布式系统需要做到的事情:
- Consistency 一致性
- Availability 可用性
- Partition tolerance 分区容错
注意:这三个指标不可能同时做到,只能满足其中两点
分区容错性:
服务于服务之间存放的地址(位置)是不一样的,所以两个服务之间的调用不可能保证一定成功,总会存在失败;例如:网络抖动,调用超时等等;所以该指标P必须成立,剩下的就只能在C和A选一个了
一致性(强一致性):
- 强一致性,要求更新过的数据能被后续的访问都能看到
- 弱一致性,能容忍后续的部分或者全部访问不到
- 最终一致性,经过一段时间后要求能访问到更新后的数据
场景:有一个客户端A,一个服务端B和一个服务端C(两服务之间数据需同步)B和C服务中有一个数据key=小王;
A对B服务发起修改操作,将key修改成老王;A又对C服务发起查询key的操作,在查询的时候如果B的数据还没有同步到C中,遇到了网络抖动或者超时原因,那么会处于等待状态,等同步完成了才会将key的值返回。也就是说无论如何,拿到的数据都将是最新的
可用性:
例如上一个例子,如果数据B在同步数据到C时,如果出现网络抖动或者超时,不会等待数据同步,会直接将key=小王的值返回给A客服端。
也就是说,只需要能返回结果数据就可以,可以不用保证数据的一致性
总之,两个服务之间的调用无法同时满足一致性与可用性;如果选择了一致性必将放弃服务的可用性;如果选择了可用性也就意味着放弃了一致性。
所以只有两个可能性:AP或CP
2.BASE理论
BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consisten(最终一致性)三个短语的缩写,来自 ebay 的架构师提出。BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:
- 既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
1.Basically Available(基本可用)
也就是说如果系统出现了故障,依然还是可以用
2.Soft state(软状态)
相对的就有一个硬状态
硬状态:例如在一个集群中(服务器存放的数据是一样的),服务器之间必须做到数据的统一,也就是会保证服务器数据的一致性。
软状态:也是例如在一个集群中,允许服务器中的数据存在中间状态,也就是说,服务器之间的数据可以不用做到一致性;例如:向集群的服务器A修改一条数据,那么如果集群中的服务器B因为某些原因不能同步修改后的数据,那么它还是会保持原来未修改的数据。
3.Eventually consistent(最终一致性)
系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态
还是例如在一个集群中,向A服务更新数据,B服务器可能因为某些原因不会及时同步,但是,无论经过多长时间,最终总会实现数据的同步也就是最终一致性。
三、分布式事务的解决方案
1.基于XA协议的两阶段提交
XA规范中的分布式事务由AP、RM、TM组成
AP:应用程序(可以理解成是一个完整的程序)
RM:资源管理器(参与了分布式事务的服务)
TM:事务管理器(管理所有参与了分布式事务的服务)
微服务举例场景:
服务器A添加一条数据时,同时也需要向服务器B和C也添加一条数据(分布式场景下)
二阶段协议:
第一阶段:
例如上面的场景:AP属于的是整个项目的总称;服务器A、B、C都属于RM,TM是独立出来的,用于管理RM
一开始,TM会要求所有的RM准备提交对应的事务分支,也就是要服务器A、B、C告诉TM,自己本身对数据的操作能不能进行提交(大白话的说,就是你能不能办事),如果能提交就返回ok给TM,不能就返回no给TM,如果有一个RM发送了no,那么它就需要将自己的事务进行回滚,并丢弃该事务分支。
第二阶段:
TM会根据所有的RM返回的结果决定是提交还是回滚(ok:提交 no:回滚),如果决定提交,那么TM会通知所有的RM进行提交;如果决定回滚那么TM会通知所有的RM回滚自己的事务分支
总结
优点:很好的保证了数据的强一致性,适合对数据强一致性高的场景(并不能保证100%强一致)
缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
2.TCC补偿机制
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
- Try 阶段主要是对业务系统做检测及资源预留
- Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
例如: A要向 B 转账,思路大概是:
我们有一个本地方法,里面依次调用:
1、首先在 Try 阶段,要先调用远程接口把 B和 A的钱给冻结起来。
2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点: 相比两阶段提交,可用性比较强
缺点: 数据的一致性要差一些。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
3.消息最终一致性
使用RocketMQ实现
版本为4.3以上,因为4.3以上的版本才支持事务消息
- 发送halfMessage半消息
- 确认方法根据数据库执行的结果判断halfMessage是否需要提交/回滚/未执行
- 回调补偿方法:根据消息中封装的数据库事务id定位到具体的数据库本地事务操作,判断数据库是否成功提交数据。根据结果,确认进一步提交/回滚。
4.使用Seata解决分布式事务问题
SeataAT模式中的角色:
- TM 事务管理器(相当于一个业务中的主业务)
- RM 资源管理器(主业务中需要使用到的子业务)
- TC 事务协调器(第三方,管理TM和RM)
场景:在使用微服务架构的新闻类app中,有一业务:用户认证,即普通用户申请成为自媒体用户,如果审核通过,在用户服务中,会向自媒体服务以及作者服务的数据库中都添加一条该用户的数据,以此说明该用户确实成为了自媒体人。而这个业务中涉及到了三个微服务,且都需要在各自的服务中的数据库添加一条数据。那么就产生了分布式事务的问题。解决如下:
使用Seata解决很简单,在servic中实现该业务的方法上添加@GlobalTransactional注解。
Seata具体实现流程分为两个阶段:
一阶段:
- 所有业务正常执行,并且都会在数据库中将数据真正的进行添加,并在日志表undo_log表中记录日志,与修改数据相反的数据,用于数据的回滚
- 执行业务时,所有业务都会在TC中注册自己的信息,便于TC的管理
- 每个业务执行的结果(提交/回滚)都会回馈给上级,当所有的业务执行完成后,TM会收集到所有业务执行的成果,那也就是进入第二阶段了。
二阶段:
- TM会进行全局事务决议,通过所有的业务返回结果判断需要最终执行的结果
- 如果所有业务中有一个业务是回滚,那么最终结果就是回滚。只有所有的业务都是提交,那么最终执行结果才是提交
- TM将最终结果发送给TC,TC根据该结果是提交还是回滚,在告诉所有注册过它的服务最终需要执行的结果。
- 如果是提交,那么所有的服务中的日志表记录的数据都将被删除掉。
- 如果是回滚,那么所有的服务就执行日志表中的数据,并删除日志。
会产生的问题:脏读脏写
原因:
在第一阶段中,数据库中的数据在还没有真正决定是提交还是回滚的情况下,数据已经真正修改了,那么如果在这个时候,有个业务去访问你刚添加的数据他是可以访问的。但其实你能访问的。一但最终结果是回滚,那么数据是需要回滚的。所以会产生数据的脏读现象。