简述
Saga事务方案目前已有较成熟的开源实现servicecomb-saga,但servicecomb-saga是采用java语言实现的,对于golang语言的业务项目来说接入servicecomb-saga方案有一些困难,本项目尝试为servicecomb-saga实现一个golang语言的omega,以帮助golang语言的业务项目接入。
本程序库参考servicecomb-saga的omega模块,完全兼容servicecomb-saga java版实现,可与java版互操作。
使用方法
可参考test示例,下面说明主要步骤
假设原有的业务代码如下:
test/old/demo.go
package main
import (
"fmt"
"time"
"os"
"os/signal"
"syscall"
)
var (
BALANCES map[string]int
)
func init() {
initDatas()
}
func initDatas(){
BALANCES = make(map[string]int, 0)
BALANCES["foo"] = 500
BALANCES["bar"] = 500
}
func TransferMoney() error {
err := TransferOut("foo", 100)
if err != nil {
return err
}
err = TransferIn("bar", 100)
if err != nil {
return err
}
return nil
}
func TransferOut(from string, amount int) error {
oldAmount, _ := BALANCES[from]
BALANCES[from] = oldAmount - amount
return nil
}
func TransferIn(to string, amount int) error {
oldAmount, _ := BALANCES[to]
BALANCES[to] = oldAmount + amount
return nil
}
func main() {
TransferMoney()
stopped := false
go func() {
s := make(chan os.Signal)
signal.Notify(s, syscall.SIGINT, syscall.SIGTERM)
<-s
stopped = true
}()
for !stopped {
fmt.Println(BALANCES["foo"], BALANCES["bar"])
time.Sleep(time.Second * 3)
}
}
上面的代码比较简单,就是完成一个基本的转帐,将钱从一个帐户转到另一个帐户。
只需要根据以下的步骤,进行简单的改造即可接入分布式事务支持,最终改造后的代码见test/sagatx_demo.go。
初始化SagaAgent
在程序入口处初始化SagaAgent,代码如下:
func main(){
......
saga.InitSagaAgent("saga-go-demo", "10.12.142.216:30571", nil)
......
}
构造SagaStart、Compensable方法
由于go语言特性,无法无侵入地进行AOP编程,只能采用Decorator模式代替,因此用Decorator对原来的分布事务入口函数、本地事务函数进行包装,代码如下:
var (
TransferMoneySagaStartDecorated func() error
TransferOutCompensableDecorated func(from string, amount int) error
TransferInCompensableDecorated func(to string, amount int) error
)
func init() {
err := saga.DecorateSagaStartMethod(&TransferMoneySagaStartDecorated, TransferMoney, 20)
if err != nil {
panic(err)
}
err = saga.DecorateCompensableMethod(&TransferOutCompensableDecorated, TransferOut, CancelTransferOut, 5)
if err != nil {
panic(err)
}
err = saga.DecorateCompensableMethod(&TransferInCompensableDecorated, TransferIn, CancelTransferIn, 5)
if err != nil {
panic(err)
}
......
}
.......
func CancelTransferOut(from string, amount int) error {
oldAmount, _ := BALANCES[from]
BALANCES[from] = oldAmount + amount
return nil
}
......
func CancelTransferIn(to string, amount int) error {
oldAmount, _ := BALANCES[to]
BALANCES[to] = oldAmount - amount
return nil
}
注意,每个本地事务函数要提供对应的幂等补偿函数
修改对应的包装函数
由于go语言特性,无法无侵入地进行AOP编程,需要手动将原来的分布事务入口函数、本地事务函数修改为对应的包装函数,代码如下:
func TransferMoney() error {
//err := TransferOut("foo", 100)
err := TransferOutCompensableDecorated("foo", 100)
if err != nil {
return err
}
//err = TransferIn("bar", 100)
err = TransferInCompensableDecorated("bar", 100)
if err != nil {
return err
}
return nil
}
func main() {
......
//TransferMoney()
TransferMoneySagaStartDecorated()
......
}
传递saga上下文信息
如果一个分布式事务涉及到多个业务服务,则需要在业务服务间传递saga上下文信息,这里涉及两个步骤。
-
在接收到HTTP请求处使用middleware接收saga上下文信息,请使用合适的middleware进行处理。
-
发送HTTP请求到其它业务服务时,使用sagactx.InjectIntoHttpHeaders将当前的saga上下文信息织入HTTP请求头中。
TODO
- 支持多alpha负载均衡
License
Licensed under an Apache 2.0 license.