2022年5月22日 作者 zeroheart

seata记录

【纯悦享版】spring cloud alibaba之seata从入门到源码讲解!分布式事务、分布式缓存、分布式锁、微服务网约车项目实战!_哔哩哔哩_bilibili

分布式事务解决方案–Seata源码解析 – Lucky帅小武 – 博客园 (cnblogs.com)

1.写隔离

需要拿到全局锁才能执行

2.读隔离,默认是读未提交,如果要读已提交,则是通过select for update来获取全局锁。

3.整体流程

4.流程

从配置文件入手,使用Seata时需要配置两个bean分别是DataSourceProxy和GlobalTransactionScanner,配置如下:

<bean id="accountDataSourceProxy" class="io.seata.rm.datasource.DataSourceProxy">
        <constructor-arg ref="accountDataSource" />
</bean>

<bean class="io.seata.spring.annotation.GlobalTransactionScanner">
         <constructor-arg value="dubbo-demo-account-service"/>
         <constructor-arg value="my_test_tx_group"/>
</bean>

从字面以上看DataSourceProxy意思就是数据源的代理,构造函数中需要传入需要被代理的数据源,比如阿里的DruidDataSource;GlobalTransactionScanner意思是全局事务扫描器,那么应该就是用来扫描项目中配置的所有全局事务。所以了解Seata就需要从这两个类开始着手。

5.2.1、初始化过程
1、启动Seata Server,Seata Server是一个Netty服务器,默认监听2181端口

2、应用项目启动,通过配置的GlobalTransacationScanner对象,在初始化该bean的时候执行init方法,分别初始化TM客户端和RM客户端,分别是TmNettyRemotingClient和RmNettyRemotingClient对象

3、TM客户端和RM客户端和TC服务器保持通信,并通过心跳机制保持有效长连接

4、RM初始化的时候会将自己管理的资源信息注册给TC服务器,也就是负责的数据源信息,TC采用Map<Channel,RpcContext>存储TM和RM的客户端信息

5、客户端和服务器初始化时都会注册监听的事件编号并绑定对应的事件处理器

6、通过代理模式分别给数据源DataSource创建代理对象DataSourceProxy,获取的Connection对象为代理对象ConnectionProxy

5.2.2、开启事务流程
1、通过GlobalTransactionScanner给被@GlobalTransactional注解修饰的方法创建动态代理,创建了一个方法拦截器GlobalTransactionInterceptor对象

2、被@GlobalTransactional注解修饰的方法执行时走方法拦截器GlobalTransactionalInterceptor对象的invoke方法处理增强逻辑

3、根据事务传播机制选择是否走事务,如果走事务就开启全局事务,由当前的TM向TC发送开启全局事务的请求

4、TC接收开启事务请求,根据配置决定采用哪种持久化方式,可以采用Redis、DB、File等方式持久化全局事务,并返回全局事务ID

5、TM获取到全局事务ID,存入上下文RootContext中,本质是通过ThreadLocal存储

6、TM对应业务方调用分布式服务将XID传递过去,Dubbo采用RpcContext、HTTP采用Header的方式

7、RM对应业务方被调用,获取XID,先注册RM到TC,然后执行本地事务并提交,并且记录undoLog到数据库,

8、RM将本地事务提交结果通过brancheReport上报给TC

9、TC接收分支上报结果,先更新全局事务和分支事务状态,然后进行持久化

5.2.3、提交事务流程
1、如果业务执行成功,TM创建全局事务提交请求并发送给TC

2、TC接收全局事务提交请求,更新持久化的全局事务状态

3、TC有个定时任务,发送删除UndoLog的请求给所有RM用于清理指定时间段内的UndoLog

4、RM接收到删除UndoLog请求,删除指定时间段内的UndoLog,默认是删除超过7天的

5.2.4、回滚事务流程
1、如果业务执行失败,TM创建全局事务回滚请求并发送给TC

2、TC接收全局事务回滚请求,更新持久化的全局事务状态

3、TC根据全局事务获取全部分支事务,遍历通知分支事务回滚事务,仅通知分支事务提交成功的,对于分支事务提交失败的直接删除;

4、分支事务从数据库查询undoLog日志,并执行undoLog日志中的命令进行回滚操作

5、分支事务回滚成功返回分支回滚状态给TC服务器

6、回滚成功则TC删除分支事务,

7、如果回滚失败则会将回滚任务加入队列重试回滚

5.细节

5.2.6、实现细节梳理
1、服务器注册TM和RM的逻辑处理?
业务系统启动时通过GlobalTransactionScanner初始化方法afterPropertiesSet,初始化TMClient和RMClient
实际是分别创建两个Netty客户端TmNettyRemotingClient和RmNettyRemotingClient对象和TC服务器创建连接,并分别发送TM和RM注册的指令给TC
TC处理TM和RM注册的逻辑主要如下:
1.创建RpcContext对象封装TM客户端;
2.创建客户端Channel和RpcContext存储已经认证的<Channel,RpcContext> IDENTIFIED_CHANNELS集合中缓存;
3.客户端认证ID为 applicationId:clientIp
4.TC采用ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>> TM_CHANNELS 存储所有TM信息,key是客户端认证ID,value是端口号和RpcContext的关联信息
5.TC采用ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>>>> RM_CHANNELS 存储所有RM信息
存储RM信息的Map有多层,每一层的key依次为resourceId -> applicationId -> ip -> port -> RpcContext
6.所以TM和RM注册的过程实际就是在缓存集合中存储对应的RpcContext的过程

2、服务器注册全局事务的逻辑处理?
被@GlobalTransactional注解修饰的方法执行时会被方法拦截器拦截,会发起全局事务开启请求,获取服务器返回的XID,绑定到全局上下文RootContext中,
全局上下文RootContext采用key为TX_XID存储XID,实际就是采用ThreadLocal<Map<key,value>>形式存储
服务器接收TM发起全局事务请求之后处理逻辑如下:
1.创建GlobalSession对象表示全局事务
2.根据持久化配置选择redis、file、DB来存储全局事务信息
3.持久化全局事务信息包括XID、status、transactionId、transactionName、transactionServiceGroup、beginTime、applicationId等
4.XID=ipAddress:port:transactionId
5.返回全局事务开启结果,返回XID

3、服务器注册分支事务的逻辑处理?
1.服务器接收分支注册请求,先锁住全局事务会话GlobalSession
2.根据GlobalSession创建分支事务会话BranchSession
3.GlobalSession采用ArrayList存储branchSession
4.将分支事务信息存储到分支事务表中
5.返回分支事务注册结果并返回分支事务ID

4、分支事务上报状态的逻辑处理?
Seata采用DataSourceProxy代理DataSource,并通过ConnectionProxy代理Connection,并PreparedStatement也采用了代理PreparedStatementProxy,
分支执行数据库操作时从上下文RootContext获取XID,如果存在全局事务XID,则执行完SQL语句之后,执行ConnectionProxy的commit方法,commit方法逻辑如下:
1.ConnectionProxy会先注册分支事务,获取分支事务ID;
2.记录undoLog存储在数据库
3.然后提交本地事务
4.上报本地事务提交的状态
5.TC服务器更新分支事务上报的状态

5、undoLog的使用?
本地事务执行完成成会注册分支事务,并且本地记录undoLog,当全局事务提交成功,TC会采用定时任务通知RM删除undoLog,如果全局事务回滚,TC会通知RM进行本地事务回滚,本地事务从数据库中查询undoLog进行数据回滚
undoLog日志清理策略有三种:
1、TC服务器会创建定时任务每天通知一次所以RM删除7天以前的undoLog;
2、RM有定时任务1秒执行一次,删除所有二阶段提交成功的分支事务相关的undoLog
3、分支事务回滚后执行完undo逻辑之后会删除undoLog

6、TC如何存储全局事务信息?
TC可以采用redis、file、DB等方式存储全局事务信息

7、全局事务XID如何传递?
业务发起方发起全局事务获取全局事务XID,调用RM服务时,会通过请求将XID传递给服务提供者,服务提供者通过Filter或Interceptor获取XID并通过ThreadLocal存在本地的RootContext上下文中
如Dubbo采用RpcContext传递,HTTP请求采用Header传递

8、异常回滚的逻辑处理?
8.1、分支事务正常,业务系统异常导致回滚?

业务系统TM会发起全局事务回滚请求给TC,TC再遍历所有提交成功的分支事务,发生分支事务回滚通知

8.2、分支事务异常导致全局事务回滚?

分支事务异常会上报状态给TC,当TM发起全局事务回滚时,TC遍历所有提交成功的分支事务,发生分支事务回滚通知;执行失败的分支事务不需要再进行回滚;

8.3、全局事务回滚时,分支事务回滚失败如果解决?

分支事务有undoLog记录,当分支事务回滚失败时,TC会每秒重试通知一次回滚

8.4、全局事务回滚时,TC异常断开如何恢复?

TC重启之后会再次通知需要回滚的分支事务进行回滚

8.5、全局事务回滚时,分支事务相关数据已经被修改如何回滚?

当分支事务需要回滚时,如果此时数据以及被其他事务修改,那么此时就会回滚失败,提示当前数据和期望数据不一致。