- Seata-组件
- 2PC
- Seata-AT
- Seata-XA
- Seata-TCC
- Seata-SAGA:业务流(最终一致性,不保证事务的隔离)
- Seata-集成
Seata-组件
- 核心组件:TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围,开始全局事务、提交或回滚全局事务
- RM (Resource Manager) - 资源管理器 :管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
- 分布式事务的生命周期
- TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起
- RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联
- TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚
- TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚
- 使用
@GlobalTransactional
开启分布式事务(XA模式需要同时使用@Transcational
) - 使用
@GlobalLock
+select ... for update
实现全局事务的读隔离
2PC
- 2PC 两阶段提交
- TM通知各个RM准备提交它们的事务分支。如果RM判断自己进行的工作可以被提交,那就对工作内容进行持久化,再给TM肯定答复;要是发生了其他情况,那给TM的都是否定答复
- TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare失败的话,则TM通知所有RM回滚自己的事务分支
- ACID:两阶段提交方案下全局事务的ACID特性,是依赖于RM的。一个全局事务内部包含了多个独立的事务分支,这一组事务分支要么都成功,要么都失败。各个事务分支的ACID特性共同构成了全局事务的ACID特性。也就是将单个事务分支支持的ACID特性提升一个层次到分布式事务的范畴
- 2PC存在的问题
- 同步阻塞问题:2PC 中的参与者是阻塞的。在第一阶段收到请求后就会预先锁定资源,一直到 commit 后才会释放
- 单点故障:一旦协调者TM发生故障,参与者RM会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作
- 数据不一致:若协调者第二阶段发送提交请求时崩溃,可能部分参与者收到commit请求提交了事务,而另一部分参与者未收到commit请求而放弃事务,从而造成数据不一致的问题
Seata-AT
- AT(强一致性,适合并发量不高的场景):业务无侵入,是一种改进后的两阶段提交(一阶段提交释放资源,二阶段日志回滚补偿 - undo_log 表)
- 业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源(不会一直持有资源的锁)
- 对业务sql进行解析,转换成undolog,并同时入库
- 提交异步化,非常快速地完成;回滚通过一阶段的回滚日志进行反向补偿
- 分布式事务操作成功,则TC通知RM异步删除undolog
- 分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚
- 问题:如果事务进行期间,其他线程修改了这行数据,导致undo log中前置镜像的值和库表实际值不一致,就会导致回滚失败,其他事务也会被拒绝,必须人工介入修改库表数据后删除undo log
- 分布式事务操作成功,则TC通知RM异步删除undolog
- 业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源(不会一直持有资源的锁)
- TM 位于事务最顶层:下游服务如果处理了异常导致上有TM所在的服务无法捕获异常,就会导致事务失效的情况
- Feign的服务降级之后一定要重新抛出异常
Seata-XA
- XA(强一致性,适用于中间件场景):一直持有资源的锁,不需要undo_log表
- 配置方式:
seata.data‐source‐proxy‐mode=XA
- 从编程模型上,XA 模式与 AT 模式基本上完全一致,只是由于依赖本地事务(代理数据源-XADataSource),
@GlobalTransactional
必须配合@Transactional
使用,否则无效
- 配置方式:
- AT和XA模式数据源代理机制对比
Seata-TCC
- TCC(Try-Confirm-Cance):最终一致性,适合高并发事务场景,比如金融领域,不会一直持有资源的锁
- 侵入式的分布式事务解决方案(需要手动实现try, commit, cancel):完全不依赖数据库,能够实现跨数据库、跨应用资源管理
- SEATA的TCC模式就是手工的AT模式,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log
- 资源预留(Try)、确认操作(Confirm)、取消操作(Cancel)
- Try:对业务资源的检查并预留
- Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功
- Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放
- 一阶段 prepare 行为;二阶段 commit 或 rollback 行为
- try-commit:try 阶段首先进行预留资源,然后在 commit 阶段扣除资源
- try-cancel:try 阶段首先进行预留资源,预留资源时扣减库存失败导致全局事务回滚,在 cancel 阶段释放资源
- 空回滚、幂等、悬挂:Seata都已经封装好了,基于日志表 tcc_fence_log(对tcc的执行顺序进行日志记录,就可以识别这三个问题)
- 空回滚:try 因为网络问题没有调用成功,按理说不需要 cancel,但是 cancel 了
- 造成数据不一致:在try中扣减,在cancel增加,try没有执行,而cancel执行了
- 幂等: TC 重复进行二阶段提交,Confirm/Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放
- 由于网络问题,事务提交后没有响应到服务端,导致服务端发动重试机制,产生重复提交,重复释放同理
- 悬挂:try 方法被网络阻塞,导致空回滚,但是空回滚后 try 方法被成功执行了
- 空回滚:try 因为网络问题没有调用成功,按理说不需要 cancel,但是 cancel 了
- Seata解决方案:在tcc_fence_log表中记录状态status
tried:1 committed:2 rollbacked:3 suspended:4
- try 执行后是 tried
- 解决空回滚问题:当执行 cancel 时,检测到当前没有记录,就进行空回滚,并置为 suspended
- 解决悬挂问题:try 执行时检测到现在是 suspended 状态,就放弃执行
- 解决空回滚问题:当执行 cancel 时,检测到当前没有记录,就进行空回滚,并置为 suspended
- confirm 执行后是 committed;cancel 执行后是 rollbacked
- 解决幂等问题:当重复进行二阶段调用时,当前状态已经是二阶段状态了
- 通过检查目前事务的状态,可以轻松避免空回滚、幂等、悬挂的问题
- try 执行后是 tried
// @LocalTCC 适用于SpringCloud+Feign模式下的TCC,@LocalTCC一定需要注解在接口上
@LocalTCC
public interface OrderService {
/*
* 定义了分支事务的 resourceId,commit和 cancel 方法
* name = 该tcc的bean名称,全局唯一
* commitMethod = commit 为二阶段确认方法
* rollbackMethod = rollback 为二阶段取消方法
* BusinessActionContextParameter注解,传递参数到二阶段中
* useTCCFence:用于解决TCC幂等,悬挂,空回滚问题,需增加日志表tcc_fence_log
*/
@TwoPhaseBusinessAction(name = "prepareSaveOrder", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)
boolean deduct(@BusinessActionContextParameter(paramName = "commodityCode") String commodityCode, @BusinessActionContextParameter(paramName = "count") int count);
boolean commit(BusinessActionContext actionContext);
boolean rollback(BusinessActionContext actionContext);
}
Seata-集成
- db存储模式+Nacos(注册&配置中心)方式部署
- 将Seata Server注册到Nacos,修改conf/application.yml文件
- 确保client与server的注册处于同一个namespace和group,不然会找不到服务
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata‐server
server‐addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 7e838c12‐8554‐4231‐82d5‐6d93573ddf32
cluster: default
username:
password:
- 配置Nacos配置中心地址,修改conf/application.yml文件
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server‐addr: 127.0.0.1:8848
namespace: 7e838c12‐8554‐4231‐82d5‐6d93573ddf32
group: SEATA_GROUP
data‐id: seataServer.properties
username:
password:
- 上传 seataServer.properties 到 Nacos (设定db模式)
- 修改:https://github.com/apache/incubator-seata/blob/v1.5.1/script/config-center/config.txt
- seata是通过jdbc的executeBatch来批量插入全局锁的
- 连接参数中的rewriteBatchedStatements为true时,在执行executeBatch,并且操作类型为insert时,jdbc驱动会把对应的SQL优化成
insert into () values (), ()
的形式来提升批量插入的性能(插入性能为原来的10倍多)
- 连接参数中的rewriteBatchedStatements为true时,在执行executeBatch,并且操作类型为insert时,jdbc驱动会把对应的SQL优化成
store.mode=db
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
- 配置事务分组
seataServer.properties
里的service.vgroupMapping.default_tx_group=default
与 Seata Server 注册到 Nacos 的registry.nacos.cluster=default
必须保持一致- 要与client配置的事务分组一致
seata.tx-service-group=default_tx_group
(和service.vgroupMapping.
的后缀对应)
- 启动Seata Server
- seata‐server.sh ‐p 8091 ‐h 127.0.0.1 ‐m db
- -m 事务日志存储方式,支持file,db,redis,默认file
- -n 指定seata-server节点ID
- seata‐server.sh ‐p 8091 ‐h 127.0.0.1 ‐m db
- 事务分组如何找到后端Seata集群(TC)
- 客户端中配置了事务分组,SpringBoot 通过 seata.tx-service-group 配置
- 客户端会通过用户配置的配置中心去寻找 service.vgroupMapping .[事务分组配置项],取得配置项的值就是TC集群的名称,SpringBoot 通过
seata.service.vgroup-mapping.事务分组名=集群名称
配置 - 拿到集群名称程序通过一定的前后缀+集群名称去构造服务名
- 拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的TC服务列表