vlambda博客
学习文章列表

分布式事务,你懂了吗

前言

事务是什么想必不用我来说是什么了吧,在单机系统中,我们可以通过spring来管理事务,不管是编程式事务还是声明式事务都非常的不戳。但是单体系统中的有事务一旦牵扯到了分布式,就会有些问题,先思考一下问什么?而且在项目中事务必然是逃不过的一个问题,分布式系统中更是如此,那今天就来说一下分布式系统中的常见方案吧。

单机事务与分布式事务

先来回答上面问题,单机系统中的事务为什么到了分布式中不适用。

分布式事务,你懂了吗

就拿用户要下单来说,订单接口要做的首先是要创建订单,然后远程调用库存服务扣减库存,如果订单时由于各种问题失败了,但是扣减库存的请求已经发到另一个服务了,已经对库存做出了更改。我们没办法让另一个服务即时的进行回滚,做不到要么一起成功,要么一起失败。

为了更好地理解分布式事务,先来了解一些基本的概念。

事务的基本性质

原子性:一系列的操作整体不可分割,要么一起成功,要么一起失败。
一致性:数据在事务前后,业务整体保持一致。
隔离性:不同的事务之间相互隔离。
持久性:一旦事务成功,数据会持久化在数据库。

事务的ACID,这个就不多说了。

CAP定律

一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

分布式事务,你懂了吗

也就是说这三个指标不可能同时做到,最多只能3选2,这个结论就叫做CAP定理。

BASE理论

BASE理论是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写。
核心思想:即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

  • 基本可用(Basically Available)

    • 什么是基本可用呢?假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:

    • 响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用的搜索引擎可以在2秒作用返回结果。功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单。但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。

  • 软状态(Soft State)    * 相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。

    • 软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

  • 最终一致性(Eventually Consistent)  

    • 上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性,从而达到数据的最终一致性。这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。

BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。其实在分布式系统中,不仅仅是分布式事务,基本所有的技术都是围绕CAP和BASE展开的。

XA方案

分布式事务,你懂了吗

xa方案是是基于两阶段提交的,它实现了两阶段提交协议。定义了事务管理器 (TM)以及一个或多个资源管理器(RM)以及应用程序(AP),并使它们之间形成通信。XA的关键是在于由事务管理器 (TM)来控制两阶段提交。

分布式事务,你懂了吗

结合这张图来看,第一阶段为准备阶段,事务协调器要求每个涉及到事务的数据库提交此操作,并反映是否可以提交。第二阶段为提交阶段。事务协调器要求每个数据库提交数据,其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此次事务中的那部分信息。

优缺点:
1.XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。
2.XA性能不理想,特别是在交易下单链路,往往并发很高,XA无法满足高并发场景
3.在mysql中支持不理想,mysql的XA实现,没有记录prepare阶段日志,主备切换会导致两个数据库数据不一致
4.许多nosql也没有支持XA,这让XA的应用场景变的非常狭义。
5.TM出现问题,一直修复不了的话,RM会一直阻塞,但也有3阶段提交,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)

TCC事务补偿型方案

分布式事务,你懂了吗

TCC的全称是: TryConfirmCancel
Try阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
Confirm阶段:这个阶段说的是在各个服务中执行实际的操作。
Cancel阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)


TCC提供了另外的一个思路, 就是想冻结事务需要的资源,再协调所有的应用的事务,如果所有应用都成功了就会提交事务,正常消费冻结资源, 如果有应用事务失败, 就会解冻所有冻结的资源, TCC也是需要分段的,同时也有一个事务管理器。
第一阶段 prepar阶段:调用自定义的prepare逻辑。
第二阶段 commit/rollback阶段:调用自定义的commit/rollback逻辑。

优缺点:
1.不会阻塞其他的事务, 因为提前冻结了资源, 其他的事务就不会和他竞争资源了,自然就不会阻塞。
2.虽然冻结了资源不用和其他事务竞争增加了并发性,但是在本事务内部各个应用还是需要阻塞等待其他所有应用的完成的,所以要应用的话建议在各个业务执行的时间都比较短的时候再用。
3.增加了业务的复杂度,需要编写try/confirm/cancel三个方法。
4.对应用的侵入性强, 不同的业务需要使用不同的 冻结解冻消费资源业务代码,并且各个应用不能通用。

最大努力通知

按字面意思来理解,就是尽自己最大的努力来通知,一遍是用于短信发送验证码,或者是邮箱验证之类的,大家应该也都遇到过短信验证码没有发送过来的情况。其实现就是按规律进行通知,不保证数据一定能通知成功,但会提供可查询的操作接口进行核对。这种方案也是结合mq进行实现的。

分布式事务,你懂了吗

例如:通过mq发送http请求,设置最大的通知次数5次,达到通知次数后不再通知。那么当服务A调用服务B失败时,就会进行重试,5次之后还失败那就算是失败了,毕竟我都已经尽我最大努力来通知你了,你还要我怎样。

分布式事务,你懂了吗

可靠消息+最终一致性方案

分布式事务,你懂了吗

此方案基于本地事务+MQ的可靠消息来实现,通过异步消息的方式,确保最终一致性。

那么结合上图,来说明下详细的步骤。
1.服务A确认处理完业务后,发送一条消息给MQ,成功则MQ回复ACK,失败则直接回滚服务A。
2.MQ收到消息发送消息给远程消费端服务B,收到消息回复ACK。或是MQ接收到消息后可以进行消息持久化到DB,并记录消息状态。
3.服务B收到消息进行确认,若是已持久化消息,那么可以再更改消息状态。
4.服务B进对消息行消费,消费成功后回复MQ,若已经持久化消息则改变消息状态为完成
5.可以定期对持久化消息的表进行扫描,特殊情况可以介入人工处理

可以看到此最重要就是消息的可靠性性,想要了解更多关于可靠消息的可以看我的另一篇文章,可靠消息

seata

Seata 是一款阿里开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 也 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

分布式事务,你懂了吗

这是seata官网首页的一张图。可以看出它的核心是由 分布式事务处理过程的ID+三组件来完成的。

Transaction Coordinator(TC)(事务协调者):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
Transaction Manager(TM)(事务管理器):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
Resource Manager(RM)(资源管理器):控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
XID(事务ID):全局事务的id

今天主要详细说明下AT模式,使用方式类似于spring的@transcational注解,AT模式使用@GlobalTransactional就能控制全局事务,可以做到对业务的无入侵,它是怎么来实现的呢?

执行过程

1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一 个全局的唯一的XID
2.XID在微服务调用链路中的上下文中传播
3.RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
4.TM向TC发起针对XID的全局提交或回滚决议
5.TC调度XID下管辖的全部分支事务完成提交或回滚请求

AT模式机制(到对业务的无侵入)

  • 一阶段:

    • 业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和链接资源。

  • 二阶段:

    • 提交异步化,非常快速的完成

    • 回滚通过一阶段的回滚日志进行反向补偿

实现

一阶段加载:
在一阶段,seata会拦截“业务sql”
1.解析sql语义,找到“业务sql”要更新的业务数据,在业务数据被更新前,将其保存成“before image”
2.执行“业务sql”更新业务数据
3.在业务数据更新之后,将其保存成“after image”,最后生成行锁

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

分布式事务,你懂了吗

二阶段提交:
因为“业务sql”在一阶段已经提交至数据库,所以seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据的清理即可。

二阶段回滚:
二阶段如果是回滚的话,seata就需要回滚一阶段已经执行的“业务sql”,还原业务数据。
回滚的方式便使用“before image”还原业务数据,但在还原首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

小结

通过上面所述,说了现如今常用的几种方案,但并不是所说分布式事务就可以来强行用,就是不要为了用分布式事务而去用。分布式事务问题在我们开发中还是需要来积极避免的,使用的话也是根据项目业务去具体选择方案落地的。

写了好几篇分布式相关的东西,本来打算接下来写一些集群搭建相关的,不过因为最近开始学习大数据可能得放一放了。不过在学习大数据期间我会记录下我的学习过程,欢迎各位小伙伴吗,加我或者提前关注一波,一起交流与学习。

完!

关注我,我们一起学习,一起进步。
每天都要变得比昨天更好一点.