本着“理论先行,实践紧随”的理念,这里强烈建议大家先完成上期理论篇内容的学习,再跟随我的思路一起进入本期实战篇的学习.
在写这篇文章的过程中,我在另一边并行完成了一个开源项目的搭建. 这个项目是基于 golang 从零到一实现的 TCC 分布式事务框架,当前于 github 的开源地址为:https://github.com/xiaoxuxiansheng/gotcc
本期分享内容将会紧密围绕着这个开源项目展开. 受限于个人水平,在项目实现以及文章讲解中有着诸多不当之处,权当在此抛砖引玉,欢迎大家多多批评指正.
1 架构设计
1.1 整体架构
首先我们简单回顾一下有关于分布式事务以及 TCC 的概念.
所谓事务,对应的语义是“要么什么都不做,要么全都做到位”,需要针对多个执行动作,建立一套一气呵成、不可拆解的运行机制.
在事务中包括的一些执行动作,倘若涉及到跨数据库、跨组件、跨服务等分布式操作,那我们就称这样的事务是分布式事务.
分布式事务在实现上存在很多技术难点,是一个颇具挑战的有趣话题. 目前业界也形成了一套相对成熟且普遍认同的解决方案,就是——TCC:Try-Confirm/Cancel.
TCC 本质上是一种 2PC(two phase commitment protocal 两阶段提交)的实现:
• 把分布式事务中,负责维护状态数据变更的模块,封装成一个 TCC 组件
• 把数据的变更状态拆分为对应 Try 操作的【冻结】、对应 Confirm 操作的【成功】以及对应 Cancel 操作的【失败回滚】
• 抽出一个统筹全局的事务协调者角色 TXManager. 在执行分布式事务时,分为两个阶段:
• 阶段 I:先对所有组件执行 Try 操作
- • 阶段 II:根据上阶段 Try 操作的执行结果,决定本轮执行 Confirm 还是 Cancel 操作
图片
在我们实现 TCC 框架的实战环节中,首先需要明确的事情是:
• 哪部分内容在 TCC 架构中属于通用的流程,这部分内容可以抽取出来放在 sdk 中,以供后续复用
• 哪部分内容需要给使用方预留出足够的自由度,由使用方自行实现,然后和通用 sdk 进行接轨.
最终,这两部分内容明确如下:
• 在 TCC sdk 中实现的通用逻辑包含了和事务协调器 txManager 有关的核心流程
• 事务协调器 TXManager 开启事务以及 try-confirm/cancel 的 2PC 流程串联
• 事务协调器 TXManager 异步轮询任务,用于推进事务从中间态走向终态
• TCC 组件的注册流程
• 需要预定义事务日志存储模块 TXStore 的实现规范(声明 interface)
• 需要预定义 TCC 组件 TCCComponent 的实现规范(声明 interface)
• TCC 组件和 TXStore 两部分内容需要由使用方自行实现:
• 使用方自行实现 TCCComponent 类,包括其 Try、Confirm、Cancel 方法的执行逻辑
• 使用方自行实现具体的 TXStore 日志存储模块. 可以根据实际需要,选型合适的存储组件和存储方式
图片
1.2 TCC Component
下面是关于 TCC 组件的定位:
• 这部分内容需要由用户自行实现,并在 TXManager 启动时将其注册到注册中心 RegistryCenter 当中.
• 当使用方调用 TXManager 开启事务时,会通过 RegistryCenter 获取这些组件,并对其进行使用
• TCC 组件需要具备的能力包括如下几项:
图片
1.3 TX Manager
下面是关于事务协调器 TXManager 的定位.
- • TXManager 是整个 TCC 架构中最核心的角色
- • TXManager 作为 gotcc 的统一入口,供使用方执行启动事务和注册组件的操作
- • TXManager 作为中枢系统分别和 RegisterCenter、TXStore 交互
- • TXManager 需要串联起整个 Try-Confirm/Canel 的 2PC 调用流程
- • TXManager 需要运行异步轮询任务,推进未完成的事务走向终态
图片
1.4 TX Store
TXStore 是用于存储和管理事务日志明细记录的模块:
• 需要支持事务明细数据的 CRUD 能力
• 通常情况下,底层需要应用到实际的存储组件作为支持
• TXStore 在 gotcc 的 sdk 中体现为一个抽象的 interface. 需要由用户完成具体类的实现,并将其注入到 TXManager 当中.
图片
1.5 RegistryCenter
最后是 TCC 组件的注册管理中心 RegistryCenter,负责给 txManager 提供出注册和查询 TCC 组件的能力.
图片
2 TXManager 核心源码讲解
理完了基本的流程和概念,下面我们一起开启一线实战环节.
2.1 类图
首先捋一下,在 gotcc 核心 sdk 中,涉及到的几个核心类:
- • TXManager:事务协调器,class
- • TXStore:事务日志存储模块,interface
- • registryCenter:TCC 组件注册管理中心,class
- • TCCComponent:TCC 组件,interface
通过下面的 UML 类图,展示一下几个核心类之间的关联性:
图片
2.2 核心类定义
图片
2.2.1 TXManager
下面是关于事务协调器 TXManager 的几个核心字段:
• txStore:内置的事务日志存储模块,需要由使用方实现并完成注入
• registryCenter:TCC 组件的注册管理中心
• opts:内聚了一些 TXManager 的配置项,可以由使用方自定义,并通过 option 注入
• ctx:用于反映 TXManager 运行生命周期的的 context,当 ctx 终止时,异步轮询任务也会随之退出
• stop:用于停止 txManager 的控制器. 当 stop 被调用后,异步轮询任务会被终止
2.2.2 RegistryCenter
注册中心 registryCenter 中的内容很简单,通过 map 存储所有注册进来的 TCC 组件,要求各组件都有独立的组件 ID;通过一把读写锁 rwMutex 保护 map 的并发安全性
2.2.3 TXStore
下面 gotcc sdk 中,对事务日志存储模块 TXStore interface 的定义,这个点很重要,要求后续使用方在实现具体的 TXStore 模块时,需要实现这里所罗列出来的所有方法,并且要保证实现方法满足预期的功能:
• CreateTX:创建一条事务明细记录,会在入参中传入本事务涉及的 TCC 组件列表,同时需要在出参中返回全局唯一的事务 id
• TXUpdate:更新一条事务明细记录. 这里指的更新,针对于,事务中某个 TCC 组件 Try 响应状态的更新
• TXSubmit:提交一条事务的执行结果. 要么置为成功,要么置为失败
• GetHangingTXs:获取所有未完成的事务明细记录
• GetTX:根据事务 id,获取指定的一条事务明细记录
• Lock:锁住整个事务日志存储模块(要求为分布式锁)
• Unlock:解锁整个事务日志存储模块
2.3 注册组件
下面是注册 TCC 组件的处理流程:
首先,使用方通过 TXManager 对外暴露的公开方法 Register,开启注册流程,传入对应的 TCCComponent:
• TXManager 会调用注册中心 registeryCenter 的 register 方法,将对应 component 注入到 map 中. 这里有两个点值得一提:
• Register 方法可以并发使用,其内部会通过 rwMutex 维护 map 的并发安全性
• TCC 组件不能重复注册,即不能存在重复的 component id
上游 TXManager 可以通过 component id,进行 TCC 组件的查询. 倘若某个 component id 不存在,则会抛出错误:
2.4 事务主流程
下面进入最核心的部分,介绍一下整个分布式事务的运行流程.
2.4.1 主流程
用户可以通过 txManager.Transaction 方法,一键启动动一个分布式事务流程,其中包含的几个核心步骤展示如下图:
图片
txManager.Transaction 方法是用户启动分布式事务的入口,需要在入参中声明本次事务涉及到的组件以及需要在 Try 流程中传递给对应组件的请求参数:
txManager.Transaction 对应源码如下,核心步骤均给出了注释. 核心的 try-confirm/cancel 流程,会在后续的 txManager.twoPhaseCommit 方法中展开.
2.4.2 2PC 串联
此处涉及 try-confirm/cancel 流程的串联,可以说是整个 gotcc 框架的精髓所在,请大家细品斟酌.
对应流程图展示如下,方法源码中也给出了相对详细的注释:
图片
2.4.3 事务进度推进
当一笔事务在第一阶段中所有的 Try 请求都有了响应后,就需要根据第一阶段的结果,执行第二阶段的 Confirm 或者 Cancel 操作,并且将事务状态推进为成功或失败的终态:
- • 倘若所有组件的 Try 响应都是成功,则需要批量调用组件的 Confirm 接口,并在这之后将事务状态更新为成功
- • 倘若存在某个组件 Try 响应为失败,则需要批量调用组件的 Cancel 接口,并在这之后将事务状态更新为失败
- • 倘若当前事务已执行超时,同样需要批量调用组件的 Cancel 接口,并在这之后将事务状态更新为失败
图片
2.5 异步轮询流程
接下来聊聊 txManager 的异步轮询流程. 这个流程同样非常重要,是支撑 txManager 鲁棒性的重要机制.
倘若存在事务已经完成第一阶段 Try 操作的执行,但是第二阶段没执行成功,则需要由异步轮询流程进行兜底处理,为事务补齐第二阶段的操作,并将事务状态更新为终态
2.5.1 启动时机
异步轮询任务是在 txManager 的初始化流程中启动的,通过异步 goroutine 持久运行:
2.5.2 轮询流程
异步轮询任务运行时,基于 for 循环 + select 多路复用的方式,实现定时任务的执行.
轮询的时间间隔会根据一轮任务处理过程中是否出现错误,而进行动态调整. 这里调整规则指的是:当一次处理流程中发生了错误,就需要调大当前节点轮询的时间间隔,让其他节点的异步轮询任务得到更大的执行机会.
图片
有关于轮询时间间隔的退避谦让策略为:每次对时间间隔进行翻倍,封顶为初始时长的 8 倍:
2.5.3 批量推进事务进度
下面是异步轮询任务批量推进事务第二阶段执行的流程,核心是开启多个 goroutine 并发对多项事务进行处理:
3 GOTCC 使用案例讲解
从第 3 章开始,我们从实际应用 gotcc 框架的使用方视角出发,对所需要实现的模块进行定义,然后给出应用 gotcc 框架的代码示例.
3.1 TCC 组件实现
首先,我们对 TCC 组件的具体实现类进行定义:
3.1.1 类定义
定义一个 MockComponent 类,其中内置了 redis 客户端,用于完成一些状态数据的存取.
图片
3.1.2 Try 流程
下面实现一下 TCC 组件的 Try 方法,关键要点已于代码中通过注释的形式给出:
图片
3.1.3 Confirm 流程
下面实现一下 TCC 组件的 Confirm 方法,关键要点已于代码中通过注释的形式给出:
图片
3.1.4 Cancel 流程
下面实现一下 TCC 组件的 Cancel 方法,关键要点已于代码中通过注释的形式给出:
3.2 TX Store 实现
接下来是关于事务日志存储模块 TXStore 的具体实现:
3.2.1 类定义
声明了一个 MockTXStore 类,里面通过 mysql 存储事务日志明细数据,通过 redis 实现 TXStore 模块的分布式锁.
其中和事务日志明细数据库直接交互的操作被封装在 TXRecordDAO 当中.
图片
事务日志存储 DAO 层:
接下来是关于事务日志明细记录的持久化对象(PO,Persistent Object)模型定义:
• 内置了 gorm.Model,包含了主键 ID、创建时间 CreatedAt、更新时间 UpdatedAt、删除时间 DeletedAt 几个字段
• 事务状态 Status,标识事务所处的状态,分为进行中 hanging、成功 successful、失败 failure
• 组件 Try 响应明细记录 ComponentTryStatuses: 记录了事务下各组件 Try 请求响应结果,会以一个 json 字符串的格式存储,其真实的类型为 map[string]*ComponentTryStatus
下面是事务日志明细表的建表语句:
3.2.2 创建事务记录
接下来是通过过 TXStore 模块创建一条事务明细记录的实现代码:
dao 层创建事务明细记录的实现代码:
3.2.3 事务明细更新
下面是更新一笔事务明细的方法,其处理流程是:
• 针对这笔事务记录加写锁
• 根据组件的 try 响应结果,对 json字符串进行更新
• 将事务记录写回表中.
3.2.4 查询事务
接下来是查询事务的两个方法:
在 dao 层实现了一个通用的事务日志查询方法,通过 option 模式实现查询条件的灵活组装:
下面是关于 option 的具体定义,更多有关于这种模式的设计实现思路,可以参见我之前发表的文章——Golang 设计模式之建造者模式
3.2.5 提交事务结果
接下来是在事务执行完成后,将执行结果更新到事务明细记录中的处理方法:
3.2.6 加/解全局锁
最后,是实现整个 txStore 模块加/解锁的处理方法,内部是基于 redis 实现的分布式锁:
到这里为止,所有前置准备工作都已经处理完成,接下来我们展示一个应用到 gotcc 框架的使用示例。
3.3 使用代码示例
由于我实现的 txStore 和 tccComponent 需要依赖到 mysql 和 redis 两个组件,因此在这里需要输入对应的信息.
单测代码相对比较简单,其中一些要点通过注释给出:
4 总结
到这里,本文正文内容全部结束. 这里回头再对本期分享的内容做个总结:
• 本期我基于 golang 从零到一搭建了一个 TCC 分布式事务框架的开源项目 gotcc,并围绕着这个项目展开源码级别的讲解。
• 在 gotcc 中,对事务协调器 TXManager 相关的核心处理逻辑,如 Try-Confirm/Cancel 两阶段流程串联、TCC 组件注册、异步轮询任务等内容进行实现,并将这部分核心内容抽出放在了 SDK 中,供应用方使用。
• 在 gotcc 中还定义了 TCC 组件和事务日志存储模块的抽象 interface,这部分内容需要由应用方自行实现,并在使用 gotcc 时将其注入到 TXManager 当中。