当前位置: 首页 > 编程日记 > 正文

TCC分布式事务的实现原理

目录

一、写在前面

二、业务场景介绍

三、进一步思考

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

(1)TCC实现阶段一:Try

 (2)TCC实现阶段二:Confirm

(3)TCC实现阶段三:Cancel

五、总结与思考

一、写在前面

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

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

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

二、业务场景介绍

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

那对一个订单支付之后,我们需要做下面的步骤:

  • 更改订单的状态为“已支付”

  • 扣减商品库存

  • 给会员增加积分

  • 创建销售出库单通知仓库发货

这是一系列比较真实的步骤,无论大家有没有做过电商系统,应该都能理解。

三、进一步思考

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

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

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

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

结果呢?由于库存服务操作数据库异常,导致库存数量还是100。这不是在坑人么,当然不能允许这种情况发生了!

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

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

所以说,我们有必要使用TCC分布式事务机制来保证各个服务形成一个整体性的事务。

上面那几个步骤,要么全部成功,如果任何一个服务的操作失败了,就全部一起回滚,撤销已经完成的操作。

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

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

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

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

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

1、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的操作。

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

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

2、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逻辑。然后,正式完成各个服务的所有业务逻辑的执行。

同样,给大家来一张图,顺着图一起来看看整个过程。

3、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逻辑。

大家看看下面的图,直观的感受一下。

五、总结与思考

好了,兄弟们,聊到这儿,基本上大家应该都知道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分布式事务的效果了。

这种方案的应用场景:

这种方案说实话几乎很少用人使用,我们用的也比较少,但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。

比如说我们,一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用TCC,严格严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,在资金上出现问题

比较适合的场景:这个就是除非你是真的一致性要求太高,是你系统中核心之核心的场景,比如常见的就是资金类的场景,那你可以用TCC方案了,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否ok,不ok就执行补偿/回滚代码。

而且最好是你的各个业务执行的时间都比较短。

但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。

转载于:https://www.cnblogs.com/Joy-Hu/p/10931569.html

相关文章:

Brute Force算法介绍及C++实现

字符串的模式匹配操作可以通过Brute Force算法来实现。字符串匹配操作即是查看S串(目标串或主串)中是否含有T串(模式串或子串),如果在主串中查找到了子串,则模式匹配成功,返回模式串中的第一个字符在主串中的位置;如果未找到&…

使用Go内置库实现简易httpbin功能

简介 通过学习「Go语言圣经」的入门部分,了解到 go 内置库里提供了一个简单的 http 功能。于是想模拟下httpbin[1]的 get 方法显示 header 头信息的功能来练手。 本人 Go 初学小白,为了练习只是简单的实现了请求 header 的 JSON 格式展示,跟 …

Google图嵌入工业界最新大招,高效解决训练大规模深度图卷积神经网络问题

导读:本文主要介绍Google发表在KDD 2019的图嵌入工业界最新论文,提出Cluster-GCN,高效解决工业界训练大规模深度图卷积神经网络问题,性能大幅提升基础上依靠可训练更深层网络达到SOTA效果,并开源了源代码。作者 | yyl4…

经典树型表结构之SORT_NO

为什么80%的码农都做不了架构师?>>> 在以下情况需要对经典树型表的sort_no进行重排序:1、插入节点(插入子树),需调整节点后所有sort_no;2、删除节点(删除子树),需调整节…

Ubuntu14.04上安装TensorRT 2.1操作步骤

在Ubuntu14.04 上安装TensorRT2.1有两种方法:(1)、通过.deb直接安装;(2)、通过Tar文件安装。这里通过Tar文件安装。安装步骤:1. 安装CUDA 8.0,可参考: http://blog.csdn.net/fengbingchun/article/details/53840684 ;…

学会这门编程知识,可能决定你能进什么样的企业

对于程序员来讲,很多技术真正掌握之后,都能影响甚至说改变一个人的命运,比如:python、AI、DL、算法等等,但是如果只让你选择其中的一项基础知识,你会选择哪个呢?如果是我, 我会选——…

雷林鹏分享:MySQL 及 SQL 注入

MySQL 及 SQL 注入 如果您通过网页获取用户输入的数据并将其插入一个MySQL数据库,那么就有可能发生SQL注入安全的问题。 本章节将为大家介绍如何防止SQL注入,并通过脚本来过滤SQL中注入的字符。 所谓SQL注入,就是通过把SQL命令插入到Web表单递…

dedecms网站文章内容按自定义排序的方法

标签dede:arclist的排序是通过orderby来指定的,如下: {dede:arclist orderby’排序字段’ } {/dede:arclist} orderby’sortrank’ 文档排序方式 orderby’hot’ 或 orderby’click’ 表示按点击数排列 orderby’sortrank’ 或 orderby’pubdate’ 按…

有了这套模板,再不担心刷不动LeetCode了

(图片下载自视觉中国)作者 | 李威来源 | https://www.liwei.party/整理 | 五分钟学算法(ID: CXYxiaowu)正文下面的动画以 「力扣」第 704 题:二分查找 为例,展示了使用这个模板编写二分查找法的一般流程。b…

一线互联网技术:Java工程师架构知识系统化汇总,面完45K!

根据高端招聘平台100 offer发布的Java人才盘点报告,在过去的2018年,Java仍然是最流行、招聘供需量最大的技术语言。在此基础上,互联网行业针对 Java 开发的招聘需求,也是近年技术类岗位供需量最大,且变化最稳定的。企业…

C++中局部类的使用

类可以定义在某个函数的内部,我们称这样的类为局部类(local class)。局部类定义的类型只在定义它的作用域内可见。和嵌套类不同,局部类的成员受到严格控制。局部类的所有成员(包括函数在内)都必须完整定义在类的内部。因此,局部类的作用与嵌套…

按键驱动的恩恩怨怨之概述

转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/23946487 研究按键驱动已经有几天了,尽管是0基础的驱动,可是当中包括的知识确实不少。接下来的几篇文章我会分别从浅入深的分析按键驱动。希望能对大家有所帮助。因为屌…

C++中嵌套类的使用

一个类可以定义在另一个类的内部,前者称为嵌套类(nested class)或嵌套类型(nested type)。嵌套类常用于定义作为实现部分的类。嵌套类可用于隐藏实现细节。嵌套类是一个独立的类,与外层类基本没什么关系。特别是,外层类的对象和嵌套类的对象是…

挑战弱监督学习的三大热门问题 AutoWSL2019挑战赛正式开赛

AutoWSL2019作为11月17-19日亚洲机器学习大会(ACML)主会议竞赛单元之一,由第四范式、ChaLearn、RIKEN和微软联合举办,其中竞赛分享和颁奖将与大会WSL-Workshop共同举办。据悉,AutoWSL是继AutoCV、AutoCV2、AutoNLP、Au…

数据连接池的工作机制是什么?

以典型的数据库连接池为例: 首先普通的数据库访问是这样的:程序和数据库建立连接,发送数据操作的指令,完成后断开连接。等下一次请求的时候重复这个过程,即每个请求都需要和数据库建立连接和断开连接,这样当…

apkplug插件托管服务简化与简介-05

2019独角兽企业重金招聘Python工程师标准>>> 本文基于TuoClondService1.1.0讲解 apkplug插件托管服务是提供给开发者一个远程发布插件的管理平台,但v1.0.0版本接口调用有些复杂我们在v1.1.0版本中着重对其进行了简化 与封装,使开发者能更简…

SpringBoot-JPA入门

SpringBoot-JPA入门 JPA就是Spring集成了hibernate感觉。 注解,方法仓库(顾名思义的方法,封装好了,还有自定义的方法)。 案例: spring:datasource:url: jdbc:mysql://localhost:3306/springboot?useUnicodetrue&c…

PCA、LDA、MDS、LLE、TSNE等降维算法的Python实现

整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】网上关于各种降维算法的资料参差不齐,但大部分不提供源代码。近日,有人在 GitHub 上整理了一些经典降维算法的 Demo(Python)集合,同时给出了参考资料的链接。PCA…

C++11中enum class的使用

枚举类型(enumeration)使我们可以将一组整型常量组织在一起。和类一样,每个枚举类型定义了一种新的类型。枚举属于字面值常量类型。 C包含两种枚举:限定作用域的和不限定作用域的。这里主要介绍限定作用域的。不限定作用域的使用可以参考: ht…

Windows下Mysql主从配置(Mysql5.5)

主数据库IP:192.168.3.169从数据库IP:192.168.3.34主数据库配置my.inin:在[mysqld]下添加配置数据:server-id1 #配一个唯一的ID编号,1至32。log-binmysql-bin #二进制文件存放路径#设置要进行或不要进行主从复制的数据库名,同…

K-最近邻法(KNN) C++实现

关于KNN的介绍可以参考: http://blog.csdn.net/fengbingchun/article/details/78464169 这里给出KNN的C实现,用于分类。训练数据和测试数据均来自MNIST,关于MNIST的介绍可以参考: http://blog.csdn.net/fengbingchun/article/deta…

AI大佬“互怼”:Bengio和Gary Marcus隔空对谈深度学习发展现状

编译 | AI科技大本营编辑部出品 | AI科技大本营(ID:rgznai100)去年以来,由于纽约大学教授 Gary Marcus 对深度学习批评,导致他在社交媒体上与许多知名的 AI 研究人员如 Facebook 首席 AI 科学家 Yann LeCun 进行了一场论战。不止 …

Centos7多内核情况下修改默认启动内核方法

1.1 进入grub.cfg配置文件存放目录/boot/grub2/并备份grub.cfg配置文件 [rootlinux-node1 ~]# cd /boot/grub2/ [rootlinux-node1 grub2]# cp -p grub.cfg grub.cfg.bak [rootlinux-node1 grub2]# ls -ld grub.cfg* -rw-r--r--. 1 root root 5162 Aug 11 2018 grub.cfg -rw-r…

TensorRT Samples: MNIST

关于TensorRT的介绍可以参考: http://blog.csdn.net/fengbingchun/article/details/78469551以下是参考TensorRT 2.1.2中的sampleMNIST.cpp文件改写的实现对手写数字0-9识别的测试代码,各个文件内容如下:common.hpp:#ifndef FBC_TENSORRT_TE…

网红“AI大佬”被爆论文剽窃,Jeff Dean都看不下去了

作者 | 夕颜、Just出品 | AI科技大本营(ID:rgznai100)【导读】近日,推特上一篇揭露 YouTube 网红老师 Siraj Raval 新发表论文涉抄袭其他学者的帖子引起了讨论。揭露者是曼彻斯特大学计算机科学系研究员 Andrew M. Webb,他在 Twit…

数位dp(求1-n中数字1出现的个数)

题意:求1-n的n个数字中1出现的个数。 解法:数位dp,dp[pre][now][equa] 记录着第pre位为now,equa表示前边是否有降数字(即后边可不能够任意取,true为没降,true为已降);常规的记忆化搜…

TensorRT Samples: MNIST API

关于TensorRT的介绍可以参考: http://blog.csdn.net/fengbingchun/article/details/78469551 以下是参考TensorRT 2.1.2中的sampleMNISTAPI.cpp文件改写的实现对手写数字0-9识别的测试代码,各个文件内容如下:common.hpp:#ifndef FBC_TENSORR…

免费学习AI公开课:打卡、冲击排行榜,还有福利领取

CSDN 技术公开课 Plus--AI公开课再度升级内容全新策划:贴近开发者,更多样、更落地形式多样升级:线上线下、打卡学习,资料福利,共同交流成长,扫描下方小助手二维码,回复:公开课&#…

Gamma阶段第一次scrum meeting

每日任务内容 队员昨日完成任务明日要完成的任务张圆宁#91 用户体验与优化:发现用户体验细节问题https://github.com/rRetr0Git/rateMyCourse/issues/91#91 用户体验与优化:发现并优化用户体验,修复问题https://github.com/rRetr0Git/rateMyC…

windows 切换 默认 jdk 版本

set JAVA_HOMEC:\jdk1.6.0u24 set PATH%JAVA_HOME%\bin;%PATH%转载于:https://www.cnblogs.com/dmdj/p/3756887.html