1. 事务的ACID
-
原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行,要么都不执行。
-
一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态。
-
隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。
- 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。
2. Redis的事务
-
Redis 的事务不能保证所有操作要么都执行要么都不执行。
-
Redis事务中的某个命令失败了,之后的命令还是会被处理,Redis 不会停止命令,意味着也不会回滚。
- Redis认为如果命令出错是语法使用错误,应该在开发的时候就被检测出来,不应在生产环境中出现。回滚并不能免于编程错误。
3. 2PC
-
是一种强一致性设计
-
引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段
-
正常情况
-
异常情况
-
协调者故障分析
- 2PC 是同步阻塞协议
- 协调者有超时机制
- 协调者是一个单点,存在单点故障问题
- 协调者在发送准备命令之前挂了,事务没开始,无影响
- 协调者在发送准备命令之后挂了,处于事务资源锁定的状态,事务无法执行,阻塞其他操作
- 协调者在发送回滚事务命令之前挂了,事务无法继续提交,第一阶段准备成功的参与者都阻塞
- 协调者在发送回滚事务命令之后挂了,大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞
- 协调者在发送提交事务命令之前挂了,资源都阻塞
- 协调者在发送提交事务命令之后挂了,大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞
-
通过选举得到新协调者
- 每个参与者自身的状态只有自己和协调者知道,因此新协调者无法通过在场的参与者的状态推断出挂了的参与者是什么情况
- 极端情况下还是无法避免数据不一致问题
-
总结
- 同步阻塞的,尽量保证强一致性的分布式事务
- 总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险
- 不能做到数据一致,需要补偿机制
4. 3PC
- 在参与者中也引入了超时机制
- CanCommit、PreCommit 和 DoCommit
- 正常情况
- 优势
- 不会直接锁资源而是先询问情况
- 加入超时机制
- 如果是等待预提交命令超时,那该干啥就干啥了
- 如果是等待提交命令超时,那么参与者就会提交事务了
- 劣势
- 多一个阶段,性能会差一些,大部分情况资源都可用
- 超时机制依然存在数据不一致情况
- 比如在等待提交命令时候超时了,参与者默认执行的是提交事务操作,但是有可能执行的是回滚操作,这样数据就不一致了
- 总结
- 引入预提交阶段来使得参与者之间的状态得到统一。如果调度者挂了,新协调者来的时候发现有一个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认了,所以此时执行的就是提交命令。
- 引入了参与者超时机制,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。因为挂了的参与者到底有没有执行事务无法断定。需要补偿机制
5. TCC
-
TCC
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
-
思想上类似2PC,TCC 就是通过代码人为实现了两阶段提交
-
TCC模型还有个事务管理者的角色,变成多点,引入集群。用来记录TCC全局事务状态并提交或者回滚事务
-
引入超时,超时后进行补偿,并且不会锁定整个资源
-
优势
- TCC可以跨数据库、跨不同的业务系统来实现事务
-
劣势
- TCC 对业务的侵入较大和业务紧耦合
撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等
6. 本地消息表
-
利用各系统本地的事务来实现分布式事务
-
有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候将业务的执行和将消息放入消息表中的操作放在同一个事务中,保证消息放入本地表中业务肯定是执行成功的
-
后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
-
重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理
-
实现的是最终一致性
7. 消息事务
-
利用MQ事务
-
先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务
-
再根据本地事务的结果向 Broker 发送 Commit 或者RollBack命令
-
RocketMQ的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行Commit 或者RollBack命令。
-
如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可
-
如果是RollBack那么订阅方收不到这条消息,等于事务就没执行过
-
消息事务实现的也是最终一致性
8. 最大努力通知
- 本地消息表也可以算最大努力,事务消息也可以算最大努力。
- 最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
- 适用于对时间不敏感的业务,例如短信通知。
9. Saga
-
长事务的解决方案,更适合于“业务流程长、业务流程多”的场景
-
特别是针对参与事务的服务是遗留系统服务,此类服务无法提供TCC模式下的三个接口,就可以采用Saga模式
-
针对每一个分布式事务的每个执行操作或者是步骤都是一个 Ti,例如扣减库存是T1、创建订单是T2、支付服务是T3。那么针对每个Ti都对应一个补偿动作Ci,例如回复库存C1、订单回滚C2、支付回滚C3
-
优势
- 一阶段提交本地事务,无锁,高性能
- 参与者可异步执行,高吞吐
- 补偿服务易于实现,因为一个更新操作的反向操作是比较容易理解的
-
劣势
- 不保证隔离性(可以看到其他事务)
- Saga操作模式更像发电子邮件,发出的电子邮件是正确的,接收者按正常方式解读。如果邮件发错了,就再发一封告诉接收者,请忽略上一封邮件。但有时候这个操作已经不可逆了。
- 举例
- 分布式事务内先给用户A充值,然后给用户B扣减余额,如果在给A用户充值成功,在事务提交以前,A用户把余额消费掉了,如果事务发生回滚,这时则没有办法进行补偿了。
- 应对方法
- 业务流程设计时遵循“宁可长款,不可短款”的原则,长款意思是客户少了钱机构多了钱,以机构信誉可以给客户退款,反之则是短款,少的钱可能追不回来了,所以在业务流程设计上一定是先扣款;
- 有些业务场景可以允许让业务最终成功,在回滚不了的情况下可以继续重试完成后面的流程,达到最终一致性的目的
- 不保证隔离性(可以看到其他事务)
-
两种恢复策略
-
两种模式
10. 总结
- 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
- TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法
- Saga是一种长事务的解决方案,比TCC更简单,但不能保证隔离性
- 本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务