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, io.seata.rm.datasource.exec.StatementCallback, java.lang.Object...)
--io.seata.rm.datasource.exec.ExecuteTemplate.execute(java.util.List, io.seata.rm.datasource.StatementProxy, io.seata.rm.datasource.exec.StatementCallback, 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。

你可能感兴趣的:(seata原理解析)