seata原理解析

1. 三种角色

  • tc,运行于seata-server,协调事务。每次收到开启事务请求,生成一个xid。
  • tm,运行于业务项目,向tc申请开启事务,获得xid。
  • rm,运行于业务项目,参与事务,向tc注册分支事务。

2. 一次正常commit流程

2.1. tm向tc申请开启事务、执行业务代码、向tc申请提交事务

  • 调用带注解的方法,进入切面
    io.seata.tm.api.TransactionalTemplate.execute
    -io.seata.tm.api.TransactionalTemplate.beginTransaction # 获取一个xid,然后放到上下文中
    --io.seata.tm.api.DefaultGlobalTransaction.begin(int, java.lang.String)
    ---io.seata.tm.DefaultTransactionManager.begin # 申请开启事务的请求消息GlobalBeginRequest,响应消息GlobalBeginResponse,可以看到response包含的xid
    ----io.seata.tm.DefaultTransactionManager.syncCall
    -----io.seata.core.rpc.netty.AbstractNettyRemotingClient.sendSyncRequest(java.lang.Object)
    ------io.seata.core.rpc.netty.AbstractNettyRemoting.sendSync # 使用netty调用seata-server,开启事务,获得一个xid
    --RootContext.bind(xid);
    -business.execute() #执行业务代码
    -io.seata.tm.api.TransactionalTemplate.commitTransaction # 提交分布式事务,请求消息GlobalCommitRequest,响应消息GlobalCommitResponse,request包含xid
    --io.seata.tm.api.DefaultGlobalTransaction.commit
    ---io.seata.tm.DefaultTransactionManager.commit # 申请提交分布式事务
    ----io.seata.tm.DefaultTransactionManager.syncCall
    -----io.seata.core.rpc.netty.AbstractNettyRemotingClient.sendSyncRequest(java.lang.Object)
    ------io.seata.core.rpc.netty.AbstractNettyRemoting.sendSync # 使用netty调用seata-server

2.2 tm给下游组件传递xid

  • 使用feign调用其他组件,将xid添加到http-header,key为TX_XID
    com.alibaba.cloud.seata.feign.SeataFeignClient.execute
    -com.alibaba.cloud.seata.feign.SeataFeignClient.getModifyRequest

openfeign使用这种方式传递xid
RestTemplate使用SeataRestTemplateInterceptor
dubbo使用ApacheDubboTransactionPropagationFilter、AlibabaDubboTransactionPropagationFilter

2.3. rm组件获取http-header中的TX_XID

com.alibaba.cloud.seata.web.SeataHandlerInterceptor.preHandle
-RootContext.bind(rpcXid) #绑定到上下文

2.4. rm执行数据库操作

io.seata.rm.datasource.PreparedStatementProxy.execute
-io.seata.rm.datasource.exec.ExecuteTemplate.execute(io.seata.rm.datasource.StatementProxy<S>, io.seata.rm.datasource.exec.StatementCallback<T,S>, java.lang.Object...)
--io.seata.rm.datasource.exec.ExecuteTemplate.execute(java.util.List<io.seata.sqlparser.SQLRecognizer>, io.seata.rm.datasource.StatementProxy<S>, io.seata.rm.datasource.exec.StatementCallback<T,S>, java.lang.Object...)
---io.seata.rm.datasource.sql.SQLVisitorFactory.get # 获取sql识别器
---获取sql执行器,增删改、select for update
----io.seata.rm.datasource.exec.BaseTransactionalExecutor.execute # 从上下文拿到xid,绑定到Connection
-----io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.doExecute # 执行sql
------io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.executeAutoCommitFalse
-------io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.beforeImage # 生成操作前镜像。抽象方法,根据增删改,有不同实现。
-------执行sql
-------io.seata.rm.datasource.exec.AbstractDMLBaseExecutor.afterImage # 生成操作前镜像。抽象方法,根据增删改,有不同实现。
-------io.seata.rm.datasource.exec.BaseTransactionalExecutor.prepareUndoLog # 用前后镜像制作undo_log,用于回滚

2.5. rm提交本地事务

org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit
-io.seata.rm.datasource.ConnectionProxy.commit
--io.seata.rm.datasource.ConnectionProxy.doCommit
---io.seata.rm.datasource.ConnectionProxy.processGlobalTransactionCommit
----io.seata.rm.datasource.ConnectionProxy.register
-----io.seata.rm.DefaultResourceManager.branchRegister
------io.seata.rm.AbstractResourceManager.branchRegister # 向tc注册分支事务,请求消息BranchRegisterRequest,包含xid,lockKeys,resourceId;响应消息BranchRegisterResponse,包含branchId(分支事务id)
----io.seata.rm.datasource.undo.UndoLogManagerFactory.getUndoLogManager
----io.seata.rm.datasource.undo.UndoLogManager.flushUndoLogs # 插入undo_log
----java.sql.Connection.commit # 组件提交本地事务

2.6. rm处理tc的commit消息

io.seata.core.rpc.processor.client.RmBranchCommitProcessor.process
-io.seata.core.rpc.processor.client.RmBranchCommitProcessor.handleBranchCommit # 请求消息BranchCommitRequest,值得注意的是请求消息来自来自tc(seata-server),包含xid,branchId,resourceId。rm处理完成后给tc回复响应消息BranchCommitResponse,包含xid,branchId,BranchStatus。
--io.seata.rm.AbstractRMHandler.onRequest
---io.seata.core.protocol.transaction.BranchCommitRequest.handle
----io.seata.rm.AbstractRMHandler.handle(io.seata.core.protocol.transaction.BranchCommitRequest)
-----io.seata.core.exception.AbstractExceptionHandler.exceptionHandleTemplate
------io.seata.rm.AbstractRMHandler.doBranchCommit
-------io.seata.rm.datasource.DataSourceManager.branchCommit
--------io.seata.rm.datasource.AsyncWorker.branchCommit
---------io.seata.rm.datasource.AsyncWorker.addToCommitQueue # 把要提交的分支事务信息加到一个队列,有定时任务消费该队列。

2.7. rm端定时检查已完成的分支事务的队列,清理undo_log

io.seata.rm.datasource.AsyncWorker.AsyncWorker # 定时调用doBranchCommitSafely,1秒1次
-doBranchCommitSafely
--io.seata.rm.datasource.AsyncWorker.doBranchCommit
---io.seata.rm.datasource.AsyncWorker.dealWithGroupedContexts
----io.seata.rm.datasource.AsyncWorker.deleteUndoLog
-----io.seata.rm.datasource.undo.AbstractUndoLogManager.batchDeleteUndoLog # 清理undo_log

3. 问题

3.1. 为什么带注解@GlobalTransactional的方法能开启分布式事务?

  1. io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary方法扫描带@GlobalTransactional注解的方法,生成代理。
  2. 代理方法的拦截器是io.seata.spring.annotation.GlobalTransactionalInterceptor,invoke方法调用handleGlobalTransaction方法,调用TransactionalTemplate.execute
  3. TransactionalTemplate.execute,在该方法中可以看到
  • beginTransaction(txInfo, tx) # tm向tc申请开启事务
  • rs = business.execute(); # 执行业务代码
  • commitTransaction(tx); # 提交事务
  • completeTransactionAfterThrowing(txInfo, tx, ex); # 如果出现异常,执行回滚

3.2. tm开启事务,xid是怎样向下游组件传递的?

调用下游组件时,tm把xid放到了请求中。常见的有http系列和dubbo系列。

3.2.1. http系列

tm把xid放到http-header中,header_name为TX_XID。下游组件从header中获取。
spring-cloud-starter-alibaba-seata.jar下面有三个包:feign、rest、web

  • feign对应使用openfeign的tm。
    com.alibaba.cloud.seata.feign.SeataFeignClient#getModifyRequest方法中,把xid放到了http_header

  • rest对应使用RestTemplate的tm。
    com.alibaba.cloud.seata.rest.SeataRestTemplateInterceptor#intercept方法中,把xid放到了http_header

  • web对应rm
    com.alibaba.cloud.seata.web.SeataHandlerInterceptor#preHandle方法中
    ,获取http_header TX_XID。

3.2.2. dubbo系列

实现了dubbo框架的Filter,获取上游的xid,给下游发送xid,有apache和alibaba两个实现:

  • ApacheDubboTransactionPropagationFilter
  • AlibabaDubboTransactionPropagationFilter

3.3. rm的数据库操作为什么能产生undo_log?

seata对DataSource、Connection、Statement、PreparedStatement做了代理。

  • 使用io.seata.rm.datasource.SeataDataSourceProxy代理DataSource,getConnection方法返回的是io.seata.rm.datasource.AbstractConnectionProxy
  • AbstractConnectionProxy#createStatement()返回的是io.seata.rm.datasource.StatementProxy
  • AbstractConnectionProxy#prepareStatement()返回的是io.seata.rm.datasource.PreparedStatementProxy
    seata-spring-boot-starter
  • StatementProxy和PreparedStatementProxy执行sql时,调用io.seata.rm.datasource.exec.ExecuteTemplate#execute,该方法内部根据数据库操作生成undo_log

3.4. tc、tm、rm三个角色是什么运行的?

  • tc角色运行于seata-server,一般会部署一套seata-server集群。
  • tm和rm角色运行于业务组件。

3.5. tm、rm是怎样启动的?怎样和tc通信?

业务组件使用jar包seata-spring-boot-starter.jar装配rm和rm。
也可以不使用seata-spring-boot-starter.jar,自己手工配制seata。但不推荐新手这样做,容易出现疑难杂症。

  • io.seata.spring.boot.autoconfigure.SeataAutoConfiguration#globalTransactionScanner构造一个GlobalTransactionScanner。

  • GlobalTransactionScanner#initClient方法调用TMClient.init构造tm,调用RMClient.init构造rm。

  • tm是一个基于netty的tcp client,它从注册中心获取seata-server列表,跟所有的seata-server都建立连接,并保持心跳。它注册了一组io.seata.core.rpc.processor.RemotingProcessor用于处理tc发来的消息。

    ClientOnResponseProcessor、ClientHeartbeatProcessor

  • rm跟tm差不多,也是基于netty的tcp client,只是它注册的是另一组RemotingProcessor。

    RmBranchCommitProcessor、RmBranchRollbackProcessor、RmUndoLogProcessor、ClientOnResponseProcessor、ClientHeartbeatProcessor

  • tc是一个基于netty的tcp server,它监听指定的端口,处理tm和rm的连接。它也注册了一组RemotingProcessor。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容