分布式事务
因为分布式中因为网络问题会导致一些不确定性,所以一个完备的分布式事务方案,一定时补偿型的。
1. ACID
数据一致性:数据库从一个状态转移到另一个状态,没有其他状态。
2. 刚性事务和柔性事务
- 刚性事务
单库满足的ACID叫刚性事务。 -
柔性事务
在分布式场景下,刚性事务很难实现,一般使用柔性事务解决。
image.png
2.1 柔性事务对ACID的支持
- 原子性:
严格遵循; - 一致性
事务完成后的一致性严格遵循,事务中的一致性可适当放宽; - 隔离性
并行事务间不可影响;事务中间结果可见性允许安全放宽; - 持久性
严格遵循;
为了可用性、性能的需要,柔性事务降低了一致性(C)与隔离性(I) 的要求,即“基本可用,最终一致”;
2.2柔性事务的分类
- CC(commit,cancel)
参见[DRDS 柔性事务漫谈](https://mp.weixin.qq.com/s/36YSQiUle3t8mJ8MeA-4Ww) - TCC
- XA
- 消息事务
3. Two Phase Commitment Protocol(2PC)
3.1 XA
XA 协议并没有定义怎么实现全局的 Snapshot,像 MySQL 官方文档里就建议使用串行化的隔离级别来保证分布式事务一致性:
“As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.”(对于分布式事务来说,可重复读隔离级别不足以保证事务一致性,如果你的程序有全局一致性读要求,可以考虑串行化隔离级别.)
当然,由于串行化隔离级别的性能较差,所以很多分布式数据库都自己实现了分布式 MVCC 机制来提供全局的一致性读。一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局 Snapshot,每个事务或者每条 SQL 执行时都去获取一次,从而实现不同隔离级别下的一致性。比如 Google 的 Spanner 就是用 TrueTime 来控制访问全局 Snapshot。
XA 模型另外一个意义在于其普适性,抛开性能问题的情况下,几乎可以适用于所有业务模式,这对于一些基础性的技术产品来说是非常有用的,比如分布式数据库、云服务的分布式事务框架等。
- 同步阻塞问题
执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。 - 协调者单点故障
由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) - 数据不一致
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作,但是其他部分未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据部一致性的现象。
另外一种情况是:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
3.2 TCC
TCC
DTS是蚂蚁的一款实现了TCC的事务中间件:https://www.cloud.alipay.com/docs/2/46887
TCC 是一种编程模型,是应用层的 2PC。
TCC 模型除了跨服务的分布式事务这一层作用之外,还具有两阶段划分的功能,通过业务资源锁定,允许第二阶段的异步执行,而异步化思想正是解决热点数据并发性能问题的利器之一。
- 业务操作分2阶段实现
需要考虑将业务操作分2步实现,并且一阶段成功,二阶段提交必须能成功; - 关联事务数据和业务数据
TCC参与者一,二阶段的入参只包含事务数据(通常是主事务ID和分支事务ID),参与者处理业务数据之前需要根据事务数据关联到具体的业务数据; - 幂等性
TCC服务的3个方式在设计和实现时,要考虑幂等性,允许同一笔事务的重复提交和重复回滚。 - 数据可见性控制
当一笔分布式事务正在处理中(一阶段已执行,二阶段未执行),此时如果有查询,则需要兼容未处理完的中间数据的可见性。
一般通过文案展示告诉用户中间数据的存在,比如告诉用户当前冻结的金额有多少。 - 隔离性控制
对于状态类数据,需要提供隔离性控制来允许不同事务操作同一个业务资源。比如账户余额,不同事务操作的金额是隔离的,要做到事务之间相互不影响; - 允许空回滚
回滚请求处理时,如果对应的具体业务数据为空,则返回成功;
事务执行过程中,如果发起方调用参与者一阶段方法时超时,此时发起方会回滚事务,调用参与者的回滚方法;如果参与者没收到一阶段请求(一阶段请求可能永远不会到达),而是收到二阶段回滚请求,则应当允许空回滚; - 防悬挂
事务执行过程中,如果发起方调用参与者一阶段方法时超时,此时发起方会回滚事务,调用参与者的回滚方法;通常情况下如上所述参与者会允许空回归,返回回滚成功;发起方回滚所有参与者之后结束此分布式事务;
但是在参与者回滚之后,之前超时的一阶段请求到达(一阶段请求包没有丢,只是由于网络延迟,晚于二阶段回滚到达),此时参与者如果按照一个正常的一阶段请求处理,那么接下来将永远不会收到二阶段请求,出现事务悬挂;
故参与者在设计时,需要考虑如何防止出现这种悬挂;通常的做法是一阶段请求执行前,判断当前参与者有无执行过空回归,无则正常执行一阶段,有则返回一阶段失败; - 拒绝空提交
提交请求处理时,如果对应的具体业务数据为空,则返回失败;由于编码bug等原因,参与者未收到一阶段请求的情况下,收到二阶段提交请求,需要返回失败;