分佈式事務保持一致性,記錄下我的方案,暫命名爲事務傳遞提交。
假設有ABCD四個系統,有一個事務,需要在四個系統裏都進行事務修改數據提交。
先設置一個事務傳遞參數,如下:
type AffairArgs struct {
Sys string //系統名
Method RpcMethod //方法名
Dic *map[string]interface{} //方法參數
}
第一步,A作爲主系統,先處理完事務,不要提交;
第二步,A生成調用BCD所需要的數據,示意如下:
注意:如果A對BCD執行順序有要求,可以調整BCD的順序,BCD是按照數組的順序依次執行的。
//rpc調用B系統接口
argAry := []saRpc.AffairArgs{
saRpc.AffairArgs{
Sys: "B",
Method: "B的接口",
Dic: "參數",
},
saRpc.AffairArgs{
Sys: "C",
Method: "C的接口",
Dic: "參數",
},
saRpc.AffairArgs{
Sys: "D",
Method: "D的接口",
Dic: "參數",
},
}
err := saRpc.Call("B接口", &argAry)
if err != nil {
_ = sess.Rollback()
}
第三步,BCD中事務處理示意如下:
//事務接口演示
func Affair_demo(ary *[]saRpc.AffairArgs) error {
var err error
if ary != nil {
var next *saRpc.AffairArgs
var curr *saRpc.AffairArgs
var index = -1
for i, v := range *ary {
if v.Sys == revel.AppName {
index = i
curr = &v
} else if next == nil {
next = &v
}
}
//刪除當前系統的事務
if index >= 0 {
*ary = append((*ary)[:index], (*ary)[index+1:]...)
}
var sess *xorm.Session
if curr != nil {
//完成當前系統需要完成的操作,不要提交事務
sess, err = Affair_demo(curr)
}
//執行下一個系統的事務
if next != nil {
err = saRpc.Call(next.Method, next.Dic, nil)
if err != nil {
//roll back
}
}
//提交事務
if err == nil && sess != nil {
err = sess.Commit()
if err != nil && curr != nil {
saLog.Log(curr.Sys, curr.Method, "提交事務失敗")
}
}
}
return err
}
事務處理思路是:
- 先在數組內,查找到本系統需要處理的事務(curr),再找下一個需要處理事務的系統(next)。
- 本系統處理完,不提交事務
- 調用下一個系統,等待下一個系統執行結果
- 下一個系統如果執行成功,則提交事務;如果失敗,則回滾。
- 返回上一個系統執行結果
完整執行過程:
假設事務傳遞的順序是A->B->C->D,在傳導的過程中,執行的過程如下:
- ABC事務都完成了,但是都沒提交事務;
- 最後D完成事務,成功則commit,失敗則rollback,並返回C結果;
- C收到結果後,成功則commit,失敗則rollback,返回B結果;
- B收到結果後,成功則commit,失敗則rollback,返回A結果;
- A收到結果後,成功則commit,失敗則rollback;
- 這樣逐級把結果返回,最終完成整個事務。
這裏會有一個問題,假設D成功了,返回到C的時候,C commit失敗了,則會返回失敗,B和A會回滾,但是D就無法回滾了。
commit失敗是極小概率事件,一般是災難(網絡問題),只能人爲干涉了
該方案優點:
較爲簡單易用,跟一般的rpc服務並沒有太大的差異,且不用擔心單點故障
缺點也很明顯:
ABCD是阻塞、順序調用,執行效率會比較低;對於commit失敗的處理,在極小概率下會出現數據不一致情況。