共计 3236 个字符,预计需要花费 9 分钟才能阅读完成。
解决 NPC
分布式系统最大的敌人可能就是 NPC 了,在这里它是 Network Delay, Process Pause, Clock Drift 的首字母缩写。咱们先看看具体的 NPC 问题是什么:
- Network Delay,网络提早。尽管网络在少数状况下工作的还能够,尽管 TCP 保障传输程序和不会失落,但它无奈打消网络提早问题。
- Process Pause,过程暂停。有很多种起因能够导致过程暂停:比方编程语言中的 GC(垃圾回收机制)会暂停所有正在运行的线程;再比方,咱们有时会暂停云服务器,从而能够在不重启的状况下将云服务器从一台主机迁徙到另一台主机。咱们无奈确定性预测过程暂停的时长,你认为继续几百毫秒曾经很长了,但实际上继续数分钟之久过程暂停并不常见。
- Clock Drift,时钟漂移。现实生活中咱们通常认为工夫是安稳流逝,枯燥递增的,但在计算机中不是。计算机应用时钟硬件计时,通常是石英钟,计时精度无限,同时受机器温度影响。为了在肯定水平上同步网络上多个机器之间的工夫,通常应用 NTP 协定将本地设施的工夫与专门的工夫服务器对齐,这样做的一个间接后果是设施的本地工夫可能会忽然向前或向后跳跃。
分布式事务既然是分布式的零碎,天然也有 NPC。分布式事务可分为
- 分布式数据库外部的分布式事务:NPC 中的 C 给这类利用,带来了极大的挑战,例如 Spanner 采纳了原子钟,并且减少了 Commit-Wait 来解决问题。TiDB 采纳单点授时,引入了一个单点。
- 跨数据库的异构分布式事务:这是咱们这篇文章探讨的主题,因为没有波及工夫戳,所以 NPC 带来的困扰次要是 NP。
TCC 的空弥补与悬挂
咱们以分布式事务中的 TCC(Try,Confirm,Cancel)作为例子,对于 TCC 模式的具体介绍能够参考:分布式事务最经典的七种解决方案
个别状况下,一个 TCC 回滚时的执行程序是,先执行完 Try,再执行 Cancel,然而因为 N,则有可能 Try 的网络提早大,导致先执行 Cancel,再执行 Try。
这种状况就引入了分布式事务中的两个难题:
- 空弥补:Cancel 执行时,Try 未执行,事务分支的 Cancel 操作须要判断出 Try 未执行,这时须要疏忽 Cancel 中的业务数据更新,间接返回
- 悬挂:Try 执行时,Cancel 已执行实现,事务分支的 Try 操作须要判断出 Cancel 一致性,这时须要疏忽 Try 中的业务数据更新,间接返回
分布式事务还有一类须要解决的常见问题,就是反复申请,业务须要做幂等解决。因为空弥补、悬挂、反复申请都跟 NP 无关,咱们把他们统称为子事务乱序问题。在业务解决中,须要小心解决好这三种问题,否则会呈现谬误数据。
现有计划的问题
我看到开源我的项目 dtm 之外,包含各云厂商,各开源我的项目,他们给出的业务实现倡议大多相似如下:
- 空弥补:“针对该问题,在服务设计时,须要容许空弥补,即在没有找到要弥补的业务主键时,返回弥补胜利,并将原业务主键记录下来,标记该业务流水已弥补胜利。”
- 防悬挂:“须要查看以后业务主键是否曾经在空弥补记录下来的业务主键中存在,如果存在则要拒绝执行该笔服务,免得造成数据不统一。”
上述的这种实现,可能在大部分状况下失常运行,然而上述做法中的“先查后改”在并发状况下是容易掉坑里的,咱们剖析一下如下场景:
- 失常执行程序下,Try 执行时,在查完没有空弥补记录的业务主键之后,事务提交之前,如果产生了过程暂停 P,或者事务外部进行网络申请呈现了拥塞,导致本地事务期待较久
- 全局事务超时后,Cancel 执行,因为没有查到要弥补的业务主键,因而判断是空弥补,间接返回
- Try 的过程暂停完结,最初提交本地事务
- 全局事务回滚实现后,Try 分支的业务操作没有被回滚,产生了悬挂
事实上,NPC 里的 P 和 C,以及 P 和 C 的组合,有很多种的场景,都能够导致上述竞态状况,就不一一赘述了。
尽管这种状况产生的概率不高,然而在金融畛域,一旦波及金钱账目,那么带来的影响可能是微小的。
PS:幂等管制如果也采纳“先查再改”,也是一样很容易呈现相似的问题。解决这一类问题的关键点是要利用惟一索引,“以改代查”来防止竞态条件。
子事务屏障技术
上面咱们来详解 dtm 是如何解决这个问题的。
dtm 独创了子事务屏障技术,用于同时解决空弥补、防悬挂、幂等这三个问题,对于 TCC 事务,他的具体工作过程如下:
- 在本地数据库中创立好子事务屏障表 dtm_barrier.barrier,惟一索引为 gid-branchid-branchop
- 对于 Try、Confirm、Cancel 操作,insert ignore 一条记录 gid-branchid-try|confirm|cancel,如果影响行数为 0(反复申请、悬挂),间接提交返回
- 对于 Cancel 操作额定再 insert ingore 一条记录 gid-branchid-try,如果影响行数为 1(空弥补),间接提交返回
- 执行业务逻辑并提交返回,如果业务产生谬误则回滚
如果 Try 和 Cancel 的执行工夫没有重叠,那么读者容易剖析出上述过程可能解决空弥补和悬挂问题。如果呈现了 Try 和 Cancel 执行时间重叠的状况,咱们看看会产生什么。
假如 Try 和 Cancel 并发执行,Cancel 和 Try 都会插入同一条记录 gid-branchid-try,因为惟一索引抵触,那么两个操作中只有一个可能胜利,而另一个则会等持有锁的事务实现后返回。
- 状况 1,Try 插入 gid-branchid-try 失败,Cancel 操作插入 gid-branchid-try 胜利,此时就是典型的空弥补和悬挂场景,依照子事务屏障算法,Try 和 Cancel 都会间接返回
- 状况 2,Try 插入 gid-branchid-try 胜利,Cancel 操作插入 gid-branchid-try 失败,依照上述子事务屏障算法,会失常执行业务,而且业务执行的程序是 Try 在 Cancel 前
- 状况 3,Try 和 Cancel 的操作在重叠期间又遇见宕机等状况,那么至多 Cancel 会被 dtm 重试,那么最终会走到状况 1 或 2。
综上各种状况的具体阐述,子事务屏障可能在各种 NP 状况下,保障最终后果的正确性。
事实上,子事务屏障有大量长处,包含:
- 两个 insert 判断解决空弥补、防悬挂、幂等这三个问题,比其余计划的三种状况别离判断,逻辑复杂度大幅升高
- dtm 的子事务屏障是 SDK 层解决这三个问题,业务齐全不须要关怀
- 性能高,对于失常实现的事务(个别失败的事务不超过 1%),子事务屏障的额定开销是每个分支操作一个 SQL,比其余计划代价更小。
上述的实践与剖析过程也同样实用于 SAGA。dtm 外面的子事务屏障同时反对了 TCC 和 SAGA 两种事务模式。
Java 接入
dtm 是首个反对主动解决子事务乱序问题的开源框架,极大的加重了业务累赘,升高了分布式事务的应用门槛。
dtm 曾经提供了 Java 的 SDK,蕴含了子事务屏障性能,帮忙用户主动解决子事务乱序问题。DTM 提供的是一套 SDK 模式(非注解模式)的接口,易于了解和应用。
咱们编写了一个十分残缺的示例,该示例的次要性能模仿了一个跨行转账(跨服务转账)的业务场景,蕴含的内容有:如何组织子事务、如何做冻结资金、如何解决失败、如何应用子事务屏障。您在这个例子的根底上改一改,就可能实现您理论的 TCC 分布式事务。
这篇文章用 Java 轻松实现一个分布式事务 TCC,主动解决空弥补、悬挂、幂等给出了具体的 Java TCC 事务原理与接入过程,不便用户疾速上手。
小结
浏览完这篇干货文,心愿这里介绍的子事务屏障技术可能帮忙你更好更正确的实现你的分布式事务。
咱们不仅提供了后面 Java 的子事务屏障 SDK,还提供了 go、python 版本的子事务屏障,有须要能够自取:dtm
如果您有分布式事务相干的业务需要,dtm 可能反对多种语言,并且简略易上手。
如果您想要学习分布式事务相干的常识,dtm 的文档备受好评,可能让读者疾速入门分布式事务,实践联合实际,让读者逐渐深刻。
欢送大家拜访 dtm,欢送 Issue、PR、Star