func (coordinator *DefaultCoordinator) (session getty.Session, err error) {// 释放 TCP 连接SessionManager.ReleaseGettySession(session)session.Closelog.Errorf("getty_session{%s} got error{%v}, will be closed.", session.Stat, err)}
func (coordinator *DefaultCoordinator) OnClose(session getty.Session) {log.Info("getty_session{%s} is closing......", session.Stat)}
func (coordinator *DefaultCoordinator) OnMessage(session getty.Session, pkg interface{}) {log.Debugf("received message:{%v}", pkg)rpcMessage, ok := pkg.(protocal.RpcMessage)if ok {_, isRegTM := rpcMessage.Body.(protocal.RegisterTMRequest)if isRegTM {// 将 TransactionManager 信息和 TCP 连接建立映射关系coordinator.OnRegTmMessage(rpcMessage, session)return}
heartBeat, isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage)if isHeartBeat && heartBeat == protocal.HeartBeatMessagePing {coordinator.OnCheckMessage(rpcMessage, session)return}
if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST ||rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY {log.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body)_, isRegRM := rpcMessage.Body.(protocal.RegisterRMRequest)if isRegRM {// 将 ResourceManager 信息和 TCP 连接建立映射关系coordinator.OnRegRmMessage(rpcMessage, session)} else {if SessionManager.IsRegistered(session) {defer func {if err := recover; err != nil {log.Errorf("Catch Exception while do RPC, request: %v,err: %w", rpcMessage, err)}}// 处理事务消息,全局事务注册、分支事务注册、分支事务提交、全局事务回滚等coordinator.OnTrxMessage(rpcMessage, session)} else {session.Closelog.Infof("close a unhandled connection! [%v]", session)}}} else {resp, loaded := coordinator.futures.Load(rpcMessage.Id)if loaded {response := resp.(*getty2.MessageFuture)response.Response = rpcMessage.Bodyresponse.Done
func (coordinator *DefaultCoordinator) OnCron(session getty.Session) {
}
coordinator.OnRegTmMessage(rpcMessage, session)注册 Transaction Manager, coordinator.OnRegRmMessage(rpcMessage, session)注册 Resource Manager。具体逻辑分析见下文。
消息进入coordinator.OnTrxMessage(rpcMessage, session)方法,将按照消息的类型码路由到具体的逻辑当中:
switch msg.GetTypeCode {case protocal.TypeGlobalBegin:req := msg.(protocal.GlobalBeginRequest)resp := coordinator.doGlobalBegin(req, ctx)return respcase protocal.TypeGlobalStatus:req := msg.(protocal.GlobalStatusRequest)resp := coordinator.doGlobalStatus(req, ctx)return respcase protocal.TypeGlobalReport:req := msg.(protocal.GlobalReportRequest)resp := coordinator.doGlobalReport(req, ctx)return respcase protocal.TypeGlobalCommit:req := msg.(protocal.GlobalCommitRequest)resp := coordinator.doGlobalCommit(req, ctx)return respcase protocal.TypeGlobalRollback:req := msg.(protocal.GlobalRollbackRequest)resp := coordinator.doGlobalRollback(req, ctx)return respcase protocal.TypeBranchRegister:req := msg.(protocal.BranchRegisterRequest)resp := coordinator.doBranchRegister(req, ctx)return respcase protocal.TypeBranchStatusReport:req := msg.(protocal.BranchReportRequest)resp := coordinator.doBranchReport(req, ctx)return respdefault:return nil}
4)session manager 分析
Client 端同 Transaction Coordinator 建立连接起连接后,通过 clientSessionManager.RegisterGettySession(session)将连接保存在 serverSessions = sync.Map{}这个 map 中。map 的 key 为从 session 中获取的 RemoteAddress 即 Transaction Coordinator 的地址,value 为 session。这样,Client 端就可以通过 map 中的一个 session 来向 Transaction Coordinator 注册 Transaction Manager 和 Resource Manager 了。具体代码见getty_client_session_manager.go 。
Transaction Manager 和 Resource Manager 注册到 Transaction Coordinator 后,一个连接既有可能用来发送 TM 消息也有可能用来发送 RM 消息。我们通过 RpcContext 来标识一个连接信息:
type RpcContext struct {Version stringTransactionServiceGroup stringClientRole meta.TransactionRoleApplicationId stringClientId stringResourceSets *model.SetSession getty.Session}
当收到事务消息时,我们需要构造这样一个 RpcContext 供后续事务处理逻辑使用。所以,我们会构造下列 map 来缓存映射关系:
var (// session -> transactionRole// TM will register before RM, if a session is not the TM registered,// it will be the RM registeredsession_transactionroles = sync.Map{}// session -> applicationIdidentified_sessions = sync.Map{}
// applicationId -> ip -> port -> sessionclient_sessions = sync.Map{}
// applicationId -> resourceIdsclient_resources = sync.Map{})
这样,Transaction Manager 和 Resource Manager 分别通过coordinator.OnRegTmMessage(rpcMessage, session)和 coordinator.OnRegRmMessage(rpcMessage, session)注册到 Transaction Coordinator 时,会在上述 client_sessions map 中缓存 applicationId、ip、port 与 session 的关系,在client_resources map 中缓存 applicationId 与 resourceIds(一个应用可能存在多个 Resource Manager) 的关系。
在需要时,我们就可以通过上述映射关系构造一个 RpcContext。这部分的实现和 java 版 seata 有很大的不同,感兴趣的可以深入了解一下。具体代码见getty_session_manager.go 。
至此,我们就分析完了seata-golang 整个 RPC 通信模型的机制。
seata-golang 的未来
seata-golang 从今年 4 月份开始开发,到 8 月份基本实现和 java 版seata 1.2 协议的互通,对 mysql 数据库实现了 AT 模式(自动协调分布式事务的提交回滚),实现了 TCC 模式,TC 端使用 mysql 存储数据,使 TC 变成一个无状态应用支持高可用部署。下图展示了 AT 模式的原理:
seata 官方 : https://seata.io
java 版 seata : https://github.com/seata/seata
seata-golang 项目地址 : https://github.com/opentrx/seata-golang
seata-golang go 夜读 b 站分享: https://www.bilibili.com/video/BV1oz411e72T