极客时间已完结课程限时免费阅读

加餐 | TCC如何实现指令执行的原子性?

加餐 | TCC如何实现指令执行的原子性?-极客时间

加餐 | TCC如何实现指令执行的原子性?

讲述:于航

时长09:42大小8.88M

你好,我是韩健。
在上一讲我提到,虽然 MySQL XA 能实现数据层的分布式事务,解决多个 MySQL 操作的事务问题,但我现在负责的这套业务系统还面临别的问题:在接收到外部的指令后,我需要访问多个内部系统,执行指令约定的操作,而且,还必须保证指令执行的原子性(也就是事务要么全部成功,要么全部失败)。
那么我是如何实现指令执行的原子性呢?答案是 TCC。
在我看来,上一讲中,基于二阶段提交协议的 XA 规范,实现的是数据层面操作的事务,而 TCC 能实现业务层面操作的事务。
对你来说,理解了二阶段提交协议和 TCC 后,你可以从数据层面到业务层面,更加全面理解如何实现分布式事务了,这样一来,当你在日常工作中,需要实现操作的原子性或者系统状态的一致性时,就知道该如何处理了。
那么为了帮助你更好地理解 TCC,咱们还是先来看一道思考题。
我以如何实现订票系统为例,假设现在要实现一个企鹅订票系统,给内部员工提供机票订购服务,但在实现订票系统时,我们需要考虑这样的情况:
我想从深圳飞北京,但这时没有直达的机票,要先定深圳航空的航班,从深圳去上海,然后再定上海航空的航班,从上海去北京。
因为我的目的地是北京,所以如果只有一张机票订购成功,肯定是不行的,这个系统必须保障 2 个订票操作的事务要么全部成功,要么全部不成功。那么该如何实现 2 个订票操作的事务呢?
带着这个问题,我们进入今天的学习,先来了解一下什么是 TCC。

什么是 TCC?

04 讲,我们介绍了 TCC,你如果对 TCC 不熟悉,或者忘记了,那么可以回过头复习一下。在这里,我只想补充一点,那就是:你可以对比二阶段提交协议来理解,TCC 包含的预留、确认或撤销这 2 个阶段,比如:
Try 是指预留,它和二阶段提交协议中,提交请求阶段的操作类似,具体来说就是,系统会将需要确认的资源预留、锁定,确保确认操作一定能执行成功。
Confirm 是指确认,它呢和二阶段提交协议中,提交执行阶段的操作类似,具体是指,系统将最终执行的操作。
Cancel 是指撤销,比较像二阶段提交协议中的回滚操作,具体指系统将撤销之前预留的资源,也就是撤销已执行的预留操作对系统产生的影响。
在我看来,二阶段提交协议和 TCC 的目标,都是为了实现分布式事务,这也就决定了它们“英雄所见略同”,在思想上是类似的,但我再次强调一下,这两个算法解决的问题场景是不同的,一个是数据层面,一个是业务层面,这就决定了它们在细节实现是不同的。所以接下来,我们就一起看看 TCC 的细节。
为了更好地演示 TCC 的原理,我们假设深圳航空、上海航空分别为订票系统提供了以下 3 个接口:机票预留接口、确认接口和撤销接口。
那么这时,订票系统可以这样来实现操作的事务:
首先,订票系统调用 2 个航空公司的机票预留接口,向 2 个航空公司申请机票预留。
如果两个机票都预留成功,那么订票系统将执行确认操作,也就是订购机票。
但如果此时有机票没有预留成功(比如深圳航空的从深圳到上海的机票),那这时该怎么办呢?这时订票系统就需要通过撤销接口来撤销订票请求。
你看这样,我们就实现了订票操作的事务了。TCC 是不是也很容易理解呢?答案是肯定的,那它难在哪儿呢?
在我看来,TCC 的难点不在于理解 TCC 的原理,而在于如何根据实际场景特点来实现预留、确认、撤销三个操作。所以,为帮助你更深刻的理解 TCC 三操作的实现要点,我将以一个实际项目具体说一说。

如何通过 TCC 指令执行的原子性?

我在一开始提到,当我接收到外部指令时,需要实现操作 1、2、3,其中任何一个操作失败,我都需要暂停指令执行,将系统恢复到操作未执行状态,然后再重试。
其中,操作 1、2、3 的含义具体如下。
操作 1:生成指定 URL 页面对应的图片,并持久化存储。
操作 2:调用内部系统 1 的接口,禁用指定域名的访问权限。
操作 3:通过 MySQL XA 更新多个数据库的数据记录。
那么我是如何使用 TCC 来解决这个问题的呢?答案是我在实现每个操作时,都会分别实现相应的预留、确认、撤销三操作。
首先,因为操作 1 是生成指定 URL 页面对应的图片,我是这么实现 TCC 三操作的。
预留操作:生成指定页面的图片,并存储到本地。
确认操作:更新操作 1 状态为完成。
撤销操作:删除本地存储的图片。
其次,因为操作 2 是调用内部系统 1 的接口,禁用该域名的访问权限,那么,我是这么实现 TCC 三操作的。
预留操作:调用的内部系统 1 的禁用指定域名的预留接口。这时我们先通知系统 1 预留相关的资源。
确认操作:调用的内部系统 1 的禁用指定域名的确认接口。我们执行禁用域名的操作,这时,禁用域名的操作的生效了。
撤销操作:调用的内部系统 1 的禁用指定域名的撤销接口。我们撤销对该域名的禁用,并通知内部系统 1 释放相关的预留资源。
最后,操作 3 是通过 MySQL XA 更改多个 MySQL 数据库中的数据记录,并实现数据更新的事务。我是这么实现 TCC 三操作的:
预留操作:执行 XA START 和 XA END 准备好事务分支操作,并调用 XA PREPARE,执行二阶段提交协议的提交请求阶段,预留相关资源。
确认操作:调用 XA COMMIT 执行确认操作。
撤销操作:调用 XA ROLLBACK 执行回滚操作,释放在 Try 阶段预留的资源。
在这里,你可以看到,确认操作是预留操作的下一个操作,而撤销操作则是用来撤销已执行的预留操作对系统产生的影响,类似在复制粘贴时,我们通过“Ctrl Z”撤销“Ctrl V”操作的执行,就像下图的样子。而这是理解 TCC 的关键,我希望你能注意到。
这样一来,在操作 1、2、3 的预留操作执行结束,如果预留操作都执行成功了,那么我将执行确认操作,继续向下执行。但如果预留操作只是部分执行成功,那么我将执行撤销操作,取消预留操作执行对系统产生的影响。通过这种方式(指令对应的操作要么全部执行,要么全部不执行),我就能实现指令执行的原子性了。
另外,在实现确认、撤销操作时,有一点需要我们尤为注意,因为这两个操作在执行时可能会重试,所以,它们需要支持幂等性。

内容小结

本节课我主要带你了解了 TCC,以及如何使用 TCC 实现分布式事务。我希望你明确这样几个重点。
1.TCC 是个业务层面的分布式事务协议,而 XA 规范是数据层面的分布式事务协议,这也是 TCC 和 XA 规范最大的区别。
2.TCC 与业务耦合紧密,在实际场景使用时,需要我们根据场景特点和业务逻辑来设计相应的预留、确认、撤销操作,相比 MySQL XA,有一定的编程开发工作量。
3. 本质上而言,TCC 是一种设计模式,也就是一种理念,它没有与任何技术(或实现)耦合,也不受限于任何技术,对所有的技术方案都是适用的。
最后,我想补充的是,因为 TCC 是在业务代码中编码实现的,所以,TCC 可以跨数据库、跨业务系统实现资源管理,满足复杂业务场景下的事务需求,比如,TCC 可以将对不同的数据库、不同业务系统的多个操作通过编码方式,转换为一个原子操作,实现事务。
另外,因为 TCC 的每一个操作对于数据库来讲,都是一个本地数据库事务,那么当操作结束时,本地数据库事务的执行也就完成了,所以相关的数据库资源也就被释放了,这就能避免数据库层面的二阶段提交协议长时间锁定资源,导致系统性能低下的问题。

课堂思考

我提到了自己通过 TCC 解决了指令执行的原子性问题。那么你不妨想想,为什么 TCC 能解决指令执行的原子性问题呢?欢迎在留言区分享你的看法,与我一同讨论。
最后,感谢你的阅读,如果这节课让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 8

提建议

上一篇
加餐 | MySQL XA是如何实现分布式事务的?
下一篇
16 | InfluxDB企业版一致性实现剖析:他山之石,可以攻玉
 写留言

精选留言(17)

  • Dovelol
    2020-06-29
    老师好,想问下,如果有3个服务调用,都会涉及到update数据库等操作,那预留资源具体是怎么预留的呢?开启事务,修改,不commit?然后等着commit请求过来后在commit数据库事务吗?

    作者回复: 加一颗星:),可以引入一个额外字段,在预留阶段,更新额外字段的值,在确认阶段,将额外字段的值更新到原计划更新的那个字段。

    共 5 条评论
    15
  • hello
    2020-06-29
    老师,再请教您一个问题,TCC有什么好的开源实现可供借鉴没?

    作者回复: 加一颗星:),tcc-transaction、seata等,可以了解下。

    共 4 条评论
    12
  • dra
    2020-06-28
    良心课程,老师用心了。专栏虽完结,还在不停加餐,为学员提供知识服务
    共 1 条评论
    9
  • Corner
    2020-06-30
    首先感谢老师长久以来坚持持续加餐和优化。然后有一个问题需要请教一下,在预留阶段有哪些常用的数据处理手段呢?是否数据上都要为预留操作增加相应的字段标记。

    作者回复: 加一颗星:),比如,可以通过状态位、标志位来标记。

    7
  • hello
    2020-06-29
    老师,请教您一个问题,您文中举例操作1,操作2,操作3这三个操作都各自实现TCC(预留操作、确认操作、撤销操作),那TCC都是针对单个操作(如针对操作1、操作2、操作3)实现,那要实现操作1+操作2+操作3这三个操作要么全成功,要么全失败如如何达到的?

    作者回复: 加一颗星:),如果三个操作的预留操作都成功了,就执行对应的确认操作,如果有任何一个不成功,就执行“已执行成功的预留操作”对应的撤销操作,这样,都回到了最初的状态。

    共 3 条评论
    6
  • Geek_7a6fde
    2021-10-07
    原文问题:我提到了自己通过 TCC 解决了指令执行的原子性问题。那么你不妨想想,为什么 TCC 能解决指令执行的原子性问题呢? 个人理解回答:TCC的try阶段可以预留或者锁定资源,就当于对该资源加锁,加锁了肯定就可以保证原子性了,confirm或cancel就是相当于释放锁
    1
  • Heaven
    2020-08-21
    在执行的过程中,TCC利用了二阶段提交的思想,既能够锁定成功,就相当于收到了锁定的确认操作,只有在所有的锁定确认收到了,才可以继续执行Confrim操作,在执行Confrim操作的时候,就不允许失败的存在,即使挂了,也会有后来人再上线的时候重新顶替上去
    1
  • Psyduck
    2020-07-01
    非常感谢老师,之前在看 TCC 时还是很模糊的感觉,通过这篇文章的讲解感觉清晰多了。
    1
  • i_chase
    2022-12-18 来自广东
    也就是说,tcc做了如下假设:预留资源成功了,那么confirm/cancel就一定是成功的?
  • NieXY
    2022-10-07 来自浙江
    为什么说tcc是最终一致性?感觉它也是应用了二阶段提交的思想,应该是强一致性啊
  • once
    2022-09-13 来自安徽
    这篇说的很好, 让我了解了TCC的实现理论思想, 其实有点像库存中的 预占 后扣减or释放
  • 上帝打草稿
    2021-10-03
    如果操作2在确认操作时,突然宕机了怎么办
    共 1 条评论
  • 竹马彦四郎的好朋友影...
    2021-02-10
    再一次完结撒花~
  • kylexy_0817
    2020-10-18
    韩老师辛苦了。想问题下,在项目中使用TCC,是否需要额外的点存放着数据变更前的快照呢?因为好让在cancel的时候恢复?

    作者回复: 加一颗星:),可以的

  • Simple
    2020-08-25
    老师你好,对下面这句话没理解: 另外,因为 TCC 的每一个操作对于数据库来讲,都是一个本地数据库事务,那么当操作结束时,本地数据库事务的执行也就完成了,所以相关的数据库资源也就被释放了,这就能避免数据库层面的二阶段提交协议长时间锁定资源,导致系统性能低下的问题。 问题: tcc不也需要try吗?只有所有服务都try成功了,才能提交,在等待所有业务都try成功的过程中还是会锁定资源,感觉和二阶段一样存在资源锁定问题
    展开
    共 2 条评论
  • 林绍灏
    2020-07-08
    老师讲得真的很好,终于弄通了TCC
  • 欧阳
    2020-07-01
    受益匪浅!感谢老师!