分布式事务实现⽅案及其优缺点以及适⽤的场景
分布式事务
本⽂会介绍常见的分布式事务实现⽅案和其优缺点以及适⽤的场景,并会带出它们的⼀些变体实现。
还会捎带⼀下分布式数据库对 2PC 的改进模型,看看分布式数据库是如何做的。
然后再分析⼀波分布式事务框架Seata的具体实现,看看分布式事务究竟是如何落地的。
事务
分布式和微服务的关系
事务的ACID想必⼤家都熟知,这其实是严格意义上的定义,指的是事务的实现必须具备 原⼦性、⼀致性、隔离性和持久性。
不过严格意义上的事务很难达到,像我们熟知的数据库就有各种隔离级别,隔离级别越⾼性能越低,所以往往我们都会从中到属于⾃⼰的平衡,不会遵循严格意义上的事务。
并且在我们平⽇的谈论中,所谓的事务往往简单的指代⼀系列的操作全部执⾏成功,或者全部失败,不会出现⼀些成功⼀些失败的情形。
清晰了平⽇我们对事务的定义之后,再来看看什么是分布式事务。
分布式事务
由于互联⽹的快速发展,以往的单体架构顶不住这么多的需求,这么复杂的业务,这么⼤的流量。
单体架构的优势在于前期快速搭建、快速上线,并且⽅法和模块之间都是内部调⽤,没有⽹络的开销更加的⾼效。从某⽅⾯来说部署也⽅便,毕竟就⼀个包,扔上去。
不过随着企业的发展,业务的复杂度越来越⾼,内部耦合极其严重,导致牵⼀发⽽动全⾝,开发不易,测试不易。并且⽆法根据热点服务进⾏动态的伸缩,⽐如商品服务访问量特别⼤,如果是单体架
构的话我们只能把整个应⽤复制多份集部署,浪费资源。因此拆分势在必⾏,微服务架构就这么来了。
拆分之后服务之间的边界就清晰了,每个服务都能独⽴地运⾏,独⽴地部署,所以能以服务级别弹性伸缩了。
服务之间的本地调⽤变成了远程调⽤,链路更长了,⼀次调⽤的耗时更长了,但是总体的吞吐量更⼤了。
不过拆分之后还会引⼊其他复杂度,⽐如服务链路的监控、整体的监控、容错措施、弹性伸缩等等运维监控的问题,还有像分布式事务、分布式锁跟业务息息相关的问题等。
往往解决了⼀个痛点⼜会引⼊别的痛点,所以架构的演进都是权衡的结果,就看你们的系统更能忍受哪种痛点了。
⽽今天我们谈及的就是分布式事务这个痛点。
分布式事务是由多个本地事务组成的,分布式事务跨越了多设备,之间⼜经历的复杂的⽹络,可想⽽知想要实现严格的事务道路阻且长。
单机版事务都不会严格遵守事务的严格实现,更别说分布式事务了,所以在现实情况下我们只能实现残缺版的事务。
在明确了事务和分布式事务之后,我们就先来看看常见的分布式事务⽅案:2PC、3PC、TCC、本地消息、事务消息。
⽅案1:2PC
2PC,Two-phase commit protocol,即 两阶段提交协议。它引⼊了⼀个 事务协调者 ⾓⾊,来管理各个 参与者(就是各数据库资源)。
整体分为两个阶段,分别是 准备阶段 和 提交/回滚阶段。
第⼀个阶段:准备阶段。
1. 由事务协调者给每个参与者发送准备命令,每个参与者收到命令之后会执⾏相关事务操作,你可以认为除了事务的提交啥都做了。
2. 然后每个参与者会返回响应告知协调者⾃⼰是否准备成功。
3. 协调者收到每个参与者的响应之后就进⼊第⼆阶段,根据收集的响应,如果有⼀个参与者响应准备失败那么就向所有参与者发送回滚命令,反之发
送提交命令。
第⼆个阶段:提交 / 回滚阶段。
这个协议其实很符合正常的思维,就像上课点名的时候,⽼师就是协调者的⾓⾊,学⽣都是参与者。
⽼师⼀个⼀个的点名,我们⼀个⼀个的喊到,最后⽼师收到所有同学的到之后就开始了今天的讲课。
⽽和点名有所不同的是,⽼师发现某⼏个学⽣不在还是能继续上课,⽽我们的事务可不允许这样。
事务协调者在第⼀阶段未收到个别参与者的响应,则等待⼀定时间就会认为事务失败,会发送回滚命令,所以在 2PC 中事务协调者有超时机制。
2PC 的优缺点
2PC 的优点是能利⽤数据库⾃⾝的功能进⾏本地事务的提交和回滚,也就是说提交和回滚实际操作不
需要我们实现,不侵⼊业务逻辑由数据库完成,在之后讲解 TCC 之后相信⼤家对这点会有所体会。
2PC 主要有 三⼤缺点:同步阻塞、单点故障、数据不⼀致 问题。
同步阻塞
可以看到在第⼀阶段执⾏了准备命令后,我们每个本地资源都处于锁定状态,因为除了事务的提交之外啥都做了。所以这时候如果本地的其他请求要访问同⼀个资源,⽐如要修改商品表 id 等于 100 的那条数据,那么此时是被阻塞住的,必须等待前⾯事务的完结,收到提交/回滚命令执⾏完释放资源后,这个请求才能得以继续。所以假设这个分布式事务涉及到很多参与者,然后有些参与者处理⼜特别复杂,特别慢,那么那些处理快的节点也得等着,所以说效率有点低。
单点故障
可以看到这个单点就是协调者,如果协调者挂了整个事务就执⾏不下去了。如果协调者在发送准备命令前挂了还⾏,毕竟每个资源都还未执⾏命令,那么资源是没被锁定的。可怕的是在发送完准备命令之后挂了,这时候每个本地资源都执⾏完处于锁定状态了,都杵着了,这就很僵硬了,如果是某个热点资源都阻塞了,这估计就要GG了。
数据不⼀致问题
因为协调者和参与者之间的交流是经过⽹络的,⽽⽹络有时候就会抽风的或者发⽣局部⽹络异常。那么就有可能导致某些参与者⽆法收到协调者的请求,⽽某些收到了。⽐如是提交请求,然后那些收到命令的参与者就提交事务了,此时就产⽣了数据不⼀致的问题。
2PC⼩结
它是⼀个 同步阻塞 的 强⼀致性两阶段提交协议,分别是准备阶段和提交/回滚阶段。
2PC 的优势在于对业务没有侵⼊,可以利⽤数据库⾃⾝机制来进⾏事务的提交和回滚。
它的缺点:是⼀个同步阻塞协议,会导致⾼延迟和性能的下降,并且存在协调者单点故障问题,极端情况下会有数据不⼀致的问题。
当然这只是协议,具体的落地还是可以变通了,⽐如协调者单点问题,我就搞个主从来实现协调者,对吧。
分布式数据库的 2PC 改进模型
可能有些⼈对分布式数据库不熟悉,没有关系,我们主要学的是思想,看看⼈家的思路。
简单的讲下 Percolator 模型,它是基于分布式存储系统 BigTable 建⽴的模型,BigTable 是啥也不清楚的同学没有关系影响不⼤。
还是拿转账的例⼦来说,我现在有 200 块钱,你现在有 100 块钱,为了突出重点我也不按正常的结构来画这个表。
然后我要转 100 块给你。
1. 此时事务管理器发起了准备请求,然后我账上的钱就少了,你账上的钱就多了,⽽且事务管理器还记录下这次操作的⽇志。
2. 此时的数据还是私有版本,别的事务是读不到的,简单的理解 Lock 上有值就还是私有的。可以看到我的记录 Lock 标记的是 PK,你的记录标记
的是指向我的记录指针,这个 PK是随机选择的。
3. 然后事务管理器会向被选择作为 PK 的那条记录发起提交指令。
此时就会把我的记录的锁给抹去了,这等于我的记录不再是私有版本了,别的事务就都能访问了。
那你的记录上还有锁啊?不⽤更新吗?
答案是:不需要及时更新,因为访问你的这条记录的时候会去根据指针我的那个记录,发现记录已经提交了所以你的记录就可以被访问了。
有⼈说这效率不就差了,每次都要去⼀次,别急。后台会有个线程来扫描,然后更新把锁记录给去了。这不就稳了嘛。
相⽐于 2PC 的改进
⾸先 Percolator 在提交阶段不需要和所有的参与者交互,只需要和⼀个参与者打交道,所以这个提交是原⼦的!解决了数据不⼀致问题。
然后事务管理器会记录操作⽇志,这样当事务管理器挂了之后选举的新事务管理器就可以通过⽇志来得知当前的情况从⽽继续⼯作,解决了单点故障问题。
并且 Percolator 还会有后台线程,会扫描事务状况,在事务管理器宕机之后会回滚各个参与者上的事务。
可以看到相对于 2PC 还是做了很多改进的,也是巧妙的。 其实分布式数据库还有别的事务模型,有兴趣的同学可以⾃⾏了解。
XA 规范
让我们再回来 2PC,既然说到 2PC 了那么也简单的提⼀下 XA 规范,XA 规范是基于两阶段提交的,它实现了两阶段提交协议。
在说 XA 规范之前⼜得先提⼀下 DTP (Distributed Transaction Processing)模型,这模型规范了分布式事务的模型设计。
⽽ XA 规范⼜约束了 DTP 模型中的事务管理器(TM) 和资源管理器(RM)之间的交互,简单的说就是你们两之间要按照⼀定的格式规范来交流!
我们先来看下 XA 约束下的 DTP 模型。
AP 应⽤程序,就是我们的应⽤,事务的发起者。
RM 资源管理器,简单的认为就是数据库,具备事务提交和回滚能⼒,对应我们上⾯的 2PC 就是参与者。
TM 事务管理器,就是协调者了,和每个 RM 通信。
简单的说就是 AP 通过 TM 来定义事务操作,TM 和 RM 之间会通过 XA 规范进⾏通信,执⾏两阶段提交,⽽ AP 的资源是从 RM 拿的。
从模型上看有三个⾓⾊,⽽实际实现可以由⼀个⾓⾊实现两个功能,⽐如 AP 来实现 TM 的功能,TM 没必要抽出来单独部署。
MySQL XA
知晓了 DTP 之后,我们就来看看 XA 在 MySQL 中是如何操作的,不过只有 InnoDB ⽀持。
简单的说就是要先定义⼀个全局唯⼀的 XID,然后告知每个事务分⽀要进⾏的操作。
可以看到图中执⾏了两个操作,分别是改名字和插⼊⽇志,等于先注册下要做的事情,通过 XA START XID 和 XA END XID 来包裹要执⾏的 SQL。
然后需要发送准备命令,来执⾏第⼀阶段,也就是除了事务的提交啥都⼲了的阶段。
然后根据准备的情况来选择执⾏提交事务命令还是回滚事务命令。
基本上就是这么个流程,不过 MySQL XA 的性能不⾼这点是需要注意的。
可以看到虽说 2PC 有缺点,但是还是有基于 2PC 的落地实现的,⽽ 3PC 的引出是为了解决 2PC 的⼀些缺点,但是它整体下来开销更⼤,也解决不了⽹络分区的问题,我也没有到 3PC 的落地实现。
不过我还是稍微提⼀下,知晓⼀下就⾏,纯理论。
⽅案2:3PC
3PC 的引⼊是为了解决 2PC 同步阻塞和减少数据不⼀致的情况。
3PC 也就是多了⼀个阶段,⼀个询问的阶段,分别是 准备、预提交、提交 这三个阶段。
1. 准备阶段单纯就是协调者去访问参与者,类似于你还好吗?能接请求不。
2. 预提交其实就是 2PC 的准备阶段,除了事务的提交啥都⼲了。
3. 提交阶段和 2PC 的提交⼀致。
3PC 多了⼀个阶段其实就是在执⾏事务之前来确认参与者是否正常,防⽌个别参与者不正常的情况下,其他参与者都执⾏了事务,锁定资源。出发点是好的,但是绝⼤部分情况下肯定是正常的,所以每次都多了⼀个交互阶段就很不划算。
然后 3PC 在参与者处也引⼊了超时机制,这样在协调者挂了的情况下,如果已经到了提交阶段了,参与者等半天没收到协调者的情况的话就会⾃动提交事务。
不过万⼀协调者发的是回滚命令呢?你看这就出错了,数据不⼀致了。
还有上说 2PC 参与者准备阶段之后,如果协调者挂了,参与者是⽆法得知整体的情况的,因为⼤局是协调者掌控的,所以参与者相互之间的状况它们不清楚。⽽ 3PC 经过了第⼀阶段的确认,即使协调者挂了参与者也知道⾃⼰所处预提交阶段是因为已经得到准备阶段所有参与者的认可了。
简单的说就像加了个围栏,使得各参与者的状态得以统⼀。
⼩结 2PC 和 3PC
从上⾯已经知晓了 2PC 是⼀个强⼀致性的同步阻塞协议,性能已经是⽐较差的了。
⽽ 3PC 的出发点是为了解决 2PC 的缺点,但是多了⼀个阶段就多了⼀次通讯的开销,⽽且是绝⼤部分情况下⽆⽤的通讯。虽说引⼊参与者超时来解决协调者挂了的阻塞问题,但是数据还是会不⼀致。
可以看到 3PC 的引⼊并没什么实际突破,⽽且性能更差了,所以实际只有 2PC 的落地实现。
再提⼀下,2PC 还是 3PC 都是协议,可以认为是⼀种指导思想,和真正的落地还是有差别的。
⽅案3:TCC
不知道⼤家注意到没,不管是 2PC 还是 3PC 都是依赖于数据库的事务提交和回滚。
⽽有时候⼀些业务它不仅仅涉及到数据库,可能是发送⼀条短信,也可能是上传⼀张图⽚。所以说事务的提交和回滚就得提升到业务层⾯⽽不是数据库层⾯了。
⽽ TCC 就是⼀种业务层⾯或者是应⽤层的两阶段提交。
TCC 分为指代 Try、Confirm、Cancel ,也就是 业务层⾯需要写对应的三个⽅法,主要⽤于跨数据库、跨服务的业务操作的数据⼀致性问题。
TCC 分为两个阶段,
1.第⼀阶段是资源检查预留阶段即 Try;
2.第⼆阶段是提交或回滚;
如果是提交的话就是执⾏真正的业务操作,如果是回滚则是执⾏预留资源的取消,恢复初始状态。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。