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