她一说分布式事务,我就聊歪了
有一个女领导是非常幸福的,尤其是漂亮的女领导。那感觉,绝对是痛并快乐着。
领导今天把我叫到办公室,说做事情要有事务性思维,不要虎头蛇尾的。我心想,你说的是闭环思维吧。但我不敢明说,女人的脸,打不得。于是,我悉心请教了事务性思维的处事风格,领导用分布式事务的思想教育了我一番。
古语说:斩草要除根。比喻做事要除去祸根,以免后患。只斩了草:根会有寻仇上门的隐患;只除了根:草会暴躁的找上门来。在这里,我们就可以把草和根,看成一个整体的事务。
**你要只做了一部分,就会产生问题。就像行贿你忘了某些重要的人物,就会在时间窗口发生问题。**领导教育我说。
事务是个有趣的话题,指的是不同的资源,需要原子性的达到相同的状态。事务的属性是ACID
,在英文里是酸
的意思。
什么是分布式事务?
那什么是分布式事务?
分布式事务的对立方,肯定是非
分布式事务,也就是本地事务,我们平常经常碰到的那种,也是工作中经常遇到的。
事务多指多个操作的协作,太监那种一刀除根的操作并没有什么挑战。xjjdog
也有两篇关于数据库事务的文章,虽然不是分布式问题,但也多次引起故障,可以看到事情的严重性。
那本地事务和分布式事务有什么区别?
所谓本地事务,就是所有的操作,都在一个资源中完成,比如都在同一个MySQL中完成。
这种事务的操作,依靠数据库本身提供的能力,就可以漂亮的完成任务,并没有什么难度。在Spring
中,一个Transaction注解就能完的转。
分布式事务就没那么简单,是指的不同类型的资源之间的协作问题。
就像上图,四个操作分散在三个不同的资源中。要想达到一致性,需要三个不同的资源进行统一协调。它们底层的协议,以及实现方式,都是不一样的。那就不能来个一刀切,需要借助外部的组件来完成。
一般,要完成分布式事务,比较流行的有下面几种做法。
(1)两阶段提交方案。几乎所有的OLTP
数据库,都支持类似XA
的协议。但是两阶段提交方案开发复杂、锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。现在互联网PC机上,几乎没有用这个的,或多或少是它的变种。
(2)TCC。这种方式在电商、金融领域有较多的落地,它其实是两阶段提交的一种改进,将业务逻辑的分支显示的分成了Try
、Confirm
、Cancel
三个操作。也有一些框架ByteTCC
、TCC-transaction
、Himly
等。
(3)本地消息表。使用异步来确保方案的最终一致性。这种方式比较繁琐,需要在数据库中加新表,或者新字段来实现分布式事务。如果项目不大,其实用的还是蛮多的。
(4)MQ事务消息。直接基于MQ来实现事务。但是很多MQ是不支持事务消息的(事务消息不等同于消息事务),比如 RabbitMQ 和 Kafka 都不支持。
(5)分布式事务中间件。比如Seata,以中间件服务的形式而存在。这种内部一般都很复杂,不过都是封装好的,直接调用就可以。
先不要对这几种方式和这么多的名词深入研究,这通常会让人头大。本文假定你对以上几种方式,都有初步的理解。
ACID和BASE
很多人都体验过,一些业务代码重构起来,非常的难受;或者,加入了一些保证一致性的代码,一压测,性能掉的惊掉下巴。要在业务中引入分布式事务,需要兼顾三个方面:改造成本
、性能
、时效
。
也就是,改起来顺手,跑起来走心,效果显著。
但如果我的项目并没有那么多的分布式事务需求。为了它,去引进这么笨重的事务框架,实在是吃饱了撑的。
有没有办法,来绕过强分布式事务这个命题呢?这个在局部上,是可以的。
这个时候,我们需要提到BASE
,柔性事务
,最终一致性
。最终这个词,证明战线要拉的很长,也就是实效性可能会比较大。
BASE
和ACID
是相互对立的,也非常好记。BASE是化学中碱的意思;ACID是酸的意思。
柔性事务的理念是将业务逻辑将互斥操作,从资源层上移至业务层面。
我们来简单比较一下。
ACID
关系数据库, 最大的特点就是事务处理, 即满足ACID。
-
原子性(Atomicity):事务中的操作要么都做,要么都不做。 -
一致性(Consistency):系统必须始终处在强一致状态下。 -
隔离性(Isolation):一个事务的执行不能被其他事务所干扰。 -
持续性(Durability):一个已提交的事务对数据库中数据的改变是永久性的。
BASE
分布式数据库, 最大的特点就是分布式,即满足BASE,BASE
方法通过牺牲一致性和孤立性来提高可用性和系统性能。
BASE
为Basically Available, Soft-state, Eventually consistent三者的缩写,其中BASE分别代表:
-
基本可用(Basically Available):系统能够基本运行、一直提供服务。 -
软状态(Soft-state):系统不要求一直保持强一致状态。 -
最终一致性(Eventual consistency):系统需要在某一时刻后达到一致性要求。
这两个名词,典型的新瓶旧酒,其实在SOA
年代就已经普遍存在了。
我是倾向于BASE
的,但不可否认有些系统确实是要求强一致的ACID
。但不管各种方式,都需要对系统资源进行编排。这样,无论是选择TCC、还是消息事务、再或者一些中间件,都会有章可循。
资源编排
上面也说了,实现分布式事务的方式也就大体上面5种。这个对于有编程基础的人,都没什么难度。分布式事务难点不在编码上,而在于资源协调上。
比如,你能否说清楚参与分布式事务的每个要素,到底是怎么运转的。
做好分布式事务,就要有全局意识,也要有资料可循。否则,只靠记忆和在大脑里演练,有了问题几乎难以排查。下面,介绍一种对资源的编排方式。
首先,我们来看一下,一个操作的一些属性。
-
层级
对资源抽象的树形关系 -
优先级
如果执行失败,对系统的影响 -
同步/异步
对执行结果的实效性要求 -
分类
对资源的抽象分类。一类资源应该在一棵树中。
最终,我们把数据抽象成一个森林。
从无序到有序的这个过程,有四个步骤可以去做。
-
第一招,打标签 -
第二招,做旋转 -
第三招,分主次 -
第四招,套框架
这一套玩下来,思路清晰,资源划分规整,井然有序。等发生问题了,根据问题反馈,可以很容易的找到问题发生点。
**第一招,打标签。**就是赋予每个资源不一样的意义,比如是同步还是异步、是部门资源还是协作资源、是高并发还是高耗时...主要是对我们的单一服务进行一下画像。
就像上图,一个非常大的分布式事务,涉及到三个比较大的资源。我们初步调查发现,操作A和操作C是同步的,而操作B是异步的。
第二招,做旋转。旋转,是为了处理服务之间不同的关系。
两个服务,一左一右,是顺序关系或者并列关系;一上一下,是层级关系或者顺序关系。我们可以依靠旋转,把资源进行关系抽象。
将平行关系抽象成层级关系,可在局部上绕开分布式事务。
比如,操作A,B,C看起来都是平行的,平起平坐没有什么优先级。这样在处理资源的时候,就陷入了顾此失彼的境地。我们需要给这些资源,强行抽象一个层级关系。
第三招,分主次。是给资源定下一个父子关系,然后剔除掉影响不大的资源,将问题进一步简化。
也就是,在一个分布式事务的资源中,选择一个,作为最主要的事务发生地。
比如,我们发现,使用操作B作为统一的异步分发,就可以完成操作A和操作C的非实时性最终一致性,则将A、C分别旋转,作为操作B的子集。操作A中的A4,可以作为A2、A5的前置事务,也可以将它们旋转。最终形态如下。
第四招,套框架。根据我们的资源分布,选用合适的工具进行设计。
小例子
比如,一个经典的下单流程,用户付款下单后。需要做三步操作:
-
写订单 -
扣积分 -
减库存
我们以写订单为主要逻辑,其他作为辅助逻辑,可以做以下的方案。
下单以后,事务的成功与否,以订单的写入为准。库存扣减和积分扣减,异步执行。
为了保证这三个操作达到最终一致性,需要在订单中加入连个状态。因为订单和状态表在同一个数据库中,所以能够保证它们的事务性质。
库存和积分扣减之后,会更新这个状态。如果更新不成功也没关系,我们有额外的定时,来扫描这些超时的状态,重新发送MQ消息进行修复。
可以看到,这种方式和本地消息表非常类似。如果你的服务分布式事务的场景不多,那么使用这种方式是最简单的。
那有没有可能不用定时呢?也是可以的,走近这一步,就已经向事务消息靠近了。比如,RocketMQ可以通过实现TransactionListener
,来实现事务监听。
如果你的项目分布式事务特别多,如果再使用状态表这种模式的话,编码复杂度会急剧上升,是时候引入一些框架了。
但这种模式也要写很多代码,就拿TCC来说,对每一个资源的操作,就都需要实现:try、confirm、cancel三套逻辑,开发成本还是比较高的。
End
本篇文章主要探讨了分布式事务的一些特点,并简要介绍了对服务资源的一些编排方式。分布式事务的一些框架,还是比较难以理解的。
更详细内容,我们后面见。
近期热门文章
《》
魔幻现实主义,关爱神经衰弱
《》
不要被标题给骗了,画面感十足的消遣文章
后端技术索引,中肯火爆。全网转载上百次。
精准点评100多框架,帮你选型