详解 Seata Golang 客户端 AT 模式及其使用
概述
我们知道 Seata Java Client 的 AT 模式,通过代理数据源,实现了对业务代码无侵入的分布式事务协调机制,将与 Transaction Coordinator (TC) 交互的逻辑、Commit 的逻辑、Rollback 的逻辑,隐藏在切面和代理数据源相应的代码中,使开发者无感知。那如果这个方法,要用 Golang 来实现一遍,应该如何操作呢?关于这个问题,我想了很久,最初的设想是,对 database/sql
的 mysql driver 进行增强,在对包 github.com/go-sql-driver/mysql
研究了一段时间后,还是没有头绪,不知如何下手,最后转而增强 database/sql
包。由于 AT 模式必须保证本地事务的正确处理,在具体业务开发时,首先要通过 db.Begin()
获得一个 Tx 对象,然后再 tx.Exec()
执行数据库操作,最后 tx.Commit()
提交或 tx.Rollback()
回滚。这种处理方式算是一个 Golang 数据库事务处理的基本操作。 所以对 database/sql
的增强,我们重点关注这几个方法 db.Begin()
、 tx.Exec()
、tx.Commit()
、tx.Rollback
。
事务提交、回滚
通过 Seata Java Client 的相关代码,我们知道,在本地事务提交的时候,主要是将分支事务注册到 TC 上,并将数据库操作产生的 undoLog 一起写入到 undoLog 表;本地事务回滚的时候,需要将分支事务(即本地事务)的执行状态报告给 TC,使 TC 好知道是否通知参与全局事务的其他分支回滚。
func (tx *Tx) Commit() error {
//注册分支事务
branchId,err := tx.register()
if err != nil {
return errors.WithStack(err)
}
tx.tx.Context.BranchId = branchId
if tx.tx.Context.HasUndoLog() {
//将 undoLog 写入 undoLog 表
err = manager.GetUndoLogManager().FlushUndoLogs(tx.tx)
if err != nil {
err1 := tx.report(false)
if err1 != nil {
return errors.WithStack(err1)
}
return errors.WithStack(err)
}
err = tx.tx.Commit()
if err != nil {
err1 := tx.report(false)
if err1 != nil {
return errors.WithStack(err1)
}
return errors.WithStack(err)
}
} else {
return tx.tx.Commit()
}
if tx.reportSuccessEnable {
tx.report(true)
}
tx.tx.Context.Reset()
return nil
}
db.Begin()
会产生一个 Tx 对象,tx.Exec()
会产生 undoLog,tx.Commit()
将 undoLog 刷到数据库中。那么 undoLog 保存到哪里呢?答案是 Tx_Context 中。
type TxContext struct {
*context.RootContext
Xid string
BranchId int64
IsGlobalLockRequire bool
LockKeysBuffer *model.Set
SqlUndoItemsBuffer []*undo.SqlUndoLog
}
Commit() 方法中的 tx.tx.Context
,第一个 tx 是封装的 Tx 对象,第二个 tx 是 database/sql
的 Tx,tx.tx.Context
则是 Tx_Contex。UndoLogManager 则是操作 undoLog 的核心对象,处理 undoLog 的插入、删除,并查询出 undoLog 用于回滚。
func (tx *Tx) Rollback() error {
err := tx.tx.Rollback()
if tx.tx.Context.InGlobalTransaction() && tx.tx.Context.IsBranchRegistered() {
// 报告 TC 分支事务执行失败
tx.report(false)
}
tx.tx.Context.Reset()
return err
}
通过上面的代码呢,我们知道增强型 Tx 对象需要向 TC 注册分支事务,并报告分支事务的执行状态,相应代码如下:
func (tx *Tx) register() (int64,error) {
return dataSourceManager.BranchRegister(meta.BranchTypeAT,tx.tx.ResourceId,"",tx.tx.Context.Xid,
nil,tx.tx.Context.BuildLockKeys())
}
func (tx *Tx) report(commitDone bool) error {
retry := tx.reportRetryCount
for retry > 0 {
var err error
if commitDone {
err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
meta.BranchStatusPhaseoneDone,nil)
} else {
err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
meta.BranchStatusPhaseoneFailed,nil)
}
if err != nil {
logging.Logger.Errorf("Failed to report [%d/%s] commit done [%t] Retry Countdown: %d",
tx.tx.Context.BranchId,tx.tx.Context.Xid,commitDone,retry)
retry = retry -1
if retry == 0 {
return errors.WithMessagef(err,"Failed to report branch status %t",commitDone)
}
}
}
return nil
}
和 TC 进行通信的主要逻辑还是在 DataSourceManager 里面。AT 模式涉及的两个关键对象 DataSourceManager、UndoLogManager 就浮出水面。一个用于远程 TC 交互,一个用于本地数据库处理。
事务执行
func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
var parser = p.New()
// 解析业务 sql
act,_ := parser.ParseOneStmt(query,"","")
deleteStmt,isDelete := act.(*ast.DeleteStmt)
if isDelete {
executor := &DeleteExecutor{
tx: tx.tx,
sqlRecognizer: mysql.NewMysqlDeleteRecognizer(query,deleteStmt),
values: args,
}
return executor.Execute()
}
insertStmt,isInsert := act.(*ast.InsertStmt)
if isInsert {
executor := &InsertExecutor{
tx: tx.tx,
sqlRecognizer: mysql.NewMysqlInsertRecognizer(query,insertStmt),
values: args,
}
return executor.Execute()
}
updateStmt,isUpdate := act.(*ast.UpdateStmt)
if isUpdate {
executor := &UpdateExecutor{
tx: tx.tx,
sqlRecognizer: mysql.NewMysqlUpdateRecognizer(query,updateStmt),
values: args,
}
return executor.Execute()
}
return tx.tx.Tx.Exec(query,args)
}
执行业务 sql,并生成 undoLog 的关键,在于识别业务 sql 执行了什么操作:插入?删除?修改?这里使用 tidb 的 sql parser 去解析业务 sql,再使用相应的执行器去执行业务 sql,生成 undoLog 保存在 Tx_Context 中。
事务开启
db.Begin()
返回增强型的 Tx 对象。
func (db *DB) Begin(ctx *context.RootContext) (*Tx,error) {
tx,err := db.DB.Begin()
if err != nil {
return nil,err
}
proxyTx := &tx2.ProxyTx{
Tx: tx,
DSN: db.conf.DSN,
ResourceId: db.GetResourceId(),
Context: tx2.NewTxContext(ctx),
}
return &Tx{
tx: proxyTx,
reportRetryCount: db.conf.ReportRetryCount,
reportSuccessEnable: db.conf.ReportSuccessEnable,
},nil
}
seata-golang at 模式的使用
- 首先执行 scripts 脚本,初始化数据库
如果之前没有初始化过 seata 数据库,先执行seata-golang/scripts/server/db/mysql.sql
脚本 - 修改 dsn 数据库配置,修改下列文件:
seata-golang/tc/app/profiles/dev/config.yml
seata-golang/samples/at/product_svc/conf/client.yml
seata-golang/samples/at/product_svc/conf/client.yml
- 将下列文件中的 configPath 修改为 client.yml 配置文件的路径
seata-golang/samples/at/product_svc/main.go
seata-golang/samples/at/order_svc/main.go
seata-golang/samples/at/aggregation_svc/main.go
- 依次运行 tc、order_svc、product_svc、aggragation_svc,访问下列地址开始测试:
http://localhost:8003/createSoCommit
http://localhost:8003/createSoRollback
TC 启动参考参与 Seata 社区到 go 与 Seata 的邂逅
seata-golang 后续安排
接下来不打算再增加新的 feature。Java 版 Seata 毕竟发展了一年多时间,并且有很多社区成员一起维护,Go 版本目前主要是我在开发,时间不到2个月,现有的代码,仅是完成了框架,还需要大量优化,改bug,后续的工作重心在于使 seata-golang 稳定运行,生产可用,希望对分布式事务感兴趣且对 Go 感兴趣的同学一起加入进来,一起做些事情。进入微信群,请加我微信:scottlewis
详解 Seata Golang 客户端 AT 模式及其使用的更多相关文章
- LVS原理详解(3种工作模式及8种调度算法)
2017年1月12日, 星期四 LVS原理详解(3种工作模式及8种调度算法) LVS原理详解及部署之二:LVS原理详解(3种工作方式8种调度算法) 作者:woshiliwentong 发布日期: ...
- 红黑树原理详解及golang实现
目录 红黑树原理详解及golang实现 二叉查找树 性质 红黑树 性质 operation 红黑树的插入 golang实现 类型定义 leftRotate RightRotate Item Inter ...
- 图文详解AO打印(标准模式)
一.概述 AO打印是英文Active-Online Print的简称,也称主动在线打印.打印前支持AO通讯协议的AO打印机(购买地址>>)首先通过普通网络与C-Lodop服务保持在线链 ...
- Rserve详解,R语言客户端RSclient【转】
R语言服务器程序 Rserve详解 http://blog.fens.me/r-rserve-server/ Rserve的R语言客户端RSclient https://blog.csdn.net/u ...
- Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->干什么事(具体怎么做,就交给Stream)--》聚合
Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...
- JAVA设计模式详解(一)----------策略模式
策略模式,顾名思义就是设计一个策略算法,然后与对象拆分开来将其单独封装到一系列策略类中,并且它们之间可以相互替换.首先LZ举一个例子为大家引出这一个模式. 例子:某公司的中秋节奖励制度为每个员工发放2 ...
- 【SignalR学习系列】7. SignalR Hubs Api 详解(JavaScript 客户端)
SignalR 的 generated proxy 服务端 public class ContosoChatHub : Hub { public void NewContosoChatMessage( ...
- android中的LaunchMode详解----四种加载模式
Activity有四种加载模式: standard singleTop singleTask singleInstance 配置加载模式的位置在AndroidManifest.xml文件中activi ...
- JAVA设计模式详解(六)----------状态模式
各位朋友,本次LZ分享的是状态模式,在这之前,恳请LZ解释一下,由于最近公司事情多,比较忙,所以导致更新速度稍微慢了些(哦,往后LZ会越来越忙=.=). 状态模式,又称状态对象模式(Pattern o ...
随机推荐
- Spring学习笔记(三):面向切面的Spring
Spring之面向切面编程 一.理解何为面向切面编程 对于这个的理解,我觉得Spring实战中的例子讲得很明白: 假设我现在是一个小区用户,每个月小区都要收电费,这时候就会来人查看电表,算出来这个月电 ...
- java基础(反射,注解,多线程,juc)
JAVA基础 java反射 class对象 三种方式获取class加载时对象 1.class.forName("全类名"):将字节码文件加载进内存,返回class对象 2.类名.c ...
- 初涉WebGL
之前一直在捣鼓Vue和React栈,对组件化架构项目有了些理解和体会.今天尝尝WebGL,当然,并不打算现在深入,只是略作了解,我知道这个坑很深. js的图形库.3d库也有好几款比较流行的,如游戏开发 ...
- React:Conditional Rendering(条件渲染)
就像JS中常常会根据条件(比如if/else.switch)返回不同的值,React中也可以根据组件的状态或其他参考条件返回不同的React Element. 比如根据用户是否登陆渲染对应的UI面板. ...
- [Unity A*算法]A*算法的简单实现
写在前面:之前看过一点,然后看不懂,也没用过. 最近正好重构项目看到寻路这块,想起来就去查查资料,总算稍微理解一点了,下面记录一下自己的成果(哈哈哈 :> ) 下面分享几篇我觉得挺不错的文章 A ...
- Java基础之抽象类和接口
今天来说说抽象类和接口的实现以及它们的区别.我们知道抽象类和接口都是对具体事物的抽象,接口在实现上比抽象类更加抽象,抽象类中可以有普通方法和变量,而接口中只有抽象方法和不可变常量.但是从另一个角度看, ...
- 二、工具类ImageUtil——图片处理
这个工具类完成的工作如下: 1.第一个static方法,完成图片格式的转换.统一转换成.jpg格式. package util; import java.awt.Toolkit; import jav ...
- 【python爬虫】scrapy入门4--添加cookies
(1) settings.py 取消注释:COOKIES_ENABLED = True (2)爬虫xx.py def parse(self, response): c_dic = {自己抓包} # 获 ...
- 405 - 不允许用于访问此页的 HTTP 谓词的处理办法
今天介绍的是针对访问html页面时出现此类错误的处理办法,如果你的问题页面是其他类型,可以参考如下信息: IIS 返回 405 - 不允许用于访问此页的 HTTP 谓词.终极解决办法!!!! 1.为什 ...
- [Python基础]005.语法(4)
语法(4) 类 创建 self 方法 变量 综合例子 继承 类 创建 与其他大部分语言一样,Python也用 class 来创建类. class Person: # Person类 pass # 空语 ...