Seata官网: Seata 官网
设计核心点
Seata中涉及三个角色
TC: 事务协调者,分布式事务的核心实现,Seata把它单独成一个服务器了
TM: 事务开启者,用来发起全局事务的,(其实也可以充当TC,只不过Seata进行隔离了)
RM: 资源管理器,每个单独事务的参与者
Seata支持多种模式
强一致性的模式
AT:也是Seata首推的模式,通过undo_log日志配合本地事务
XA: 利用数据库本身支持的分布式事务处理
最终一致性模式:
TCC:通过自己开发资源锁定,提交,回滚等接口
Saga: 长事务
全局事务和本地事务的区别
本地事务:一般都是数据库保证的,例如Mysql
全局事务:当有分布式事务时出现的一个概念,保证多个本地事务的正确执行
当有全局事务时,会有一个全局的xid标识,然后每个本地事务作为一个分支事务会有bid与唯一的xid进行绑定和关联
xid的处理
每个分支事务都需要持有xid的数据,seata是基于以下几步做处理的
1,首先,TM事务先开启一个全局事务,然后获得一个Xid
2,获取Xid后当前的RM会把它放到 RootContext.bind(xid)中,其实就是一个ThreadLocal
3. 当调用其他微服务的时候通过SeataRestTemplateInterceptor拦截器拿取RootContext.getXID()获取xid然后往后面的微服务进行传输(该类实现了ClientHttpRequestInterceptor扩展点)
4. 其他微服务从SeataHandlerInterceptor中获取xid,然后又把它设置到自己的ThreadLocal中了,这样一来就实现了xid的往后传输了(该类实现了HandlerInterceptor扩展点
以上便是seata对于xid扩展点的设计
AT模式的设计
AT模式也是seata首推的模式,它通过undo_log日志和层层代理来对分布式事务进行处理,以下是它具体的流程
- 我们使用GlobalTransactional注解标记方法
- seata扫描该注解进行代理调用,有一个全局的GlobalTransaction接口,默认的实现类为DefaultGlobalTransaction
- 然后通过代理调用GlobalTransaction的begin开启全局事务方法,该方法会想TC发起一个开启全局事务的请求,然后TC返回一个xid
- 接着当前微服务按上面的流程处理xid
- 然后所有的微服务在调用commit提供本地事务时会被seata提供的ConnectionProxy进行代理,在调用commit方法,seata会先判断当前事务是否是全局事务(判断ThreadLocal中有没有XID数据),如果是的话会根据sql语句进行前置镜像的保存,然后执行sql,接着保存后置镜像,然后会向TC注册一个分支事务,如果本地事务提交失败还会发送一个报告给TC
- 如果每个分支事务都执行成功了那么TC后台会有个定时器查询全局事务的分布事务,如果全部都提交成功的话,那么就会循环所有分支事务然后由TC调用RM的branchCommit方法,如果有一个失败的话就会调用branchRollback方法
- 以上便是大概的交互流程,如果要看详细的话可能要去看源码才知道了
AT模式的优势
- 不需要持有connection的连接了,会提升数据库的性能
- TC单节点问题可以通过部署多台来解决
- 分支事务的二阶段提交有了相应的校验保证能执行完成
Seata的全局锁
分支事务在注册的时候是需要在TC那边获取一把全局锁的,TC的全局锁其实就是往数据表插入一个xidkey的字段,该字段是唯一性,同时只能有一个全局事务持有
全局锁的作用
写隔离:因为AT模式下第一阶段本地事务其实已经提交了,所以本地事务读到的数据已经是更改后的了,如果此时有其他全局事务读到了该数据,实际上是等于读到了未提交的数据,那么就有可能会出现脏读的问题,所以seata才会设计全局锁来解决这个问题,此时如果有其他全局事务要去更改这个数据,它实际上是拿不到锁的,所以就会阻塞在那里,直到上面的全局事务执行完成才能获取到数据
读隔离:用 for update 配合 GlobalLock注解seata帮我们实现了读隔离,当一个方法标记了GlobalLock后,那么有另一个GlobalLock方法要去读同一个数据,也会被阻塞那里,等到前面持有锁的线程释放锁以后就会获取到了
总结
seata核心的要求就是TC,还有就是对于分布式事务的一个设计方案,然后就是seata大量使用了代理来处理逻辑,这个也是比较值得我们借鉴的,另一个值得借鉴的就是使用ThreadLocal加上其他扩展点实现了xid的传输
对于我自己来说也是这样,如何把学到的知识用文字表达出来也是比较考验我自己的,以后也会慢慢加强这方面的能力