seata 分布式事务
全局事务id
TC 事务协调者 维护全局和分支事务状态,驱动全局事务的提交或回滚
TM 事务管理器 定义全局事务的范围,开始全局事务.提交或回滚全局事务
RM 资源管理器 管理分支事务处理的资源,与TC交谈来注册分支事务和报告分支事务的状态,并驱动分支事务的提交或回滚
分布式系统
部署在不同节点的系统,通过网络交互来完成协同完成工作的系统。
分布式事务与分布式锁的区别:
分布式锁解决的是分布式资源抢占的问题;
分布式事务和本地事务是解决流程化提交问题。
本地事务
本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。
Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
分布式事务
在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务。
另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况。
分布式事务处理的理论基础
CAP理论是:
分布式系统在设计时只能在一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)中满足两种,无法兼顾三种。
BASE理论:
BASE理论是基于CAP理论逐步演化而来的,是CP(强一致性)和AP(强可用性)权衡的结果。
BASE理论的核心思想是:
即使无法做到强一致性,但每个应用都可以根据自身业务特点采用适当的方式来使系统达到最终一致性
。Basically Available(基本可用)
响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变成3s。
系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的非核心功能无法使用。
Soft state(软状态)
数据同步允许一定的延迟。
Eventually consistent(最终一致性)
系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时。
总结
从ACID到BASE,对于事务,不同视角,对它的理解也不同:
1. 在数据库层面,通过日志文件确保了事务的一致性,以及确定了不同的事务隔离级别
2. 在Java代码层面,更多的是关注事务之间的关系
3. 而在分布式事务中,为了高可用,在事务一致性上进行了妥协,一般只保证最终一致性
一致性(Consistency):服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
可用性(Availability):服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
分区容忍性(Partition Tolerance):
分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。微服务分布式系统必须满足该特性
就是我们系统对外提供的一个可用性或者对外整体的一个系统完整性。比如我们多进程的系统;如果出现某个进程挂了、或者进程之间网络不互通的时候;那么对外还能不能提供服务;这就是我们分区容忍性。
base 理论 cap 理论强调的是集群的内部可用性、一致性、分区容错性。而 base 理论则对 cap 进行了升级演化,是对 CAP 中一致性和可用性权衡的结果,它强调的是系统,系统的内部会存在着多个集群。
base 理论的三个特性:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)
1、基本可用呢:假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:
响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 1 秒作用返回结果。
功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
2、Soft state(软状态) 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
3、Eventually consistent(最终一致性) 最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
7、分布式事务与 base: 分布式事务在 base 理论中解决的是软状态和一致性问题,根据不同的一致性要求使用不同的解决方案。二阶段、三阶段、tcc、mq等等,有些使用的是强一致性,有些是弱一致性。
分布式事务解决方案
分布式事务的实现主要有以下 5 种方案:
XA 方案 --强一致性
TCC 方案 --强一致性
本地消息表 --弱一致性
可靠消息最终一致性方案 --弱一致性
最大努力通知方案
两阶段提交方案/XA方案
所谓的 XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 Spring + JTA
就可以搞定,自己随便搜个 demo 看看就知道了。
这个方案,我们很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。
三阶段提交3PC
与两阶段提交不同的是,三阶段提交有两个改动点:
引入超时机制。同时在协调者和参与者中都引入超时机制;
在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
两阶段提交与三阶段提交
两阶段提交 上篇文章中我们说到两阶段提交2pc,两阶段提交还是有很多缺点的,它可能因为网络问题导致数据不一致,比如协调者发送的提交事务的请求由于网络问题只有部分参与者能接收到,这样部分参与者提交了事务而另一部分参与者没有提交就造成了数据的不一致,第二个问题就是协调者如果挂掉,参与者既没有提交也没有回滚,第三个问题就是同时在执行的时候参与节点的服务都是阻塞的,不能对外提供服务
三阶段提交 那么什么是三阶段提交,三阶段提交能否能解决这些问题呢?
三阶段提交分为CanCommit阶段,PreCommit阶段和doCommit阶段。第一个阶段是检查自身状态是否满足了事务操作的条件,一般可能是首先获取到这个分布式事务的锁,第二个阶段就是开始事务,但是没有提交,并返回给协调者结果,第三个阶段就是对事务的提交或回滚。
这就是三阶段提交的大致内容,那么三阶段提交是否解决了两阶段提交产生的问题呢?三阶段提交参与者也有超时机制,在preCommit的时候,如果收不到协调者的消息会执行中断事务,在doCommit阶段如果迟迟收不到协调者的消息就会进行事务提交,而数据的一致性问题和协调者的单点故障问题依然存在
总结
两阶段提交和三阶段提交都会存在一定的问题,三阶段提交相对二阶段提交多了参与者的超时机制,但剩余问题并没有得到解决,比如数据的一致性和协调者的单点故障的问题。
TCC
全称是:
Try
、Confirm
、Cancel
。
Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。
比如说我们,一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。
而且最好是你的各个业务执行的时间都比较短。
但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。
本地消息表
本地消息表其实是国外的 ebay 搞出来的这么一套思想。
这个大概意思是这样的:
A 系统在自己本地一个事务里操作同时,插入一条数据到消息表;
接着 A 系统将这个消息发送到 MQ 中去;
B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态;
如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。
这个方案说实话最大的问题就在于严重依赖于数据库的消息表来管理事务啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。
可靠消息最终一致性方案
这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
大概的意思就是:
A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。
示例
交易链路
假设我们的系统的核心交易链路如下图。用户支付订单时,首先调用订单服务的对外接口服务,然后开始核心交易链路的调用,依次经过订单业务服务、库存服务、积分服务,全部成功后再通过MQ异步调用仓储服务:
上图中,订单业务服务、库存服务、积分服务都是同步调用的,由于是核心链路,我们可以通过上一章中讲解的TCC分布式事务来保证分布式事务的一致性。而调用仓储服务可以异步执行,所以我们依赖RocketMQ来实现分布式事务。
事务执行
接着,我们来看下引入RocketMQ来实现分布式事务后,整个系统的业务执行流程发生了哪些变化,整个流程如下图:
当用户针对订单发起支付时,首先订单接口服务先发送一个half-msg消息给RocketMQ,收到RocketMQ的成功响应(注意,此时仓储服务还不能消费消息,因为half-msg还没有确认)。
然后,订单接口服务调用核心交易链路,如果其中任一服务执行失败,则先执行内部的TCC事务回滚;
如果订单接口服务收到链路失败的响应,则向MQ投递一个rollback消息,取消之前的half-msg;
如果订单接口服务收到链路成功的响应,则向MQ投递一个commit消息,确认之前的half-msg,那仓库服务就可以消费消息;
仓储服务消费消息成功并执行完自身的逻辑后,会向RocketMQ投递一个ack message,以确保消费成功。
注意,如果因为网络原因,导致RocketMQ始终没有收到订单接口服务对half-msg的commit或rollback消息,RocketMQ就会回调订单接口服务的某个接口,以查询该half-msg究竟是进行commit还是rollback。
总结
可靠消息最终一致性方案是目前业务主流的分布式事务落地方案,其优缺点主要如下:
优点: 消息数据独立存储,降低业务系统与消息系统间的耦合。
缺点: 一次消息发送需要两次请求,业务服务需要提供消息状态查询的回调接口。
一般来讲,99%的分布式接口调用不需要做分布式事务,通过监控(邮件、短信告警)、记录日志,就可以事后快速定位问题,然后就是排查、出解决方案、修复数据。
因为用分布式事务一定是有成本的,而且这个成本会比较高,特别是对于一些中小型公司。同时,引入分布式事务后,代码复杂度、开发周期会大幅上升,系统性能和吞吐量会大幅下跌,这就导致系统更加更加脆弱,更容易出bug。当然,如果有资源能够持续投入,分布式事务做好了的话,好处就是可以100%保证数据一致性不会出错。
最大努力通知方案
这个方案的大致意思就是:
系统 A 本地事务执行完之后,发送个消息到 MQ;
这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。