Seata
2024-03-25 13:51:34 1 举报
AI智能生成
Seata一个不错的学习笔记:分布式事务、二阶段、AT模式、TCC模式、SAGA模式,Seata的rpc设计、Seata的TC设计
作者其他创作
大纲/内容
概念
事务的四种特性
原子性
一致性
隔离性
持久性
XA两阶段提交协议
XA两阶段函数
以xa_开头:<br>- xa_open() xa_close():建立/关闭与资源管理器事务的链接<br>- xa_start() xa_end() : 开始/结束一个本地事务<br>- xa_prepare() xa_commit() xa_rollback() : 预提交、提交、回滚一个本地事务<br>- xa_recover() 回滚一个已进行预提交的事务<br>以ax_开头:<br>- ax_reg() ax_unreg() 允许一个资源管理器在事务协调器中动态注册/撤销注册<br>
两阶段提交协议的执行过程
第一阶段:<br>1. 事务协调器通知参与该事务的所有资源管理器开始准备事务<br>2. 资源管理器接收到消息之后开始准备(写事务日志、执行事务,但是不提交),then将就绪的消息<br>返回给事务协调器。此时大部分事务已经完成,第二阶段耗时很短<br>第二阶段<br>1. 事务协调器接收到各个资源管理器回复消息之后,基于投票进行决策---提交/取消。如果有任意一个<br>资源管理器回复失败,则发送回滚的命令,否则发送提交的命令<br>2. 各个资源管理器在接收事务协调器的提交/回滚的请求之后,执行,并将执行结果返回给事务协调器<br>
两阶段提交协议的缺点
同步阻塞问题
单点故障
事务协调器发生故障
数据不一致
第二阶段处理中,如果发生了commit的异常、那么部分接到commit请求的资源管理器就会执行<br>commit请求,但是其他的资源管理器不会执行,所以此阶段会造成事务不一致问题<br>
状态不稳定
如果事务协调器发起commit后宕机、并且对应的资源管理器也宕机了<br>那么此时状态就没有办法回溯了<br>
CAP理论
BASE理论
基本可用
基本可用是对CAP中的A的一个妥协<br>分布式系统出现不可预知的故障时,允许损失部分可用性<br>比如高并发的时候的降级等<br>
软状态
允许系统中各个节点的数据处于中间状态
最终一致性
软状态的终态,各个节点的数据副本的最终一致性
TCC柔性事务
核心思想
尽早的对所操作的资源“加锁”,如果事务可以提交,则完成对预留资源的操作;<br>如果事务不可以提交,则对预留的资源进行回滚<br>
try:完成业务准备工作<br>confirm:操作阶段完成业务提交<br>cancel:操作阶段完成业务回滚<br>
基于消息的最终一致性
核心思想
简单来讲就是将分布式事务转换为两个本地事务<br>然后依靠下游的重试机制达到最终一致性<br>
缺点:对应用侵入性很强、应用需要进行大量的业务改造,成本较高
Seata原理
<br>
Seata支持4种事务模式:AT、TCC、Saga、XA
AT模式
为Seata主推的模式,对于业务无侵入,实现了业务与事务分离<br>只需要新增一个事务注解@GlobalTransactional<br>
TCC模式
开发者需要根据自己的具体的业务背景去实现,try、confirm、cancel的方法
Saga模式
补偿协议<br>- 如果所有的正向操作均执行成功,则分布式事务提交<br>如果任何一个正向操作执行失败,则分布式事务会退回去执行前面参与者的逆向回滚操作,<br>回滚已提交的参与者,使分布式事务会到初始状态<br>缺点:<br>在一阶段已经提交了本地数据库的事务,且没有进行“预留”动作,所以不能保证隔离性,不容易进行<br>并发控制。适用场景十分有限。<br>
XA模式
Seata AT模式
源码分析
事务日志表undo_log
在数据源代理DataSourceProxy拦截业务SQL之后,会生成包含before-image和<br>after-image信息的事务日志,并保存在事务日志标 (表名:undo_log、事务日志在Seata中称为undoLog)<br>核心重点字段:rollback_info字段,记录了回滚的数据信息,内部有前置镜像和后置镜像。<br>
afterImage中的字段数据
org.apache.seata.rm.datasource.sql.struct.TableRecords<br>表元数据、表名称、行集合<br>
org.apache.seata.sqlparser.struct.TableMeta<br>表名称、列元素映射、索引元数据映射<br>
org.apache.seata.sqlparser.struct.ColumnMeta<br>列名、数据类型、是否为空等.......<br>
org.apache.seata.sqlparser.struct.IndexMeta<br>索引名称、索引类型、是否唯一索引、包含的列<br>
元数据加载
1. 定义一个 缓存 大小100kb 失效时间900s【基于咖啡因 Caffeine】
<br>2. org.apache.seata.rm.datasource.sql.struct.cache.AbstractTableMetaCache#getTableMeta<br>方法获取元数据,如果cache中没有,则从数据库获取<br>
3. fetchSchema是一个抽象方法,从数据库获取元数据
org.apache.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache#resultSetMetaToSchema<br>在数据表变动不是很频繁的情况下,seata遵循读多写少用缓存的原则,<br>并通过定时任务的方式来保持拿到的数据表元数据是最新的。<br>
数据类型定义Class<br>java.sql.Types<br>
事务日志的处理逻辑一般都在UndoLogManager中
org.apache.seata.rm.datasource.undo.AbstractUndoLogManager#flushUndoLogs<br>保存事务日志方法<br>
org.apache.seata.rm.datasource.undo.AbstractUndoLogManager#undo<br>二阶段回滚方法<br>
org.apache.seata.rm.datasource.undo.AbstractUndoLogManager#deleteUndoLog<br>二阶段回滚处理的删除事务日志方法<br>
org.apache.seata.rm.datasource.undo.AbstractUndoLogManager#batchDeleteUndoLog<br>二阶段提交处理的批量删除事务日志方法<br>
Seata数据源代理
Seata对java.sql中的DataSource、Connection、Statement、PreparedStatement<br>四个接口进行了包装<br>DataSourceProxy、ConnectionProxy、StatementProxy、PreparedStatementProxy<br>
数据源代理类
具体实现org.apache.seata.rm.datasource.DataSourceProxy#DataSourceProxy(javax.sql.DataSource, java.lang.String)<br>init方法作用:<br>1. 判断使用的是什么数据库,比如mysql<br>2. 向资源管理器注册DataSourceProxy,之所以可以注册,是因为DataSourceProxy实现了Resource接口,注册的主要工作是找到与事务分组对应的TC集群,并与集群中的每台机器建立连接<br>3. 判断是否启动定时任务,定时任务的作用是缓存数据库表结构,表结构在RM保存数据快照的时候使用 org.apache.seata.rm.datasource.sql.struct.TableMetaCacheFactory.TableMetaRefreshHolder#TableMetaRefreshHolder<br>
资源管理器
ResourceManager
org.apache.seata.core.model.ResourceManager
ResourceManagerInbound
org.apache.seata.core.model.ResourceManagerInbound<br>主要是对内操作,接收TC发来的请求【提交分支事务、回滚分支事务】<br>
ResourceManagerOutbound
主要是对外的操作,发送请求到TC【注册分支事务、上报分支状态、查询全局锁】
资源注册
1. 获取分支事务类型<br>2. 获取资源管理器<br>3. 注册资源,通过RPC客户端注册资源,将当前的资源组ID和资源ID发给TC<br>
数据库连接代理
创建数据库代理
org.apache.seata.rm.datasource.DataSourceProxy#getConnection()<br>通过目标数据源创建连接代理<br>
本地事务提交
org.apache.seata.rm.datasource.ConnectionProxy#commit<br>1、锁冲突重试机制:AT模式中RM发送创建分支事务请求到服务端,服务端会对分支涉及的行进行加锁<br>为了防止多个分布式事务兵法的修改相同行而造成数据冲突。如果发生冲突,则RM会通过LockRetryPolicy的execute()方法进行重试<br>2、分支事务提交<br> a.注册分支事务 org.apache.seata.rm.datasource.ConnectionProxy#register<br> b.保存事务的日志 org.apache.seata.rm.datasource.undo.AbstractUndoLogManager#flushUndoLogs, 把全局事务ID、分支事务ID、rollback_info(BLOB形式)、状态等字段存入undo_log表中<br> c.本地事务提交<br> d.上报分支事务状态【TC需要根据上报的事务状态来决定二阶段的处理】<br>3、查询Seata全局锁【for支持“读未提交”以上的隔离级别】<br>AT模式中,如果两个操作,创建订单&占用库存。如果一个分支事务提交之后创建订单成功,正在执行占用库存操作。那么此时另一个分支事务直接能看到这个订单,如果另一个分支事务是轮训订单执行推送,那么此时就会出现第一个分支占用库存失败-事务回滚,但是仍然被推送的情况。<br>所以,不断扫描的那个推送事务就应该利用Seata的全局锁,来实现“读已提交”的隔离级别<br>private void processLocalCommitWithGlobalLocks() throws SQLException {<br> checkLock(context.buildLockKeys());<br> try {<br> targetConnection.commit();<br> } catch (Throwable ex) {<br> throw new SQLException(ex);<br> }<br> context.reset();<br> }<br>先进性检查seata的全局锁,在进行本地事务的提交 org.apache.seata.rm.datasource.DataSourceManager#lockQuery<br>
StatementProxy & PreparedStatementProxy
Statement和PreparedStatement的区别<br>
1. Statement用于执行静态的SQL语句,需要实现准备<br>2. PreparedStatement支持动态参数,比如 ?<br>3. PreparedStatement执行SQL,SQL会先被数据库解析和编译,然后放入命令缓冲区。执行同一个PreparedStatement对象,都会再次解析,但是不会再次编译,因为缓冲区有预编译的命令,并且可以重用<br>4. PreparedStatement减少编译次数,提高数据库性能<br>5. PreparedStatement有更好的安全性<br>
PreparedStatement相关关系图
执行模板类 ExcuteTemplete的execute方法<br>org.apache.seata.rm.datasource.exec.ExecuteTemplate<br>如果sql语句不在分布式事务中,并且也没有Seata全局锁的要求,则不需要将其纳入Seata框架下进行处理,用原始的Statement方法处理即可;<br>如果这个SQL语句在分布式事务中,则将其纳入Seata框架进行处理,并根据不同SQL语句类型选用不同的执行器来执行<br>
1.SQL识别器 SQL识别器是通过Druid SQL识别器工厂创建的<br>org.apache.seata.sqlparser.druid.DruidSQLRecognizerFactoryImpl<br>
2.SQL执行器org.apache.seata.rm.datasource.exec.BaseTransactionalExecutor#execute<br>
a、生成前镜像<br>org.apache.seata.rm.datasource.exec.UpdateExecutor#beforeImage<br>
b、执行原始SQL语句
c、生成后镜像<br>org.apache.seata.rm.datasource.exec.BaseInsertExecutor#afterImage<br>
d、准备事务日志<br>org.apache.seata.rm.datasource.exec.BaseTransactionalExecutor#prepareUndoLog<br>
AT模式两阶段提交
二阶段流程图
第一阶段
Seata AT模式一阶段<br>
通过数据源代理生成SQL识别器和SQL执行器,然后执行<br>1. 开启一个数据库本地事务<br>2. 查询前镜像<br>3. 执行SQL<br>4. 查询后镜像<br>5. 生成事务日志和事务锁数据<br>6. 注册分支事务<br> - 如果注册成功,则分支提交成功,执行后续<br> - 如果发生全局锁冲突,则回滚本地事务,在休眠一段时间之后,重新执行1-6<br>7. 提交本地事务<br>8. 向TC汇报分支状态<br>
第二阶段
提交
Seata AT模式二阶段 -- 提交
回滚
Seata AT模式二阶段 -- 回滚
脏写判断:<br>1. 如果当前值和后镜像相同,则无“脏写”,不回滚<br>2. 如果当前值和后镜像不同,但是和前镜像相同,则无“脏写”,值已经是我们预期<br>3. 如果当前值和前后镜像都不相同,则发生了“脏写”,会抛出异常<br>
注意⚠️:<br>由于TC在一阶段已经对AT模式分支事务生成的行锁数据进行了“加锁”,所以正常情况并不会出现脏写的情况。<br>出现“脏写”通常是有人绕过了Seata对数据进行了修改,比如通过SQL工具直接修改数据,这并不服务Seata AT的规则,需要人工排除<br>
AT模式处理步骤
1.TM【事务管理器】端使用`@GlobalTransaction`进行全局事务开启、提交、回滚<br>2.TM【事务管理器】开始RPC调用远程服务<br>3.RM【资源管理器】端`seata-client`通过扩展`DataSourceProxy`,实现自动生成`UNDO_LOG`与`TC`【事务协调器】上报<br>4.TM【事务管理器】告知TC【事务协调器】提交/回滚全局事务<br>5.TC【事务协调器】通知RM【事务管理器】各自执行`commit/rollback`操作,同时清除`undo_log`
背景举例
Seata TCC模式
概述
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。<br><br>
1. AT模式<br>AT模式依赖关系型数据库的本地事务能力<br>- 一阶段prepare:seata框架自动完成(在一个本地事务内完成)<br>- 二阶段commit:seata框架自动清理事务日志<br>- 二阶段rollback:seara框架自动完成事务的回滚<br>2. TCC模式<br>TCC模式不强依赖本地事务的能力<br>- 一阶段prepare:调用自定义的prepare逻辑<br>- 二阶段commit:调用自定义的commit逻辑<br>- 二阶段rollback:调用自定义的rollback逻辑<br><br>两种模式的差别:<br>在AT模式下seata把每一个数据库当作一个资源<br>在TCC模式下,Seata把每组TCC服务接口当作一个资源<br>
优势<br>TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。<br><br>缺点<br>TCC 是一种侵入式的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。<br><br>适用场景<br>TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
使用
Seata RPC设计
网络通信
Seata通过Netty实现作为RPC的底层通信
事务消息信息
事务消息信息分为3大类:<br>1. TM主动向TC发起<br>2. RM主动向TC发起<br>3. TC主动向RM发起<br>
Seata 事务协调器
Seata Server
0 条评论
下一页