作者:MS07224_670 | 来源:互联网 | 2023-05-18 00:58
Workflow模式是github.comdtm-labsdtm独创推出的模式,在这个模式下,能够混合应用XA、SAGA、TCC,也能够混合应用HTTP、gRPC,用户能够对分布式事务外面的
Workflow 模式是github.com/dtm-labs/dtm独创推出的模式,在这个模式下,能够混合应用XA、SAGA、TCC,也能够混合应用HTTP、gRPC,用户能够对分布式事务外面的绝大部分内容进行定制,具备极大的灵活性,上面咱们以转账场景,讲述如何在Workflow下进行实现。
workflow例子
Workflow模式下,既能够应用HTTP协定,也能够应用gRPC协定,上面以gRPC协定作为例子,一共分为一下几步:
- 初始化 SDK
- 注册workflow
- 执行workflow
首先须要在应用workflow前对 SDK 的 Workflow 进行初始化:
import "github.com/dtm-labs/dtmgrpc/workflow"
// 初始化workflow SDK,三个参数别离为:
// 第一个参数,dtm服务器地址
// 第二个参数,业务服务器地址
// 第三个参数,grpcServer
// workflow的须要从"业务服务器地址"+"grpcServer"上接管dtm服务器的回调
workflow.InitGrpc(dtmGrpcServer, busi.BusiGrpc, gsvr)
而后须要注册workflow的处理函数
wfName := "wf_saga"
err := workflow.Register(wfName, func(wf *workflow.Workflow, data []byte) error {
req := MustUnmarshalReqGrpc(data)
wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
_, err := busi.BusiCli.TransOutRevert(wf.Context, req)
return err
})
_, err := busi.BusiCli.TransOut(wf.Context, req)
if err != nil {
return err
}
wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
_, err := busi.BusiCli.TransInRevert(wf.Context, req)
return err
})
_, err = busi.BusiCli.TransIn(wf.Context, req)
return err
})
- 这个注册操作须要在业务服务启动之后执行,因为当过程crash,dtm会回调业务服务器,持续未实现的工作
- 上述代码
NewBranch
会创立一个事务分支,一个分支会包含一个正向操作,以及全局事务提交/回滚时的回调
OnRollback/OnCommit
会给以后事务分支指定全局事务回滚/提交时的回调,上述代码中,只指定了OnRollback
,属于Saga模式
-
这外面的 busi.BusiCli
须要增加workflow的拦截器,该拦截器会主动把rpc的申请后果记录到dtm,如下所示
conn1, err := grpc.Dial(busi.BusiGrpc, grpc.WithUnaryInterceptor(workflow.Interceptor), nossl)
busi.BusiCli = busi.NewBusiClient(conn1)
当然您也能够给所有的gRPC client增加workflow.Interceptor
,这个中间件只会解决wf.Context
和wf.NewBranchContext()
下的申请
- 当工作流函数返回nil/ErrFailure,全局事务会进入Commit/Rollback阶段,反序调用函数外部OnCommit/OnRollback注册的操作
最初是执行workflow
req := &busi.ReqGrpc{Amount: 30}
err = workflow.Execute(wfName, shortuuid.New(), dtmgimp.MustProtoMarshal(req))
- 当Execute的后果为
nil/ErrFailure
时,全局事务已胜利/已回滚。
- 当Execute的后果为其余值时,dtm服务器后续会回调这个工作流工作进行重试
workflow原理
workflow是如何保障分布式事务的数据一致性呢?当业务过程呈现crash等问题时,dtm服务器会发现这个workflow全局事务超时未实现,那么dtm会采纳指数回避的策略,对workflow事务进行重试。当workflow的重试申请达到业务服务,SDK会从dtm服务器读取全局事务的进度,对于已实现的分支,会将之前保留的后果,通过gRPC/HTTP等拦截器,间接返回分支后果。最终workflow会顺利完成。
工作流函数须要做到幂等,即第一次调用,或者后续重试,都该当取得同样的后果
Workflow下的Saga
Saga模式源自于这篇论文 SAGAS,其核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都胜利提交实现,那么全局事务就失常实现,如果某个步骤失败,则依据相同程序一次调用弥补操作。
在Workflow模式下,您能够在函数中,间接调用正向操作的函数,而后将弥补操作写到分支的OnRollback
,那么弥补操作就会主动被调用,达到了Saga模式的成果
Workflow下的Tcc
Tcc模式源自于这篇论文 Life beyond Distributed Transactions:an Apostate’s Opinion,他将一个大事务分成多个小事务,每个小事务有三个操作:
- Try 阶段:尝试执行,实现所有业务查看(一致性), 预留必须业务资源(准隔离性)
- Confirm 阶段:如果所有分支的Try都胜利了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务查看,只应用 Try 阶段预留的业务资源
- Cancel 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel开释 Try 阶段预留的业务资源。
对于咱们的 A 跨行转账给 B 的场景,如果采纳SAGA,在正向操作中调余额,在弥补操作中,反向调整余额,那
么会呈现以下状况:
- A扣款胜利
- A看到余额缩小,并通知B
- 金额转入B失败,整个事务回滚
- B始终收不到这笔资金
这样给AB单方带来了极大的困扰。这种状况在SAGA中无奈防止,然而能够通过TCC来解决,设计技巧如下:
- 在账户中的 balance 字段之外,再引入一个 trading_balance 字段
- Try 阶段查看账户是否被解冻,查看账户余额是否短缺,没问题后,调整 trading_balance (即业务上的冻结资金)
- Confirm 阶段,调整 balance ,调整 trading_balance (即业务上的冻结资金)
- Cancel 阶段,调整 trading_balance (即业务上的冻结资金)
这种状况下,一旦终端用户 A 看到本人的余额扣减了,那么 B 肯定可能收到资金
在Workflow模式下,您能够在函数中,间接调用Try
操作,而后将Confirm
操作写到分支的OnCommit
,将Cancel
操作写到分支的OnRollback
,达到了Tcc
模式的成果
Workflow下的XA
XA是由X/Open组织提出的分布式事务的标准,XA标准次要定义了(全局)事务管理器(TM)和(部分)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中表演的是RM角色
XA一共分为两阶段:
第一阶段(prepare):即所有的参与者RM筹备执行事务并锁住须要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前支流的数据库根本都反对XA事务,包含mysql、oracle、sqlserver、postgre
在Workflow模式下,你能够在工作流函数中,调用NewBranch().DoXa
来开启您的XA事务分支。
多种模式混合应用
在Workflow模式下,上述的Saga、Tcc、XA都是分支事务的模式,因而能够局部分支采纳一种模式,其余分支采纳另一种模式。这种混合模式带来的灵活性能够做到依据分支事务的个性抉择子模式,因而倡议如下:
- XA:如果业务没有行锁争抢,那么能够采纳XA,这个模式须要的额定开发量比拟低,
Commit/Rollback
是数据库主动实现的。例如这个模式适宜创立订单业务,不同的订单锁定的订单行不同,相互之间并发无影响;不适宜扣减库存,因为波及同一个商品的订单都会争抢这个商品的行锁,会导致并发度低。
- Saga:不适宜XA的一般业务能够采纳这个模式,这个模式额定的开发量比Tcc要少,只须要开发正向操作和弥补操作
- Tcc:适宜一致性要求较高,例如后面介绍的转账,这个模式额定的开发量最多,须要开发包含
Try/Confirm/Cancel
幂等要求
在Workflow模式下,当crash产生时,会进行重试,因而要求各个操作反对幂等,即第屡次调用和一次调用的后果是一样的,返回雷同的后果。业务中,通常采纳数据库的unique key
来实现幂等,具体为insert ignore "unique-key"
,如果插入失败,阐明这个操作已实现,此次间接疏忽返回;如果插入胜利,阐明这是首次操作,持续后续的业务操作。
如果您的业务自身就是幂等的,那么您间接操作您的业务即可;如果您的业务为提供幂等性能,那么dtm提供了BranchBarrier
辅助类,基于上述unique-key原理,能够不便的帮忙开发者实现在Mysql/Mongo/Redis
中实现幂等操作。
以下两个是典型的非幂等操作,请留神:
- 超时回滚:如果您的业务中有一个操作可能耗时长,并且您想要让您的全局事务在期待超时后,返回失败,进行回滚。那么这个就不是幂等操作,因为在极其状况下,两个过程同时调用了该操作,一个返回了超时失败,而另一个返回了胜利,导致后果不同
- 达到重试下限后回滚:剖析过程同上。
Workflow模式暂未反对上述的超时回滚及重试达到下限后回滚,如果您有相干的场景需要,欢送把具体场景给咱们,咱们将踊跃思考是否增加这种的反对
分支操作后果
分支操作会返回以下几种后果:
- 胜利:分支操作返回
HTTP-200/gRPC-nil
- 业务失败:分支操作返回
HTTP-409/gRPC-Aborted
,不再重试,全局事务须要进行回滚
- 进行中:分支操作返回
HTTP-425/gRPC-FailPrecondition
,这个后果示意事务正在失常进行中,要求dtm重试时,不要采纳指数退却算法,而是采纳固定距离重试
- 未知谬误 :分支操作返回其余后果,示意未知谬误,dtm会重试这个工作流,采纳指数退却算法
如果您的现有服务与上述的后果不同,那么您能够通过workflow.Options.HTTPResp2DtmError/GRPCError2DtmError
来定制这部分后果
Saga的弥补操作、Tcc的Confirm/Cancel操作,依照Saga和Tcc的协定,是不容许返回业务上的失败,因为到了工作流的第二阶段Commit/Rollback,此时既不胜利,也不让重试,那么全局事务无奈实现,这点请开发者在设计时就要留神防止
事务实现告诉
局部业务场景,想要取得事务实现的告诉,这个性能能够通过在第一个事务分支上设置OnFinish
回调来实现。当回调函数被调用时,所有的业务操作曾经执行结束,因而全局事务在本质上曾经实现。回调函数能够根据传入的isCommit
来判断全局事务最终提交了还是回滚了。
有一个中央须要留神,收到OnFinish
回调时,dtm服务器上,这个事务的状态还未修改为最终状态,因而如果混合应用事务实现告诉和查问全局事务后果,那么两者的后果可能不统一,倡议用户只应用其中一种形式,而不要混合应用。
性能
在DTM里,失常实现一个Workflow事务,须要两个离开的全局事务写(一个是Prepare时保留,另一个是将状态改为胜利),须要保留两头事务进度(这部分批量化后,开销很小)。比照DTM的Saga模式,少了一个独自的分支事务保留,另外分支事务的写入质变小(胜利的事务不须要额定保留弥补分支),因而性能会比Saga的性能更好,具体的测试报告,将来会出。
下一步工作
- 逐步完善workflow的例子以及文档
- 反对分支事务并发
分割咱们
欢送拜访咱们的我的项目,并star反对咱们:
https://github.com/dtm-labs/dtm
关注【分布式事务】公众号,取得更多分布式事务相干常识