拜托,面试请不要再问我TCC分布式事务的实现原理!

欢迎关注微信公众号:石杉的架构笔记(id:shishan100)git

个人新课**《C2C 电商系统微服务架构120天实战训练营》在公众号儒猿技术窝**上线了,感兴趣的同窗,能够点击下方连接了解详情:github

《C2C 电商系统微服务架构120天实战训练营》面试

目录

1、写在前面redis

2、业务场景介绍数据库

3、进一步思考微信

4、落地实现TCC分布式事务markdown

(1)TCC实现阶段一:Try架构

(2)TCC实现阶段二:Confirm并发

(3)TCC实现阶段三:Cancelapp

5、总结与思考

1、写在前面

以前网上看到不少写分布式事务的文章,不过大多都是将分布式事务各类技术方案简单介绍一下。不少朋友看了很多文章,仍是不知道分布式事务到底怎么回事,在项目里到底如何使用。

因此我们这篇文章,就用大白话+手工绘图,并结合一个电商系统的案例实践,来给你们讲清楚到底什么是TCC分布式事务。

首先说一下,这里可能会牵扯到一些Spring Cloud的原理,若是有不太清楚的同窗,能够参考以前的文章:《拜托,面试请不要再问我Spring Cloud底层原理!》。

2、业务场景介绍

我们先来看看业务场景,假设你如今有一个电商系统,里面有一个支付订单的场景。

那对一个订单支付以后,咱们须要作下面的步骤:

  • 更改订单的状态为“已支付”
  • 扣减商品库存
  • 给会员增长积分
  • 建立销售出库单通知仓库发货

这是一系列比较真实的步骤,不管你们有没有作过电商系统,应该都能理解。

3、进一步思考

好,业务场景有了,如今咱们要更进一步,实现一个TCC分布式事务的效果。

什么意思呢?也就是说,订单服务-修改订单状态,库存服务-扣减库存,积分服务-增长积分,仓储服务-建立销售出库单。

上述这几个步骤,要么一块儿成功,要么一块儿失败,必须是一个总体性的事务

举个例子,如今订单的状态都修改成“已支付”了,结果库存服务扣减库存失败。那个商品的库存原来是100件,如今卖掉了2件,原本应该是98件了。

结果呢?因为库存服务操做数据库异常,致使库存数量仍是100。这不是在坑人么,固然不能容许这种状况发生了!

可是若是你不用TCC分布式事务方案的话,就用个Spring Cloud开发这么一个微服务系统,颇有可能会干出这种事儿来。

咱们来看看下面的这个图,直观的表达了上述的过程。

因此说,咱们有必要使用TCC分布式事务机制来保证各个服务造成一个总体性的事务。

上面那几个步骤,要么所有成功,若是任何一个服务的操做失败了,就所有一块儿回滚,撤销已经完成的操做。

好比说库存服务要是扣减库存失败了,那么订单服务就得撤销那个修改订单状态的操做,而后得中止执行增长积分和通知出库两个操做。

说了那么多,老规矩,给你们上一张图,大伙儿顺着图来直观的感觉一下。

4、落地实现TCC分布式事务

那么如今到底要如何来实现一个TCC分布式事务,使得各个服务,要么一块儿成功?要么一块儿失败呢?

你们稍安勿躁,咱们这就来一步一步的分析一下。我们就以一个Spring Cloud开发系统做为背景来解释。

一、TCC实现阶段一:Try

首先,订单服务那儿,他的代码大体来讲应该是这样子的:

若是你以前看过Spring Cloud架构原理那篇文章,同时对Spring Cloud有必定的了解的话,应该是能够理解上面那段代码的。

其实就是订单服务完成本地数据库操做以后,经过Spring Cloud的Feign来调用其余的各个服务罢了。

可是光是凭借这段代码,是不足以实现TCC分布式事务的啊?!兄弟们,别着急,咱们对这个订单服务修改点儿代码好很差。

首先,上面那个订单服务先把本身的状态修改成:OrderStatus.UPDATING

这是啥意思呢?也就是说,在pay()那个方法里,你别直接把订单状态修改成已支付啊!你先把订单状态修改成UPDATING,也就是修改中的意思。

这个状态是个没有任何含义的这么一个状态,表明有人正在修改这个状态罢了。

而后呢,库存服务直接提供的那个reduceStock()接口里,也别直接扣减库存啊,你能够是冻结掉库存

举个例子,原本你的库存数量是100,你别直接100 - 2 = 98,扣减这个库存!

你能够把可销售的库存:100 - 2 = 98,设置为98没问题,而后在一个单独的冻结库存的字段里,设置一个2。也就是说,有2个库存是给冻结了。

积分服务的addCredit()接口也是同理,别直接给用户增长会员积分。你能够先在积分表里的一个预增长积分字段加入积分。

好比:用户积分本来是1190,如今要增长10个积分,别直接1190 + 10 = 1200个积分啊!

你能够保持积分为1190不变,在一个预增长字段里,好比说prepare_add_credit字段,设置一个10,表示有10个积分准备增长。

仓储服务的saleDelivery()接口也是同理啊,你能够先建立一个销售出库单,可是这个销售出库单的状态是“UNKNOWN”。

也就是说,刚刚建立这个销售出库单,此时还不肯定他的状态是什么呢!

上面这套改造接口的过程,其实就是所谓的TCC分布式事务中的第一个T字母表明的阶段,也就是Try阶段

总结上述过程,若是你要实现一个TCC分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操做,而是完成一个Try的操做。

这个操做,通常都是锁定某个资源,设置一个预备类的状态,冻结部分数据,等等,大概都是这类操做。

我们来一块儿看看下面这张图,结合上面的文字,再来捋一捋这整个过程。

二、TCC实现阶段二:Confirm

而后就分红两种状况了,第一种状况是比较理想的,那就是各个服务执行本身的那个Try操做,都执行成功了,bingo!

这个时候,就须要依靠TCC分布式事务框架来推进后续的执行了。

这里简单提一句,若是你要玩儿TCC分布式事务,必须引入一款TCC分布式事务框架,好比国内开源的ByteTCC、himly、tcc-transaction。

不然的话,感知各个阶段的执行状况以及推动执行下一个阶段的这些事情,不太可能本身手写实现,太复杂了。

若是你在各个服务里引入了一个TCC分布式事务的框架,订单服务里内嵌的那个TCC分布式事务框架能够感知到,各个服务的Try操做都成功了。

此时,TCC分布式事务框架会控制进入TCC下一个阶段,第一个C阶段,也就是Confirm阶段

为了实现这个阶段,你须要在各个服务里再加入一些代码。

好比说,订单服务里,你能够加入一个Confirm的逻辑,就是正式把订单的状态设置为“已支付”了,大概是相似下面这样子:

库存服务也是相似的,你能够有一个InventoryServiceConfirm类,里面提供一个reduceStock()接口的Confirm逻辑,这里就是将以前冻结库存字段的2个库存扣掉变为0。

这样的话,可销售库存以前就已经变为98了,如今冻结的2个库存也没了,那就正式完成了库存的扣减。

积分服务也是相似的,能够在积分服务里提供一个CreditServiceConfirm类,里面有一个addCredit()接口的Confirm逻辑,就是将预增长字段的10个积分扣掉,而后加入实际的会员积分字段中,从1190变为1120。

仓储服务也是相似,能够在仓储服务中提供一个WmsServiceConfirm类,提供一个saleDelivery()接口的Confirm逻辑,将销售出库单的状态正式修改成“已建立”,能够供仓储管理人员查看和使用,而不是停留在以前的中间状态“UNKNOWN”了。

好了,上面各类服务的Confirm的逻辑都实现好了,一旦订单服务里面的TCC分布式事务框架感知到各个服务的Try阶段都成功了之后,就会执行各个服务的Confirm逻辑。

订单服务内的TCC事务框架会负责跟其余各个服务内的TCC事务框架进行通讯,依次调用各个服务的Confirm逻辑。而后,正式完成各个服务的全部业务逻辑的执行。

一样,给你们来一张图,顺着图一块儿来看看整个过程。

三、TCC实现阶段三:Cancel

好,这是比较正常的一种状况,那若是是异常的一种状况呢?

举个例子:在Try阶段,好比积分服务吧,他执行出错了,此时会怎么样?

那订单服务内的TCC事务框架是能够感知到的,而后他会决定对整个TCC分布式事务进行回滚。

也就是说,会执行各个服务的第二个C阶段,Cancel阶段

一样,为了实现这个Cancel阶段,各个服务还得加一些代码。

首先订单服务,他得提供一个OrderServiceCancel的类,在里面有一个pay()接口的Cancel逻辑,就是能够将订单的状态设置为“CANCELED”,也就是这个订单的状态是已取消。

库存服务也是同理,能够提供reduceStock()的Cancel逻辑,就是将冻结库存扣减掉2,加回到可销售库存里去,98 + 2 = 100。

积分服务也须要提供addCredit()接口的Cancel逻辑,将预增长积分字段的10个积分扣减掉。

仓储服务也须要提供一个saleDelivery()接口的Cancel逻辑,将销售出库单的状态修改成“CANCELED”设置为已取消。

而后这个时候,订单服务的TCC分布式事务框架只要感知到了任何一个服务的Try逻辑失败了,就会跟各个服务内的TCC分布式事务框架进行通讯,而后调用各个服务的Cancel逻辑。

你们看看下面的图,直观的感觉一下。

5、总结与思考

好了,兄弟们,聊到这儿,基本上你们应该都知道TCC分布式事务具体是怎么回事了!

总结一下,你要玩儿TCC分布式事务的话:

首先须要选择某种TCC分布式事务框架,各个服务里就会有这个TCC分布式事务框架在运行。

而后你本来的一个接口,要改造为3个逻辑,Try-Confirm-Cancel

  • 先是服务调用链路依次执行Try逻辑
  • 若是都正常的话,TCC分布式事务框架推动执行Confirm逻辑,完成整个事务
  • 若是某个服务的Try逻辑有问题,TCC分布式事务框架感知到以后就会推动执行各个服务的Cancel逻辑,撤销以前执行的各类操做

这就是所谓的TCC分布式事务。

TCC分布式事务的核心思想,说白了,就是当遇到下面这些状况时,

  • 某个服务的数据库宕机了
  • 某个服务本身挂了
  • 那个服务的redis、elasticsearch、MQ等基础设施故障了
  • 某些资源不足了,好比说库存不够这些

先来Try一下,不要把业务逻辑完成,先试试看,看各个服务能不能基本正常运转,能不能先冻结我须要的资源。

若是Try都ok,也就是说,底层的数据库、redis、elasticsearch、MQ都是能够写入数据的,而且你保留好了须要使用的一些资源(好比冻结了一部分库存)。

接着,再执行各个服务的Confirm逻辑,基本上Confirm就能够很大几率保证一个分布式事务的完成了。

那若是Try阶段某个服务就失败了,好比说底层的数据库挂了,或者redis挂了,等等。

此时就自动执行各个服务的Cancel逻辑,把以前的Try逻辑都回滚,全部服务都不要执行任何设计的业务逻辑。保证你们要么一块儿成功,要么一块儿失败

写到这里,本文差很少该结束了。等一等,你有没有想到一个问题?

若是有一些意外的状况发生了,好比说订单服务忽然挂了,而后再次重启,TCC分布式事务框架是如何保证以前没执行完的分布式事务继续执行的呢?

因此,TCC事务框架都是要记录一些分布式事务的活动日志的,能够在磁盘上的日志文件里记录,也能够在数据库里记录。保存下来分布式事务运行的各个阶段和状态。

问题还没完,万一某个服务的Cancel或者Confirm逻辑执行一直失败怎么办呢?

那也很简单,TCC事务框架会经过活动日志记录各个服务的状态。

举个例子,好比发现某个服务的Cancel或者Confirm一直没成功,会不停的重试调用他的Cancel或者Confirm逻辑,务必要他成功!

固然了,若是你的代码没有写什么bug,有充足的测试,并且Try阶段都基本尝试了一下,那么其实通常Confirm、Cancel都是能够成功的!

最后,再给你们来一张图,来看看给咱们的业务,加上分布式事务以后的整个执行流程:

很多大公司里,其实都是本身研发TCC分布式事务框架的,专门在公司内部使用,好比咱们就是这样。

不过若是本身公司没有研发TCC分布式事务框架的话,那通常就会选用开源的框架。

这里笔者给你们推荐几个比较不错的框架,都是我们国内本身开源出去的:ByteTCC,tcc-transaction,himly

你们有兴趣的能够去他们的github地址,学习一下如何使用,以及如何跟Spring Cloud、Dubbo等服务框架整合使用。

只要把那些框架整合到你的系统里,很容易就能够实现上面那种奇妙的TCC分布式事务的效果了。

下一篇文章,咱们来说讲可靠消息最终一致性方案实现的分布式事务,同时聊聊在实际生产中遇到的运用该方案的高可用保障架构。

具体参见:《最终一致性分布式事务的99.99%高可用保障生产实践》

若有收获,请帮忙转发,您的鼓励是做者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用的****原创系列

文章正在路上,欢迎扫描下方二维码,持续关注:

石杉的架构笔记(id:shishan100)

十余年BAT架构经验倾囊相授

相关文章
相关标签/搜索