seata-golang 接入指南

本文涉及的产品
应用实时监控服务-用户体验监控,每月100OCU免费额度
性能测试 PTS,5000VUM额度
可观测可视化 Grafana 版,10个用户账号 1个月
简介: seata-golang 是一个分布式事务框架,实现了 AT 模式和 TCC 模式,AT 模式相较 TCC 模式对代码的入侵性更小、需要开发的接口更少;但 AT 模式对事务操作的数据持有全局锁,从这点来说,TCC 模式性能更好。

头图.png

作者 | 刘晓敏
来源|阿里巴巴云原生公众号

seata-golang 是一个分布式事务框架,实现了 AT 模式和 TCC 模式,AT 模式相较 TCC 模式对代码的入侵性更小、需要开发的接口更少;但 AT 模式对事务操作的数据持有全局锁,从这点来说,TCC 模式性能更好。

seata 的 AT 模式将全局锁放在 transaction coordinator 也就是事务协调器上,依赖于具体锁接口的存储实现方式可以是 file/db/redis 等,而不是数据库锁,每个分支事务提交时立即释放数据库锁,这样对数据库的压力也就减小了,变相得提升了数据库的性能。seata AT 模式和 TCC 模式的原理见:[[Seata 是什么?]](http://seata.io/zh-cn/docs/overview/what-is-seata.html)

下面以 seats-golang samples 为例,就 AT 模式和 TCC 模式如何接入到业务中做一个说明。

AT 模式接入

samples/at 目录下,有三个微服务:product_svc、order_svc、aggregation_svc。

  • product_svc 负责创建订单时扣减库存。
  • order_svc 负责创建订单时写入订单主表和订单明细表。
  • aggregation_svc 通过 http 请求调用 order_svc 和 product _svc 的接口。

1.png

1. 全局事务代理

熟悉 seata java 框架的都知道,seata java 框架通过扫描 @GlobalTransactional 注解,动态生成 AOP 切面,代理被 @GlobalTransactional 标记的方法,实现全局事务的开启、提交或者回滚。

不同于作为解释型语言的 Java,Go 是一种编译型语言,所以 seata-golang 使用了反射技术实现动态代理功能,被代理的对象需要实现 GlobalTransactionProxyService 接口。

type GlobalTransactionProxyService interface {
    GetProxyService() interface{}
    GetMethodTransactionInfo(methodName string) *TransactionInfo
}

aggregation_svc 中的 Svc struct 有一个方法 CreateSo,该方法通过对 order_svc 和 product_svc 的调用实现了创建订单和扣减库存。seata-golang 要代理该 *Svc 对象,需要创建一个代理对象,被代理的方法要在代理对象中作为一个空方法成员,等待 seata-golang 去动态实现。

type ProxyService struct {
    *Svc
    CreateSo func(ctx context.Context, rollback bool) error
}

代理对象 ProxyService 通过组合方式内置被代理对象 Svc,在开发者调用 tm.Implement(svc.ProxySvc) 方法后,seata-golang 会通过 Svc 实现的 GlobalTransactionProxyService 接口获取动态创建 CreateSo 方法所需要的事务信息,然后根据这些事务信息去动态创建 CreateSo 方法:开启事务 -> 执行被代理 *Svc 对象的 CreateSo 方法逻辑 -> 根据被代理的 CreateSo 方法的返回错误信息决定提交还是回滚。

2.png

2. 传递全局事务 ID

可以通过如下三种方式传递全局事务 ID。

1)Http

在 aggregation_svc 这个服务里,Seata-golang 通过 request header (req.Header.Set("XID", rootContext.GetXID()))将 XID (全局事务 ID)传递到了 order_svc 和 product_svc,order_svc 和 product_svc 则从 Request Header 取出 XID (c.Request.Header.Get("XID"))用于分支事务处理。

2)Dubbo

如果使用 dubbo 协议 rpc 通信,则需要把 XID 注入到 attachment 中传递到下游。

3.png

如果使用 dubbo-go 框架,dubbo-go 会从 context 中读取 attachment 将其序列化传递给服务端。可以采用如下的方式,将 XID 传递出去:

context.WithValue(ctx, "attachment", map[string]string{
        "XID": rootContext.GetXID(),
}

dubbo-go 服务端则从 attachment 中取出 XID,再注入到 context 中,分支事务的业务方法则可以从 context 中获取 XID 用于分支事务处理。

// SeataFilter ...
type SeataFilter struct {
}

// Invoke ...
func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
    xid := invocation.AttachmentsByKey("XID", "")
    if xid != "" {
        return invoker.Invoke(context.WithValue(ctx, "XID", xid), invocation)
    }
    return invoker.Invoke(ctx, invocation)
}

// OnResponse ...
func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {
    return result
}

// GetSeataFilter ...
func getSeataFilter() filter.Filter {
    return &SeataFilter{}
}

上面的 filter 通过 extension.SetFilter("SEATA", getSeataFilter) 方法可将其注入到 dubbo-go 的 filter 链。

3)GRPC

grpc 可通过 metadata 传递 XID。

客户端首先将 XID 放入 md := metadata.Pairs("XID", rootContext.GetXID()),再将 metadata 传入 context:metadata.NewOutgoingContext(context.Background(), md)

服务端则通过 md, ok := metadata.FromIncomingContext(ctx) 获取到 metadata,再从中取出 XID。

3. 事务分支处理

AT 模式除了要对发起全局事务的方法做代理,还需要对数据源做代理。

seata 通过代理数据源,对 sql 语句进行解析,来获取修改数据的修改前和修改后的数据,供 transaction coordinator 回滚时使用。对数据源的代理,只需要将你创建的 sql driver 实例注入到 seata-golang 的 db 操作对象中:

db, err := exec.NewDB(config.GetATConfig(), {你的 sql driver 实例})

如果你使用了 xorm 或者 gorm,则可从 xorm 对象或者 gorm 对象中取出 sql driver 实例,用上面的方法构造出 seata-golang 的 db 操作对象。这意味着你可以同时使用 orm 框架和 seata-golang 框架,当你的操作需要用到事务时,用 seata-golang 的 db 操作对象去执行 sql 语句。

通过上一节的介绍,开发者已经可以在服务端拿到上游传递过来的 XID 了。为了将分支事务加入到全局事务组中,开发者需要使用获取的 XID 构造一个 RootContext:

rootContext := &context.RootContext{Context: ctx}
rootContext.Bind("{上游获取到的 XID}")

开启分支事务时,调用流程如下:

  • 调用 seata-golang 的 db 操作对象的 Begin 方法获取分支事务对象 tx, err := dao.Begin(ctx)
  • 执行 sql 语句则使用该分支事务对象 tx 的 Exec 方法 func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error)
  • 执行完 sql 操作逻辑后,可根据返回的结果,调用 tx.Commit()tx.Rollback() 来提交或回滚分支操作。

最后,整个分支事务是否成功提交,执行成功还是失败需要返回结果给调用方,也就是全局事务的发起方,transaction manager 会根据返回的结果决定是否提交或回滚整个全局事务。

TCC 模式接入

TCC 模式相较 AT 模式,约束会多一些。TCC 模式首先要求开发者实现 TccService 接口,还要求接口三个方法的参数都封装到一个 BusinessActionContext 里。

开发者调用 Try 方法,seata-golang 框架调用 Confirm/Cancel 方法。框架根据所有分支事务 Try 方法是否都执行成功,来决定发起全局提交或回滚。全局提交则由框架自动调用每个事务分支的 Confirm 方法,全局回滚则调用加入事务组的所有事务分支的 Cancel 方法。

type TccService interface {
    Try(ctx *context.BusinessActionContext) (bool, error)
    Confirm(ctx *context.BusinessActionContext) bool
    Cancel(ctx *context.BusinessActionContext) bool
}

在调用 Try 方法之前,事务分支要加入事务组,且需要把 Try 方法执行的上下文即 BusinessActionContext 存到 Transaction coordinator,这样框架在提交或回滚时,才能把 BusinessActionContext 参数传递给 confirm、cancel 方法,这部分逻辑仍然通过代理实现。所以开发者还需要创建一个代理类,并实现接口 TccProxyService:

type TccProxyService interface {
    GetTccService() TccService
}

通过调用 tcc.ImplementTCC({代理类实例}) 方法,框架会为代理类实现上述逻辑。开发者可在 samples/tcc 目录查看 tcc 模式的示例。

4.png

总结陈述

除了项目结构目录内的 samples,还有一个 dubbo-go 的例子 dubbo-go-seata。对于上文讲述的接入方法,还希望读者结合代码多多理解,融汇贯通。

当前 seata-golang 与最新的 seata java 1.4 版本协议上完全打通,如果有公司在技术栈上既有使用 java 语言也有使用 golang 语言,可接入 seata 框架来解决您的分布式事务后顾之忧。

如果你有任何疑问,欢迎钉钉扫码加入交流群【钉钉群号 33069364】:

5.png

参考资料

作者简介

刘晓敏 (GitHubID dk-lockdown),目前就职于 h3c 成都分公司,擅长使用 Java/Go 语言,在云原生和微服务相关技术方向均有涉猎,目前专攻分布式事务。

相关文章
|
6月前
|
存储 Java Nacos
Seata常见问题之springboot 2.3.7 和高版本 seata 2.0.0,1.6.1不兼容如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
847 0
|
6月前
|
关系型数据库 Java Nacos
Seata的部署和集成
部署Seata的tc-server
|
6月前
|
SQL 关系型数据库 数据库
seata开发指南
seata开发指南
|
11月前
|
自然语言处理 Dubbo 应用服务中间件
Higress和dubbo-go-pixiu都与网关和服务调用有关
Higress和dubbo-go-pixiu都与网关和服务调用有关
121 1
|
11月前
|
XML 编解码 JSON
kratos配置
kratos配置
|
开发框架 JavaScript .NET
Golang微服务框架kratos实现SignalR服务
SignalR 在可用的情况下使用新的 WebSocket 传输,并在必要时回退到旧传输。 虽然当然可以直接使用 WebSocket 编写应用,但使用 SignalR 意味着需要实现的许多额外功能已经为你完成。 最重要的是,这意味着你可以编写应用代码以利用 WebSocket,而无需担心为旧客户端创建单独的代码路径。 SignalR 还可以避免担心 WebSocket 的更新,因为 SignalR 已更新以支持基础传输中的更改,从而为应用程序提供跨 WebSocket 版本的一致接口。
68 0
|
Go API 微服务
Golang微服务框架Kratos轻松集成并使用Swagger UI
在我们的开发当中,调试接口,测试接口,提供接口文档给前端,那都是非常频繁的工作内容。 那么,我们需要用什么方法和工具来实施这些工作内容呢? Swagger,或者说OpenAPI。
277 0
|
前端开发 Go API
Golang微服务框架Kratos使用问题总结
官方默认的 layout 目录已其实已经包含第三方包,但proto 文件仍然会出现红色波浪线,如`import "google/api/annotations.proto";`,以 VS Code 为例,只需要添加如下文件及配置:
1087 0
Golang微服务框架Kratos使用问题总结
|
Go 微服务
|
SpringCloudAlibaba 关系型数据库 MySQL
SpringCloud与Seata分布式事务初体验
在本篇文章中我们在`SpringCloud`环境下通过使用`Seata`来模拟用户`购买商品`时由于用户**余额不足**导致本次订单提交失败,来验证下在`MySQL`数据库内事务是否会`回滚`。
下一篇
无影云桌面