分布式事务-掘金 分布式事务的解决方案有很多种, 一致性从强到弱,性能开销从从多到少:三阶段,二阶段,TCC,saga 一致性保障的基本思路,协调者掌握多个子流程的执行过程信息,协调者负责根据子流程执行信息作出决策
分布式事务产生的原因
- 不同的service节点
- 一个事务流程需要调用不同的服务执行不同的子任务
- 不同的resource节点
- 一个事务会调用不同的数据来源,例如mysql的分库操作
分布式事务的原则
CAP原则
- CAP的解释
- C 一致性原则,此处值得是读取到的数据都是事务完成之后和事务完成之前的数据,对于数据事务的中间状态时不可见的
- A 可用性,非故障节点在合理的合理的时间内返回合理的响应
- P 分区容错性,当出现网络分区后,仍能继续工作。例如某个节点故障了之后,集群仍能继续工作
- 对于CAP原则,P是大前提,因此CAP解释成,当P发生时,A和C只能选一个,假设节点1和节点2正好处于网络分区的两侧,例如节点1网络正常,而节点2网络故障。这时,一个事务请求到达集群,节点2一定是不能正常处理的,节点1对于该事务请求
- 如果节点1也对该事务不执行,这保证了C,但是违背了A
- 如果节点1对该事务执行,这保证了A,但是违背了C
BASE原则
- BASE指的是,基本可用,软状态和最终一致性,是对AP的一种扩展
- BASE允许存在中间状态,牺牲了一致性而保证了可用性,但是必须是强一致的状态
解决方案
2PC 两阶段提交协议
- 存在一个协调者,多个参与者
- 步骤:
- 第一阶段:
- 协调者想参与者发出投票,等待各个参与者的响应
- 各个参与者执行事务,并写入redo log和undo log(此时资源还未释放)
- 参与者响应协调者发起的投票,若事务执行成功,则响应同意,若执行失败,则响应终止
- 第二阶段
- 当所有的参与者都同意时,
- 协调者向参与者发送正式提交的请求
- 参与者收到后,完成事务的提交,并释放占用的资源
- 向协调者发送完成响应
- 协调者收到所有完成响应后,完成事务
- 当存在终止响应时,
- 协调者向参与者发出回滚请求
- 参与者利用undo log将事务回滚,并释放资源
- 参与者向协调者发送回滚完成
- 协调者等待所有的回滚完成后,结束事务
- 当所有的参与者都同意时,
- 第一阶段:
- 会存在很多的问题
- 所有参与者对资源的占用都是阻塞的
- 协调者要对参与者设定超时机制
- 太依赖协调者,一旦宕机,会导致整个事务阻塞
3PC 三阶段提交协议
- 引入超时机制,同时将准备阶段一分为二
- 步骤
- canCommit
- 协调者向参与者发送canCommit请求,并等待响应
- 参与者接收到请求后,如果认为自己可以执行,则返回YES,否则NO
- preCommit
- 如果参与者响应都是YES
- 向参与者发送preCommit请求
- 参与者进行事务的执行,并进行redo log,undo log的落地
- 如果成功执行了指令,则返回ack响应,同时开始等待最终指令
- 如果存在一个NO,或者存在超时
- 向参与者发送中断请求
- 参与者接收到了abort请求之后,或则超时未收到协调者的请求,会将事务中断
- 如果参与者响应都是YES
- doCommit
- 若接收到了所有参与者的ack之后,
- 向所有的参与者发送doCommit请求
- 参与者接收到了请求后,执行事务并释放资源,发送ack给协调者
- 协调者接收到所有的ack之后,完成事务
- 若未收到所有的ack或者存在超时响应
- 向所有的参与者发送abort请求
- 参与者收到后根据之前的undo log进行回滚操作,并释放所有资源
- 想协调者发送ack响应
- 协调者收到ack后,终止事务
- 若接收到了所有参与者的ack之后,
- canCommit
TCC模型 (两阶段型,补偿型)
- TCC为Try Confirm Cancel
- Try尝试待执行的业务
- 并未执行业务,只进行一致性检查,但是预留资源
- Confirm执行业务
- 直接执行,使用第一步预留的资源
- Cancel取消执行的业务
- 如果业务失败,则进行资源的回滚,释放占用资源
- Try尝试待执行的业务
- TCC必须基于本地事务
- 必须提供幂等性保障
- 会存在一些超时重试补偿机制
saga模型
- 每个Saga由一系列sub-transaction Ti 组成
- 每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果,这里的每个T,都是一个本地事务。
- 可以看到,和TCC相比,Saga没有“预留 try”动作,它的Ti就是直接提交到库。
- Saga的执行顺序有两种:
- T1, T2, T3, …, Tn
- T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n
- Saga定义了两种恢复策略:
- 向后恢复,即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。
- 向前恢复,适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, …, Tj(失败), Tj(重试),…, Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。
- 这里要注意的是,在saga模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。
还是拿100元买一瓶水的例子来说,这里定义 T1=扣100元 T2=给用户加一瓶水 T3=减库存一瓶水 C1=加100元 C2=给用户减一瓶水 C3=给库存加一瓶水 我们一次进行T1,T2,T3如果发生问题,就执行发生问题的C操作的反向。 上面说到的隔离性的问题会出现在,如果执行到T3这个时候需要执行回滚,但是这个用户已经把水喝了(另外一个事务),回滚的时候就会发现,无法给用户减一瓶水了。这就是事务之间没有隔离性的问题
- 可以看见saga模式没有隔离性的影响还是较大,可以参照华为的解决方案:从业务层面入手加入一 Session 以及锁的机制来保证能够串行化操作资源。也可以在业务层面通过预先冻结资金的方式隔离这部分资源, 最后在业务操作的过程中可以通过及时读取当前状态的方式获取到最新的更新。