vlambda博客
学习文章列表

她一说分布式事务,我就聊歪了

有一个女领导是非常幸福的,尤其是漂亮的女领导。那感觉,绝对是痛并快乐着。

领导今天把我叫到办公室,说做事情要有事务性思维,不要虎头蛇尾的。我心想,你说的是闭环思维吧。但我不敢明说,女人的脸,打不得。于是,我悉心请教了事务性思维的处事风格,领导用分布式事务的思想教育了我一番。

古语说:斩草要除根。比喻做事要除去祸根,以免后患。只斩了草:根会有寻仇上门的隐患;只除了根:草会暴躁的找上门来。在这里,我们就可以把草和根,看成一个整体的事务。

**你要只做了一部分,就会产生问题。就像行贿你忘了某些重要的人物,就会在时间窗口发生问题。**领导教育我说。

事务是个有趣的话题,指的是不同的资源,需要原子性的达到相同的状态。事务的属性是ACID,在英文里是的意思。

什么是分布式事务?

那什么是分布式事务?

分布式事务的对立方,肯定是分布式事务,也就是本地事务,我们平常经常碰到的那种,也是工作中经常遇到的。

事务多指多个操作的协作,太监那种一刀除根的操作并没有什么挑战。xjjdog也有两篇关于数据库事务的文章,虽然不是分布式问题,但也多次引起故障,可以看到事情的严重性。


那本地事务和分布式事务有什么区别?

所谓本地事务,就是所有的操作,都在一个资源中完成,比如都在同一个MySQL中完成。

她一说分布式事务,我就聊歪了

这种事务的操作,依靠数据库本身提供的能力,就可以漂亮的完成任务,并没有什么难度。在Spring中,一个Transaction注解就能完的转。

分布式事务就没那么简单,是指的不同类型的资源之间的协作问题。

她一说分布式事务,我就聊歪了

就像上图,四个操作分散在三个不同的资源中。要想达到一致性,需要三个不同的资源进行统一协调。它们底层的协议,以及实现方式,都是不一样的。那就不能来个一刀切,需要借助外部的组件来完成。

一般,要完成分布式事务,比较流行的有下面几种做法。

(1)两阶段提交方案。几乎所有的OLTP数据库,都支持类似XA的协议。但是两阶段提交方案开发复杂、锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。现在互联网PC机上,几乎没有用这个的,或多或少是它的变种。

(2)TCC。这种方式在电商、金融领域有较多的落地,它其实是两阶段提交的一种改进,将业务逻辑的分支显示的分成了TryConfirmCancel三个操作。也有一些框架ByteTCCTCC-transactionHimly等。

(3)本地消息表。使用异步来确保方案的最终一致性。这种方式比较繁琐,需要在数据库中加新表,或者新字段来实现分布式事务。如果项目不大,其实用的还是蛮多的。

(4)MQ事务消息。直接基于MQ来实现事务。但是很多MQ是不支持事务消息的(事务消息不等同于消息事务),比如 RabbitMQ 和 Kafka 都不支持。

(5)分布式事务中间件。比如Seata,以中间件服务的形式而存在。这种内部一般都很复杂,不过都是封装好的,直接调用就可以。

先不要对这几种方式和这么多的名词深入研究,这通常会让人头大。本文假定你对以上几种方式,都有初步的理解。

ACID和BASE

很多人都体验过,一些业务代码重构起来,非常的难受;或者,加入了一些保证一致性的代码,一压测,性能掉的惊掉下巴。要在业务中引入分布式事务,需要兼顾三个方面:改造成本性能时效

也就是,改起来顺手,跑起来走心,效果显著。

她一说分布式事务,我就聊歪了

但如果我的项目并没有那么多的分布式事务需求。为了它,去引进这么笨重的事务框架,实在是吃饱了撑的。

有没有办法,来绕过强分布式事务这个命题呢?这个在局部上,是可以的。

这个时候,我们需要提到BASE柔性事务最终一致性最终这个词,证明战线要拉的很长,也就是实效性可能会比较大。

BASEACID是相互对立的,也非常好记。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多框架,帮你选型