分布式事务:分布式系统会把一个应用拆分为多个可独立部署的服务,此时要完成事务,就需要这些服务之间远程交互完成事务。简单的说跨JVM进程或者跨数据库实例产生分布式事务
典型的分布式事务场景:
指的是在分布式系统中不可能同时满足以下三点(最多满足两点):
在分布式系统中分区容忍性一定是要有的(不能因为一个点故障,导致分布式系统挂了),另一点只能在一致性和可用性中取舍,为啥呢?看如下一个分布式系统:
服务A和服务B中间的链路发生了故障,此时满足分区容忍度,服务A和服务B任然能单独的提供服务:
①如果再想满足强一致性,假设客户端写了服务A,因为AB此时不能同步,所以不允许其他客户端再去读服务B未同步的数据(违反了可用性),只有当服务之间的链路恢复了且同步完成,才允许访问。
②如果想满足可用性,允许用户去访问服务B,那么就会读取到脏数据,就违反了强一致性。
在绝大多数场景下,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。
其中,一致性可分为两种:
有些系统通过牺牲强一致性,得到可用性,但它并不是完全放弃数据的一致性,而是换为最终一致性。
BASE理论是对CAP理论中AP的一个扩展,通过牺牲强一致性来获得可用性,允许数据在一段时间内是不一致的:
满足BASE理论的事务称之为“柔性事务”:
- 刚性事务满足CAP的CP,低并发,适合短事务。
- 柔性事务满足BASE理论(基本可用,最终一致),即满足CAP的AP,高并发。
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。
在分布式系统中,每个节点都知道自己的操作结果(成功或者失败),却无法知道其他节点操作的结果,当一个事务跨越多个节点时,为了保证事务的执行(保证事务的原子性与一致性),而引入一个管理者来管理所有参与者的操作结果,并指示他们是否要将操作结果进行真正的提交或者回滚。
比如订单和支付节点,订单调用支付系统,他们各自都有一个事务,管理者就是将这两个事务合二为一,变为一个真正的原子事务。
XA协议有2PC、3PC。
二阶段提交(2PC):它将事务的过程分为两个阶段来进行处理,准备阶段和提交阶段。
举例:张三和李四好久不见,老友约起聚餐,饭店老板要求先买单,才能出票。这时张三和李四分别抱怨近况不如意,囊中羞涩,都不愿意请客,这时只能只有张三和李四都付款,老板才能出票安排就餐。但由于张三和李四都是铁公鸡,形成了尴尬的一幕:
【准备阶段】:老板要求张三付款,张三付款。老板要求李四付款,李四付款。
【提交阶段】:老板出票,两人拿票纷纷落座就餐。例子中形成了一个事务,若张三或李四其中一人拒绝付款,或钱不够,店老板都不会给出票,并且会把已收款退回。
店老板就是事务管理器,张三、李四就是事务参与者,事务管理器负责决策整个分布式事务的提交和回滚,事务参与者负责自己本地事务的提交和回滚
1.第一阶段(准备阶段):
2.第二阶段(提交执行阶段)
当所有的参与者都是同意时,证明该事务执行成功,然后:
如果有任意参与者在第一阶段返回的响应为中止,或者询问超时:
二阶段提交能保证数据的强一致性(刚性事务),但也有几个缺点:
它不适合高并发高性能的场景。
Seata 是什么
Seata是开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA 和XA事务模式,为用户打造一站式的分布式解决方案。
专业术语:
Seata中的AT模式是在传统2PC的基础上演进的:
AT模式运行机制:
在AT模式下,用户只需关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。
传统的2PC事务的资源会一直占有,直到提交或回滚,而Seata的做法是阶段1就提交了,整体提高效率。
举例:
积分服务中也有TM,但是由于没有用到,因此直接可以忽略。
具体Seata的使用方式请看:传送门
三阶段提交(3PC):是两阶段提交的改进版本。
1:CanCommit阶段
2:PreCommit阶段
3:DoCommit阶段
注意:进入阶段3后,无论协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的doCommit请求或abort请求。此时,参与者都会在等待超时之后,继续执行事务提交。
2PC和3PC的区别:
优点:相比2PC,3PC降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。
缺点:数据不一致问题依然存在,当在参与者收到preCommit请求后等待doCommite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
TCC是一种应用层面侵入业务代码的两阶段提交,是目前最火的一种柔性事务方案,不会一直持有资源的锁,其核心思想:针对每个操作,都要注册一个与其对应的确认和补偿操作。
1.第一阶段
Try(尝试):主要针对系统做业务检查(一致性)及资源预留(加锁,锁住资源,隔离性)
2.第二阶段
本阶段根据第一阶段的结果,决定是执行confirm还是cancel
分支事务成功的情况:
分支事务失败的情况:
1.如果Confirm或Cancel操作执行失败,将会不断重试直到执行完成。所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。
2.还有如果RM遇到网络问题,没有执行try阶段,此时事务管理器可能会发送cancel请求,这时要注意空回滚问题。
3.如果因为网络问题或超时,cancel在try之前处理,此问题称为悬挂,即资源预留后没法继续处理,一般cancel控制了空回滚问题就不会出现,而try还是会出现
幂等性:同一个操作无论请求多少次,其结果都是相同的
举例:A转账30元给B,A和B服务不在一个服务。
方案1:
账户A
try:检查余额是否够30元
扣减30元
confirm:空
cancel:增加30元
账户B
try:增加30元
confirm:空
cancel:减少30元
方案1的问题说明:
优化方法:
账户A
try:
try悬挂处理
检查余额是否够30元
扣减30元
confirm:空
cancel:
幂等校验
cancel空回滚处理
增加可用余额30
账户B
try:空
confirm:
confirm幂等校验
正式增加30元
cancel:
空
可以看到,TCC方案有一个中间状态,它只保证数据的最终一致性。
特点:
TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点:
缺点:TCC的Try、Confirm和Cancel操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
指事务发起方完成本地事务后进行提交,然后发送一个消息到事务参与方,指示事务参与方执行它的本地事务逻辑。此方法强调的是该消息一定能够让事务参与方接收到,且最终事务要达到一致。
注意该方法实现的分布式事务不能回滚,适合用在被调用方的服务不影响调用方服务的场景下。它体现的是整体事务一定能提交的一个场景。
如:
张三给李四转账,张三首先扣自己账户的余额,然后再发消息通知李四增加自己的余额,这是可以的(李四无论如何都要执行成功);但如果反过来,李四先增加自己的余额,再通知张三扣减余额,这是不行的,因为张三的余额可能不够,事务就执行不了。
可靠性最终一致性要解决的问题:
1.本地事务与消息发送的原子性问题
事务管理者一定要保证本地事务执行和消息发送是原子的,不能本地事务执行完了,但是消息没发出去或者消息发出去了本地事务执行失败回滚的情况,否则会导致数据的不一致。
2.事务参与方接收消息的可靠性
事务参与方一定要能够从mq接收到消息,如果接收失败可以重复接收消息。
3.消息幂等性
由于网络问题,mq可能会重复投递消息,导致消费者重复消费。
可靠消息最终一致性具体的解决方案:
它的核心思想就是将分布式事务拆分成本地事务进行处理。
事务管理者在数据库额外新增一个事务消息表,将要发出去的消息先插入到该表中。插入消息的sql语句和自己的业务逻辑放在一个事务中,这也就保证了业务处理成功,消息一定持久化了。事务管理方开启一个事务,定时的将数据库的消息发送到MQ。
比如用户服务和积分服务,用户服务添加新用户,添加后还要远程调用积分服务给新用户增加积分:
1.事务管理者通过将消息持久化到数据库的步骤和处理自己的业务逻辑组成一个事务,保证新增用户成功,消息一定持久化了。并通过一个定时任务定时的扫描发送状态为未消费的消息。达到可靠生产。
2.事务参与者如果收到消息,是手动应答,并且需要等待增加积分的事务处理完了才发送ACK,rabbitmq收到ACK后转发给事务管理者,将消息数据库的状态改为已经消费了。如果消费者事务处理失败就返回NACK。定时任务还会一直发送消息。到达可靠消费。
如果事务参与者处理失败,则回复Nack,将消息重新入队消费(注意幂等性)。如果一直错误,可以将消息放入死信队列,进行人工补偿。
该方案的优点是比较成熟,耦合度低,缺点是需要额外创建表,且定时任务不定时的查找数据库带来了一定的消耗。
基于MQ的分布式事务方案其实是对本地消息表的封装,将本地消息表基于MQ内部,其他方面的协议基本与本地消息表一致。
最重要的是事务回查机制,它保证了事务的可靠生产与可靠消费。
如果commit/rollback消息丢失,通过回查机制可以发现。
如果MQ到事务参与者的commit/rollback消息丢失,也可以通过回查机制发现。
最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。它主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景;
如果接收方一直收不到结果,消息通知方应该提供接口供接收方主动查询结果
最大努力通知和可靠消息一致性的区别:可靠消息一致性通过事务管理者发送事务消息,保证事务的可靠性,而最大努力通知靠事务的参与者保证消息的可靠性,如果消息发不出去接收不到,还要提供接口保证可靠性。
Seata Saga 模式
Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,如果某个参与者出现错误回滚,会反向的回滚前面的参与者提交的事务。
Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
Saga模式的优势是:
缺点:Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。
与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持: