您當前的位置:首頁 > 繪畫

seata-golang 接入指南

作者:由 阿里技術 發表于 繪畫時間:2021-02-19

簡介:

seata-golang 是一個分散式事務框架,實現了 AT 模式和 TCC 模式,AT 模式相較 TCC 模式對程式碼的入侵性更小、需要開發的介面更少;但 AT 模式對事務操作的資料持有全域性鎖,從這點來說,TCC 模式效能更好。

頭圖.png

頭圖.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/ove

rview/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.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.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

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

4.png

總結陳述

除了專案結構目錄內的 samples,還有一個 dubbo-go 的例子 dubbo-go-seata。對於上文講述的接入方法,還希望讀者結合程式碼多多理解,融匯貫通。

當前 seata-golang 與最新的 seata java 1。4 版本協議上完全打通,如果有公司在技術棧上既有使用 java 語言也有使用 golang 語言,可接入 seata 框架來解決您的分散式事務後顧之憂。

如果你有任何疑問,歡迎釘釘掃碼加入交流群【釘釘群號 33069364】

參考資料

seata 官方

https://

seata。io

java 版 seata

https://

github。com/seata/seata

seata-golang 專案地址

https://

github。com/opentrx/seat

a-golang

seata-golang go 夜讀 b 站分享

https://www。

bilibili。com/video/BV1o

z411e72T

基於 getty 的 seata-golang 通訊模型詳解

http://

seata。io/zh-cn/blog/sea

ta-golang-communication-mode。html

作者簡介

劉曉敏

(GitHubID dk-lockdown),目前就職於 h3c 成都分公司,擅長使用 Java/Go 語言,在雲原生和微服務相關技術方向均有涉獵,目前專攻分散式事務。

版權宣告:

本文內容由阿里雲實名註冊使用者自發貢獻,版權歸原作者所有,阿里雲開發者社群不擁有其著作權,亦不承擔相應法律責任。具體規則請檢視《阿里雲開發者社群使用者服務協議》和《阿里雲開發者社群智慧財產權保護指引》。如果您發現本社群中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社群將立刻刪除涉嫌侵權內容。

標簽: Seata  Golang  事務  XID  SVC