找回密码
 立即注册
首页 业界区 业界 浅谈为什么我讨厌分布式事务

浅谈为什么我讨厌分布式事务

挡缭 6 小时前
CAP,基础理论

CAP理论是分布式系统中最核心的理论基础
1.png


  • Partition tolerance,分区容错性
the system continues to operate despite arbitrary message loss or failure of part of the system
系统能够在网络分区(网络故障或通信故障)的情况下还能继续提供服务

  • Consistency,一致性
all nodes see the same data at the same time
所有节点在同一时间的数据是相同的,也就是更新操作完成后,所有节点保存的数据相同

  • Availability,可用性
Reads and writes always succeed
系统服务一直处于可用状态,
为什么CAP只能满足两个目标

2.png

C,A,P只能同时满足两个目标,而P是分布式的基本盘,所以需要在C与A之间进行取舍。

  • 如果要保证服务可用性,就选择AP模型
    选择AP意味着舍弃强一致性,以保证可用性与分区容错性。在AP架构下,系统能够快速响应客户端的请求,即时在网络分区情况下,也尽量保证服部分节点可用,从而为客户端提供不间断的服务,允许临时数据不一致(如异步复制)
  • 如果要保证数据一致性,就选择CP模型
    选择CP意味着舍弃可用性,以保证一致性与分区容错性。在CP架构下,系统会优先保证数据的一致性,当发生网络分区时,牺牲可用性(如暂停写操作直到分区修复)。
  • 单机部署,就选择CA模型
场景:
你点外卖,选好商品后提交订单。但此时你刚好在电梯里,网络变差了(网络分区)。
你与服务器失去联系,无法确认订单真的提交成功,但你还是可以操作APP继续浏览菜单的(P,分区容错)。
这时候APP必须做出选择,A VS C.
选A(可用性),直接显示已提交(即使服务器没收到)。但如果网络一直没恢复,订单可能没提交成功,后续你需要重新下单(数据不一致)
选B(一致性),APP直接卡死或者显示加载中,必须等系统确认后才显示成功(数据一定正确,但期间无法操作)。
CAP的理论,只有网络分区发生了,才需要在CAP中进行权衡,但大部分时间,网络分区是不会发生的。因此CAP理论可以作为系统设计时需要衡量的因素,而非绝对的选择。
PACELC,拓展理论

CAP+Everything is Local and Connected = PACELC ,是CAP理论的拓展,上面说到CAP理论是在网络分区发生的情况下才需要考虑的。大多数情况下,系统都是平稳运行。在这种情况下,因为不需要考虑网络分区,所以要考虑就是数据一致与读写延迟的平衡

  • P
    分布式系统在面对网络分区时(网络故障或通信故障),仍然能够继续运行。
  • A
    分布式系统在面对故障时,依然能够提供服务并保证数据的可访问性。
  • C
    分布式系统在面对网络分区时,能够保持数据一致性。
  • E
    Eventual consistency,最终一致性,分布式系统在面对网络分区时,由于网络延迟或者异步复制等原因,可能会导致节点之间数据的不一致,但最终会达到一致状态
  • L
    Latency ,延迟,分布式系统的响应时间,在一些情况下,为提高系统响应速度,可能会牺牲一致性或者可用性
3.png

场景
承接上面的场景,当你出了电梯后,你的网络开始恢复。在此期间你点击了多次提交订单,导致服务器收到了多个重复的订单请求。
此时进入ELC矛盾阶段,在延迟(Latency)和一致性(Consistency)之间需要权衡。
选延迟(Latency),你很快看到订单状态更新,但可能不知道之前的重复提交被自动合并了(牺牲一致性,但用户体验流畅)。
选一致性(Consistency),弹出提示 “检测到多个订单请求,请确认是否需要保留一个”,等你手动选择后再处理。订单一定准确(一致性高),但你需要花时间确认(延迟高)
因此,PACELC也可以理解是对CAP的替代,因为它不仅讲述了网络分区的极端情况下如何取舍,还覆盖了系统正常时应该考虑的事项。
BASE,具体落地理论

Base 理论是 AP 系统的实践指导,通过牺牲强一致性换取高可用性和分区容错性,是 CAP 定理在工程中的具体落地方式。

  • Basically Available,基本可用
    不追求CAP中的,任何时候读写都可以成功,而是系统能够基本运行,一直提供服务。
    允许损失部分可用,可能是响应时间延长,或者服务降级。
举个例子,如果并发太多超过了系统QPS峰值,可能会提示排队。这就是通过合理手段保护系统的稳定性。

  • Soft State ,软状态
    允许系统存在中间状态,比如异步复制的延迟。并认为该状态不影响整体运行,也就是允许系统在多个不同的节点的数据副本存在数据延时
比如分布式缓存中,数据副本允许短暂不一致。

  • Eventually Consistency ,最终一致性
    数据不可能一直是软状态,必须在一个时限之后保证所有副本数据一致。
Base理论的核心是最终一致性,即时无法做到强一致性(Strong Consistency),Application可以根据自身的业务特点,采用适当的方式来达到最终一致性
CAP在基础组件中的应用

如果一个分布式场景需要很强的数据一致性,或者该场景可以接收系统相应很慢的情况。使用CP架构就比较合适了,
保证CP的架构也很多,典型的有Redis,ZooKeeper。
以ZooKeeper为例:
4.png

zk这种设计保证了CP,需要超过一半节点同意才提交写操作,这中间的可用性是很低的。
如果一个分布式场景需要很强的可用性,且能接收数据暂时不一致,那么使用AP架构就比较合适。
保证AP的架构就很多了,比如数据库的读写分离,Eureka等。为了保证用户体验,牺牲了数据的一致性。
分布式事务

分布式事务是指涉及多个独立数据库或节点的事务操作,需要保证跨节点的数据一致性。由于分布式的特性导致了传统数据库事务的ACID特性难以直接应用。
由于CAP理论的桎梏,分布式事务也需要妥协部分特性,转而采用最终一致性(BASE理论)。
2PC,Two-Phase Commit

原理:事务分两阶段提交

  • PreCommit
    通知所有节点,开启事务并预提交
  • DoCommit
    根据参与者的反馈,决定提交还是回滚。
    如果参与者全部同意,协调者通知所有参与者提交事务
    如果任一节点失败,协调者通知所有参与者回滚
5.png

3PC,Three-Phase Commit

从2PC可以看到,如果Service2服务一开始就不可用,Service1与Service3依旧会开启事务。直到协调者通知回滚才关闭事务。这中间的粒度太大了,为了优化这个问题,有了3PC

  • CanCommit
    多衍生出一个询问阶段,仅询问是否提供服务,不开启事务
  • PreCommit
    若全部同意,开始事务预提交
  • DoCommit
    正式提交或者回滚
6.png

TCC,Try-Confirm-Cancel

尽管3PC缩小了2PC阻塞的粒度,但在PreCommit阶段之后,所有节点都会开启事务,这时候阻塞的粒度,依旧很大,这里还有没有优化空间呢?TCC方案就应运而生。
TCC原理与2/3PC类似,最大不同的点就是,2/3PC依赖数据库的事务机制,TCC更依赖让代码逻辑来变相实现事务。

  • Try
    协调者调用所有微服务API的try接口,将涉及到的资源提前创建或者锁住
  • Confirm/Cancel
    正式提交或者回滚
7.png

可以看到,TCC模式在流程上,除了颗粒度小了一点外,没有本质上的区别,那我如果对并发性没有要求的话,可以无脑使用2PC呢?
使用TCC还有一个核心的因素,2PC/3PC是基于数据库的XA协议,比较局限。只能在多个数据库之间实现分布式事务,而大多数情况下,都是微服务之间的API调用,并没有实现XA协议,因此TCC才登上历史舞台。
实现TCC要注意什么?

既然TCC是一种2PC的变种实现,那么它是如何解决Service出现故障后的数据一致性的呢?
答案是不断重试,因为try阶段已经提前创建或者锁定好了所有资源,所以无论是Confirm或者cancel都可以通过不断重试直到成功。

  • try超时
    比如try过程中很慢(进程还在处理),导致了try超时。会触发多次重试,而多次重试,就需要考虑幂等的操作。
    可以利用幂等表来先查后写,来规避重复执行的问题。
要考虑第一次的try因为网络拥塞,所以在Confirm/Cancel后才到,代码逻辑一定要严谨

  • confirm超时
    检查业务状态,避免重复confirm,在极端情况下,两个重复请求同时到达,可以通过业务规则丢弃请求,比如已经提交了,再来个提交请求,就直接返回成功或者丢弃。
要考虑confirm因为网络拥塞,晚于cancel到达的情况

  • cancel超时
    同上,网络拥塞与幂等依旧是实现的难题。
要考虑cancel后,confirm到达的问题。

  • 空回滚
    Service Try还未执行,因为其它Service已经失败,所以发来了Cancel消息。
    Cancel需要清理无效资源,而资源未创建,如果代码不够健壮,就会报错。
  • 事务悬挂
    Cancel 或 Confirm 已执行后,因为拥塞而迟到的 Try 请求到达。
    Try阶段需要检查事务是否已经结束,否则拒绝执行或忽略
可以看到,自己实现TCC是一件很麻烦的事情。这对于研发来说简直就是一种灾难。
所以TCC一般都会搭配一张幂等表,来作为状态标记,辅助判断重试中过程中的各种情况。
分布式事务Id事务描述事务状态主键Id68402338-ec9d-489c-ba07-abb42546329d订单创建ACKxxxxb70240b8-17ce-4005-b322-e9b886d41e4b库存扣减Tryxxxx2bdd077c-d221-49ce-a7d4-6f441dec1928金额预扣款Cancelxxxx最终一致性事务

无论是基于2PC/3PC还是TCC的解决方案,核心都是基于XA协议的思路。事务参与者创建本地事务,由协调者协调最终的事务提交还是回滚。
上述的方案中,创建本地事务终究需要排他等待(强一致性),无论你如何优化都是无法避免的,因为这是CAP定理的桎梏。
这些全局事务方案由于操作麻烦,排他等待等因素,此类架构的架构是保证了强一致性,所以并发度不会高。
面对互联网主流的AP架构,演化出了与XA协议背道而驰的最终一致性解决方案。
8.png


  • Order Service
    开启一个本地事务,同时提交业务数据,扣减库存数据+预付款数据。保证强一致性。
  • 独立轮询服务捞数据
    由于网络原因,Order Serivce发送给MQ的消息可能会丢。因此需要一个服务一直扫描未发送的数据
  • Inventory Service/Payment Service
    收到MQ消息后,首先是幂等性校验,与TCC类似,本来会有一张幂等表来辅助实现接口幂等。
    并在同一个事务中,更新业务逻辑与幂等状态。
    如果处理失败,不会返回ACK,所以MQ会不断重试。
    如果是业务失败,同Order Service流程,回滚自己的事务同时,向Msg表发送Cancel 扣减库存数据+预付款数据。实现分布式事务回滚。
FAQ


  • 独立的轮询服务挂了
    多部署几个实例,集群化。
    要万一挂了,因为本地Msg表状态还是未发送,所以再重新发一次就好
  • MQ挂了?
    Msg是持久化在数据库中,所以消息并不会丢。如果长时间挂,再人工介入。
  • 消费端挂了?
    基于MQ的ACK机制,如果处理失败,MQ会不断重试,直到触发阈值,人工介入。
  • 消费端幂等性?
    每个服务都有自己的消息消费记录表,记录下处理过的MQ MessageId 先查后写。或者利用主键等本身能证明唯一性的数据,防止重复处理。
  • 死循环消费
    因为代码问题,导致的永远不会消费成功,可以在消息表中设计最大重试次数,或者挪到死信队列中,人工介入。
  • 本地消息表过大,影响性能
    随着写入量的增加,表的大小日积月累。这就是数据库单表优化的思路了,归档,分表等常规操作
回归正题,为什么我讨厌分布式事务

分布式事务的本质是 “妥协的艺术”,为了解决一个问题,从而引发出更多的问题。
使命召唤游戏里有一句经典台词,扑灭一场火最好的办法,是在旁边点燃一团更大的火,烧光它的可燃物,耗尽它的氧气,火自然就灭了。
不管是强一致性的2PC/3PC/TCC,还是最终一致性的本地消息表/消息队列。他们都会引入一些更麻烦的操作来实现,
比如2PC/3PC依赖DTC,TCC依赖程序员自身的素养,性能低的同时开发跟维护成本高。
最终一致性的方案也好不到哪里去,超时重试(避免因短暂延迟导致事务失败),幂等性(避免重复操作导致数据错误),补偿冲突(如多个补偿操作同时执行,需加锁或版本控制),都是分布式系统的必须处理的部分。
总结:分布式事务是 “最后的手段”,能通过业务规避就尽量规避,仅在 “数据绝对不能错”(如金融)或 “资源绝对不能冲突”(如航空订票)的场景中使用。
说人话就是,我只想早点下班,分布式事务太麻烦了。各位的项目没到那个体量就别给自己找麻烦。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册