【JAVA核心知识】分布式事务框架Seata

Seata

基本信息

GitHub:https://github.com/seata/seatastars: 20.6k
最新版本: v1.6.1 Dec 22, 2022
官方文档:http://seata.io/zh-cn/index.html

注意

官方仅仅支持同步调用。 官方在FAQ中表示对于异步框架需要自行支持。 具体的扩展思路看查阅下文中的事务传播章节。

四种事务模式

阅读前置条件

需要理解XA规范, 或者2PC提交流程。(XA是一个规范,2PC是这个规范的具体体现,理念是一样的)

概念

TC
TM
RM

Seata TCC 模式

image.png
TCC即Try-Confirm-Cancel。针对每个操作都要编排一个与其对应的确认和补偿(撤销操作)。
TCC模式不依赖于底层数据资源的事务支持,依靠开发者自己编排的Try-Confirm-Cancel逻辑来进行全局事务数据维护,Seata仅用来用来管理全局事务,统筹分支事务状态,并决策执行Confirm Or Cancel(针对已注册到TC的分支事务)。
TCC的优势在于不会对资源有任何的锁定和占用,性能较高。缺点是需要自行编排逻辑,需要对业务流转比较清楚,与业务的耦合性较高。 (Seata的TCC如果使用Fence还是会有一定的资源占用)
Seata通过方法级别的注解@TwoPhaseBusinessAction来指定 Try-Confirm-Cancel。
@TwoPhaseBusinessAction(name = “beanName”, commitMethod = “commit”, rollbackMethod = “rollback”, useTCCFence = true)
注解所在方法即为Try, commitMethod指定Confirm, rollbackMethod指定Cancel。 name用来指定beanName,即这些方法所在的bean。 如果希望通过Seata解决TCC的幂等,空回滚,悬挂等问题,就设置useTCCFence为true,同时需要建表tcc_fence_log。 useTCCFence默认为false,即开发者自己通过逻辑编排解决幂等,空回滚,悬挂这些TCC的问题。
接下来,看一下useTCCFence为true时Seata是如何解决这些问题的:

幂等

在 commit/cancel 阶段,因为 TC 没有收到分支事务的响应,需要进行重试,这就要分支事务支持幂等。
分支事务提交时,如果useTCCFence为true则会走到TCCFenceHandler 类中的 commitFence 逻辑,首先会判断 tcc_fence_log 表中是否已经有记录,如果有记录,则判断事务执行状态并返回。这样如果判断到事务的状态已经是 STATUS_COMMITTED,就不会再次提交,保证了幂等。如果 tcc_fence_log 表中没有记录,则插入一条记录,供后面重试时判断。
Rollback 的逻辑跟 commit 类似,逻辑在类 TCCFenceHandler 的 rollbackFence 方法。

防空回滚

对于分布式事务来说,在try阶段,一般我们会调用多个服务。但是在全局事务回滚时,并非一定是所有的分支事务都已进行了提交。若分支事务未提交就因为全局事务失败产生了回滚,就会出现空回滚的情况, 即回滚了一个未提交的分支事务。
如图,账户服务的分支事务执行失败导致分布式事务回滚,此时账户服务的Cannel依然会被执行,就发生了空回滚现象。
image.png
面对这种情况,Seata的处理策略是在try阶段往tcc_fence_log表里面插入一条数据,status字段是STATUS_TRIED,在Rollback阶段判断是否存在,如果不存在,则不执行回滚操作。

防悬挂

所谓的事务悬挂就是指因为某些原因分支事务在全局事务回滚之后提交。
如图,分支事务的try阶段因为某些原因(如网络阻塞)阻塞,阻塞过程中全局事务发生了回滚。回滚结束后,分支事务被收到并执行,此时就发生了事务悬挂。
image.png
对于事务悬挂,Seata的TCC模式的处理策略是在Rollback时,首先判断tcc_fence_log 中是否存在当前分支事务xid的记录,如果不存在则插入一条记录,状态是STATUS_SUSPENDED,并且不再执行回滚操作。而分支事务的try的第一步也是向tcc_fence_log 表插入xid的记录,这样若后面分支事务产生悬挂现象,也会因为tcc_fence_log 表中已有xid的记录而造成主键冲突,分支事务无法执行。从而避免了事务悬挂。 另外Rollback时对tcc_fence_log 的查询是select for update。 所以也就不会说出现并发的问题。

脏读与脏写

需要注意,TCC除了幂等,空回滚,事务悬挂问题之外。不合理的业务编排还可能导致脏写和脏读的问题,因为try执行完成后数据库事务已提交,此时数据对于其它事务已处于可见状态。如果在业务编排时未考虑此场景,就可能出现其它事务读取只进行了try而未Confirm的数据,造成脏写或脏读。 因为在业务编排时我们一定要注意,非特殊情况,TCC修改的数据要在confirm阶段后才应该对其它业务可见,在confirm之前数据应该处于数据库可见,而业务不可见的状态。

SEATA Saga 模式

Seata XA 模式

使用事务资源对XA协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
image.png
如图所示:

  • 执行阶段:
    • 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
    • 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)
  • 完成阶段:
    • 分支提交:执行 XA 分支的 commit
    • 分支回滚:执行 XA 分支的 rollback

Seata XA 模式需要依赖于数据源的XA模式,因此数据源需要是支持XA 事务的数据库。
简单的说,XA模式在数据库事务启动时注册分支事务,数据库事务执行完成后并不提交,而是反馈给TC结果。TC根据分支事务的执行结果再通知分支事务(也就是数据库事务)提交或者回滚,之后数据库事务才算真正的结束。
可以看到XA模式在全局事务执行期间需要一直保持数据库事务为已执行未提交状态,需要长期占据RM已经数据库的本地锁即连接资源,并发度必然会受到影响。好处是不会出现各种事务隔离问题。
鉴于此,Seata提供了AT模式。

Seata AT 模式

脱胎于XA规范。两阶段提交:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。


如图AT模式下,每一个分支事务操作时,都先将其操作的目标数据查出来。并记录到undolog表中(注意这个是自建的undolog表,而不是mysql的undolog日志)。并对目标数据加全局锁。如果二阶段全局事务提交,则解开全局锁。如果二阶段全局事务回滚,则从undolog表里面查出修改前的数据,再进行目标数据更新操作,以进行反向补偿。
这样做的好处是在一阶段就释放事务资源的资源,无需长时间占据事务资源。缺点是需要进行对事务资源进行额外的数据资源操作,需要额外的负载。

写隔离

AT 模式通过全局锁来实现写隔离:

  • 一阶段本地事务提交前(注意是提交前哦,而不是事务开始时),需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

也就是说,AT模式在提交之前,都会对影响到的目标数据加一个全局锁,如果锁成功,则继续提交事务,如果获取锁失败,则需要等待全局锁。可以理解AT模式会对目标数据加一个分布式锁以此来保证同一数据同时只能有一个事务操作,从而解决了脏写问题。
如果还不理解写隔离,下面以两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000为例:
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁
image.png
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
image.png
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

如果数据库默认的事务隔离级别是读已提交(Read Committed)或以上, 那么Seata默认的分布式事务隔离级别是读未提交(Read Uncommitted) 。是无法避免脏读的。
因为AT模式在第一阶段是真真切切的提交了数据库事务的,而全局事务仅仅读是不加全局锁的,因此其它事务的读自然可以读到这个全局事务未提交,数据库事务已提交的数据。
若流程需要全局事务是读已提交(Read Committed), 则需要使用select for update。 Seata会对加了select for update的读操作加全局锁,直到获取成功才执行实际的查询。如图:
image.png
Seata没有使用默认的全局事务RC模式也是出于性能考虑。

脏读和脏写

可以看到上文提到的通过select for update实现读写隔离仅仅是在两个全局事务之间,如果一个流程是全局事务,一个流程非全局事务。Seata就无法实现全局事务的读写隔离了。毕竟AT模式的一阶段完成,数据库事务实际上是已经结束了,数据已经处于可见状态。
但是有的事务确实就是一个简单的事务,而全局事务相较于简单的数数据库事务要重的多。如果为了实现全局的读写隔离,就都加入全局事务,那性能必然会受更大的影响。因此Seata推出了@GlobalLock注解。 @GlobalLock简化了rpc过程,使其做到更高的性能。当然select for update依然还是需要的。
更多的关于Seata事务隔离的信息可以见官方文档: Seata事务隔离

脏事务

需要注意的是,如果一个表存在全局事务和非全局事务混用(无论是逻辑更新还是说你直接用sql更新了数据),就可能导致脏事务。Seata在事务失败回滚时会确认undolog中的镜像数据是否和当前表中的一致,如果不一致,内部会抛出一个SQLUndoDirtyException异常(内部异常,不会抛到外部)并终止回滚流程(源码见io.seata.rm.datasource.DataSourceManager)。此时就形成了脏事务。
脏数据需手动处理,根据日志提示修正数据或者将对应undo删除(可自定义实现FailureHandler做邮件通知或其他)。
Seata还支持关闭回滚时undo镜像校验,当然该方案是很不推荐的。

防悬挂

事务悬挂定义在上文中的TCC模式中已经提过,这里不再赘述。
AT模式下产生事务悬挂的场景是:分支事务a注册TC后,a的本地事务提交前发生了全局事务回滚,此时就会导致全局事务回滚成功,而a资源被占用掉,产生了资源悬挂问题。
Seata AT模式的防悬挂措施是a回滚时发现回滚undo还未插入,则插入一条log_status=1的undo记录,a本地事务(业务写操作sql和对应undo为一个本地事务)提交时会因为undo表唯一索引冲突而提交失败。

使用

环境基础

  • 需要搭建Seate节点
  • 需要注册中心,支持:
    • eureka
    • consul
    • nacos
    • etcd
    • zookeeper 注:官方文档仅有此目录,无实际内容
    • sofa 注:官方文档无此目录
    • redis 注:官方文档无此目录
    • file (直连)
  • 支持配置中心:
    • nacos
    • consul
    • apollo
    • etcd
    • zookeeper 注:官方文档仅有此目录,无实际内容
    • file (读本地文件, 包含conf、properties、yml配置文件的支持)

server端

配置信息

必要配置#store.mode=db需要以下配置#store.mode=redis 需要以下配置
registry.type–注册中心类型,默认filestore.db.driverClassNamestore.redis.host
config.type–配置中心类型,默认filestore.db.urlstore.redis.port
store.mode–事务会话信息存储方式store.db.userstore.redis.database
store.db.passwordstore.redis.password

事务信息存储模式

  • file: 仅适用于单机模式。全局事务会话信息内存中读写并持久化本地文件root.data,性能较高
  • db: 适用于高可用集群模式。需要一个数据库实例,全局事务会话信息通过db共享,相应性能差些;
  • redis: 适用于高可用集群模式。需要一个redis实例, 性能较高,缺点是存在数据丢失风险(无持久化的宕机)

更多配置见seata参数配置

部署

  1. 下载部署包。 下载地址https://github.com/seata/seata/releases
  2. 建表(仅store.mode为db时需要),共需要三个表。global_table、branch_table、lock_table。 分别对应全局事务会话信息的3块内容:全局事务–>分支事务–>全局锁。官方建表语句地址: https://github.com/seata/seata/tree/master/script/server/db
  3. 配置与启动
    • 若计划使用启动包命令
      • 修改store.mode,相关配置在seata–>conf–>application.yml,修改store.mode=“db或者redis”
      • 修改数据库连接|redis属性配置。seata–>conf–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。
      • 使用命令 seata-server.sh -h 127.0.0.1 -p 8091 -m db 启动,命令参数为
    • 若计划使用源码启动,则配置
      • 修改store.mode,相关配置在根目录–>seata-server–>resources–>application.yml,修改store.mode=“db或者redis”
      • 修改数据库连接|redis属性配置。根目录–>seata-server–>resources–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。
      • 执行ServerApplication.java的main方法启动
    • 若使用Docker部署
      • docker部署官方文档
-h: 注册到注册中心的ip
-p: Server rpc 监听端口
-m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)
-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
-e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html

注: 官方建议堆内存分配2G,堆外内存1G

client端

集成

三种集成方式, 分表对应不依赖于spring boot, 依赖于spring boot, 依赖于spring cloud。 根据自己的项目选择一个即可。

依赖seata-all
  • 引入依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>${seata.version}</version>
</dependency>
  • 建表(AT模式或TCC模式需要)AT模式官方建表语句地址:https://github.com/seata/seata/tree/master/script/client/at/db如果TCC模式为true即通过Seata解决TCC的幂等,空回滚,悬挂问题则需要建表: tcc_fence_log注意这些表和Server端的表不一样,这个表要在所有你纳入全局事务涉及的库里面都建一份。
  • 数据源代理(仅AT模式和XA模式需要)
    • 自动代理,需使用spring的数据源自动配置若采用AT模式。启动类上使用@EnableAutoDataSourceProxy注解。若采用XA模式,则注解加上参数@EnableAutoDataSourceProxy(dataSourceProxyMode = “XA”)
    • 手动代理
@Primary
@Bean("dataSource")
public DataSource dataSource(DataSource druidDataSource) {
    // 以下两个二选一
    
    //AT 代理 
    return new DataSourceProxy(druidDataSource);
    
    //XA 代理
    return new DataSourceProxyXA(druidDataSource)
}
  • 初始化GlobalTransactionScanner
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
   String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
   String txServiceGroup = this.seataProperties.getTxServiceGroup();
   if (StringUtils.isEmpty(txServiceGroup)) {
       txServiceGroup = applicationName + "-fescar-service-group";
       this.seataProperties.setTxServiceGroup(txServiceGroup);
   }

   return new GlobalTransactionScanner(applicationName, txServiceGroup);
}
依赖seata-spring-boot-starter
  • 引入依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>${seata.version}</version>
</dependency>
  • 建表(仅AT模式需要)官方建表语句地址:https://github.com/seata/seata/tree/master/script/client/at/db注意这个表和Server端的表不一样,这个表要在所有你纳入全局事务涉及的库里面都建一份。
  • 数据源代理(仅AT模式和XA模式需要)
    • 自动代理,需使用spring的数据源自动配置若使用AT模式,无需配置,若使用XA模式则需要设置参数seata.data-source-proxy-mode=XA
    • 手动代理首先关闭自动代理seata.enable-auto-data-source-proxy=false 然后进行手动代理
@Primary
@Bean("dataSource")
public DataSource dataSource(DataSource druidDataSource) {
    // 以下两个二选一
    
    //AT 代理 
    return new DataSourceProxy(druidDataSource);
    
    //XA 代理
    return new DataSourceProxyXA(druidDataSource)
}
依赖spring-cloud-alibaba-seata
  • 引入依赖
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency
  • 建表(仅AT模式需要)同依赖seata-spring-boot-starter
  • 数据源代理(仅AT模式和XA模式需要)同依赖seata-spring-boot-starter

参数配置

配置项描述备注
registry.type注册中心
config.type配置中心
service.vgroupMapping.my_test_tx_group事务群组
(相关概念见链接)my_test_tx_group为分组,配置项值为TC集群名
service.default.grouplistTC服务列表仅注册中心为file时使用
service.disableGlobalTransaction全局事务开关默认false。false为开启,true为关闭

更多配置见seata参数配置

事务传播

目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,需要引用 spring-cloud-alibaba-seata。如果符合以上条件,则这一步骤不需要。
如果想了解原理,或者是使用的其他自研框架、异步模型、消息消费事务模型,则需要进行这一步结合 API 自行支持。
对于异步模型的个人见解: 异步模型可以考虑使用CountdownLaunch,需要TC决定提交全局事务前通知TC还有新的分支事务。
在了解如果配置事务传播之前,要先明白Seata 的事务上下文。

事务上下文

Seata 的事务上下文由RootContext 来管理。
在开启一个Seate全局事务后,RootContext 会自动绑定该事务的XID,事务结束后(提交或回滚完成),RootContext 会自动解绑 XID。

// 绑定 XID
RootContext.bind(xid);

// 解绑 XID
String xid = RootContext.unbind();

可以通过RootContext获取当前全局事务的XID,或者判定当前是否在全局事务中。

// 获取 XID
String xid = RootContext.getXID();

// 当前是否在全局事务中
boolean inGlobalTransaction = RootContext.inGlobalTransaction();

通过简单的查看源码就可以看到,RootContext的实现是依赖于ThreadLocal的。
根据官方文档描述:Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。 -这一句很重要,理解这一句话做适配的事务传播开发才更得心应手。翻译过来就是所谓的事务传播就是通过RootContext.bind 将不同模块绑定同一个XID的过程。

服务内部的事务传播

默认的,RootContext 的实现是基于 ThreadLocal 的,即 XID 绑定在当前线程上下文中。所以服务内部的 XID 传播通常是天然的通过同一个线程的调用链路串连起来的。默认不做任何处理,事务的上下文就是传播下去的。相关代码可以看源码: io.seata.core.context.ThreadLocalContextCore
同时,可以通过RootContext来挂起事务。比如希望某一段流程运行在全局事务外。

// 挂起(暂停)
String xid = RootContext.unbind();

// TODO: 运行在全局事务外的业务逻辑

// 恢复全局事务上下文
RootContext.bind(xid);
跨服务调用的事务传播

通过上述基本原理,可以得出:
跨服务调用场景下的事务传播,本质上就是要把 XID 通过服务调用传递到服务提供方,并绑定到 RootContext 中去。
只要能做到这点,理论上 Seata 可以支持任意的微服务框架。
Dubbo对全局事务的支持
这里以内置的对Dubbo的事务传播支持机制为例,来说明如何实现一个RPC框架对全局事务传播行为的支持。
_org.apache.dubbo.rpc.Filter_是Dubbo提供的拦截器,其功能类似web的filter。这里通过他来完成XID的自动装配与绑定。

/**
 * The type Transaction propagation filter.
 */
@Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100)
public class TransactionPropagationFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String xid = RootContext.getXID(); // 获取当前事务 XID
        String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); // 获取 RPC 调用传递过来的 XID
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
        }
        boolean bind = false;
        if (xid != null) { // Consumer:把 XID 置入 RPC 的 attachment 中
            RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
        } else {
            if (rpcXid != null) { // Provider:把 RPC 调用传递来的 XID 绑定到当前运行时
                RootContext.bind(rpcXid);
                bind = true;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("bind[" + rpcXid + "] to RootContext");
                }
            }
        }
        try {
            return invoker.invoke(invocation); // 业务方法的调用

        } finally {
            if (bind) { // Provider:调用完成后,对 XID 的清理
                String unbindXid = RootContext.unbind();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("unbind[" + unbindXid + "] from RootContext");
                }
                if (!rpcXid.equalsIgnoreCase(unbindXid)) {
                    LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);
                    if (unbindXid != null) { // 调用过程有新的事务上下文开启,则不能清除
                        RootContext.bind(unbindXid);
                        LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
                    }
                }
            }
        }
    }
}

消费方和提供方都加入这个拦截器就可以进行Dubbo调用的全局事务自动传播了。
更多的事务传播示例可以查看seata-samples或者 seata-integration。

使用

开启全局事务
  • 注解方式使用@GlobalTransactional注解即可。
@GetMapping(value = "testCommit")
@GlobalTransactional
public Object testCommit(@RequestParam(name = "id",defaultValue = "1") Integer id,
    @RequestParam(name = "sum", defaultValue = "1") Integer sum) {
    Boolean ok = productService.reduceStock(id, sum);
    if (ok) {
        LocalDateTime now = LocalDateTime.now();
        Orders orders = new Orders();
        orders.setCreateTime(now);
        orders.setProductId(id);
        orders.setReplaceTime(now);
        orders.setSum(sum);
        orderService.save(orders);
        return "ok";
    } else {
        return "fail";
    }
}
  • 切点模式
@Bean
public AspectTransactionalInterceptor aspectTransactionalInterceptor () {
    return new AspectTransactionalInterceptor();
}

@Bean
public Advisor txAdviceAdvisor(AspectTransactionalInterceptor aspectTransactionalInterceptor ) {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("配置切点表达式使全局事务拦截器生效");
    return new DefaultPointcutAdvisor(pointcut, aspectTransactionalInterceptor);
}
AT和XA模式开启全局事务即可,不需要其它额外的操作。
TCC 模式

Tcc模式除了需要开启全局事务之外,还需要使用**@TwoPhaseBusinessAction**注解来定义try,commit,cancel。此注解定义在方法上。

// @LocalTCC标识这是一个本地的TCC,仅在TCC参与者是 本地bean时需要
@LocalTCC
public interface TccAction {
    /**
     * 定义两阶段提交 name = 该tcc的bean名称,全局唯一 commitMethod = commit 为二阶段确认方法 rollbackMethod = rollback 为二阶段取消方法
     * useTCCFence=true 为开启防悬挂
     * BusinessActionContextParameter注解 传递参数到二阶段中
     *
     * @param params  -入参
     * @return String
     */
    @TwoPhaseBusinessAction(name = "beanName", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)
    public void insert(@BusinessActionContextParameter(paramName = "params") Map<String, String> params) {
        logger.info("此处可以预留资源,或者利用tcc的特点,与AT混用,二阶段时利用一阶段在此处存放的消息,通过二阶段发出,比如redis,mq等操作");
    }

    /**
     * 确认方法、可以另命名,但要保证与commitMethod一致 context可以传递try方法的参数
     *
     * @param context 上下文
     * @return boolean
     */
    public void commit(BusinessActionContext context) {
        logger.info("预留资源真正处理,或者发出mq消息和redis入库");
    }

    /**
     * 二阶段取消方法
     *
     * @param context 上下文
     * @return boolean
     */
    public void rollback(BusinessActionContext context) {
        logger.info("预留资源释放,或清除一阶段准备让二阶段提交时发出的消息缓存");
    }
}

@LocalTCC
@LocalTCC注解标记这是一个本地的TCC。仅在TCC参与者是 本地bean(非远程RPC服务)时需要在 接口定义中添加 该 注解。如果这是一个远程RPC服务的定义接口,则不需要添加这个这个注解。目前TCC支持 Loacl, Dubbo, HSF, Sofa。

@GlobalTransactional和@Transactional

在AT模式,@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务。@Transactional和@GlobalTransactional连用,@Transactional 只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。

全局事务的rollback

和@Transactional一样,全局事务的回滚需要捕获到异常。因此如果要回滚,异常一定要抛到最外层。如果异常在调用的过程中被吞掉的话,即使失败也是不会回滚的。 如果不想在调用中抛大量的异常。可以通过返回错误码,然后最外层根据错误码抛异常即可。
参考文档:https://zhuanlan.zhihu.com/p/561308610
https://juejin.cn/post/6911183702790209549
http://seata.io/zh-cn/docs/overview/what-is-seata.html

LCN

GitHub:https://github.com/codingapi/tx-lcnstars: 4k
最新版本: 5.0.2.RELEASE Feb 23, 2019
社区比较不活跃

PS:
【JAVA核心知识】系列导航 [持续更新中…]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/277841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Maven】<scope>provided</scope>

在Maven中&#xff0c;“provided”是一个常用的依赖范围&#xff0c;它表示某个依赖项在编译和测试阶段是必需的&#xff0c;但在运行时则由外部环境提供&#xff0c;不需要包含在最终的项目包中。下面是对Maven scope “provided”的详细解释&#xff1a; 编译和测试阶段可用…

关于2024年度PMI认证考试计划的通知

尊敬的考生&#xff1a; 经PMI和中国国际人才交流基金会研究决定&#xff0c;2024年度中国大陆地区计划举办四次PMI认证考试&#xff0c;3月、6月、8月、11月各举办一次&#xff0c;具体考试日期另行公布。如遇特殊情况需变更考试计划的&#xff0c;将提前另行通知。 PMI&#…

Ubuntu安装K8S(1.28版本,基于containrd)

原文网址&#xff1a;Ubuntu安装K8S(1.28版本&#xff0c;基于containrd&#xff09;-CSDN博客 简介 本文介绍Ubuntu安装K8S的方法。 官网文档&#xff1a;这里 1.安装K8S 1.让apt支持SSL传输 sudo apt-get update sudo apt-get -y install apt-transport-https ca-certi…

超声波清洗机实测!北柏、希亦、洁盟超声波清洗机哪款清洁强?

眼镜清洗其实是一件非常重要的事情&#xff0c;一定不要忽视&#xff0c;表面看眼镜已经清洗干净了&#xff0c;实际眼镜缝隙中的杂污渍还没清洁到位&#xff0c;时间一长就会非常容易滋生细菌以及长螨虫&#xff01;为了杜绝这种情况发生&#xff0c;大家务必重视起清洗眼镜&a…

SpringBoot2.7.12整合Knife4j

SpringBoot2.7.12整合Knife4j 是什么 Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案 添加依赖 <!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意--> <dependency><groupId>com.github.xiaoymin</groupId>&l…

Zookeeper-Zookeeper特性与节点数据类型详解

1.Zookeeper介绍 ZooKeeper 是一个开源的分布式协调框架&#xff0c;是Apache Hadoop 的一个子项目&#xff0c;主要用来解决分布式集群中应用系统的一致性问题。Zookeeper 的设计目标是将那些复杂目容易出错的分布式一致性服务封装起来&#xff0c;构成一高效可靠的原语集&…

如何本地搭建FastDFS文件服务器并实现远程访问【内网穿透】

文章目录 前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx 2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.…

交换两个数字的三种方法-LeetCode做题总结 344

344. 反转字符串 题解Java知识点交换两个数字的三种方法1、temp2、异或3、 题解 class Solution {public void reverseString(char[] s) {char temp;for(int i0,js.length-1; i<j; i,j--) {temp s[i];s[i] s[j];s[j] temp;}} }Java知识点 交换两个数字的三种方法 1、t…

Flutter BottomSheet 拖动分两段展示

第一段 第二段 实现思路 通过 GestureDetector 的 Drag 方法&#xff0c;动态改变Dialog的高度&#xff0c;通过设置一个最大高度和最小高度分成两层进行展示 实现 常用的展示BottomSheet的方法为 showModalBottomSheet /// 设置最高最好以高度的比例进行设置&#xff0c;方…

3、Git分支操作与团队协作

Git分支操作 1.什么是分支2. 分支的好处3. 分支的操作3.1 查看分支3.2 创建分支3.3 切换分支3.4 修改分支3.5 合并分支3.6 产生和解决冲突 4. 创建分支和切换分支图解5. Git团队协作机制团队内协作跨团队协作 均在git bash中进行操作。事先建好本地工作库 1.什么是分支 在版本…

使用Robot Framework实现多平台自动化测试

基于Robot Framework、Jenkins、Appium、Selenium、Requests、AutoIt等开源框架和技术&#xff0c;成功打造了通用自动化测试持续集成管理平台&#xff08;以下简称“平台”&#xff09;&#xff0c;显著提高了测试质量和测试用例的执行效率。 01、设计目标 平台通用且支持不同…

3D展2D数学原理

今年早些时候&#xff0c;我为 MAKE 杂志写了一篇教程&#xff0c;介绍如何制作视频游戏角色的毛绒动物。 该技术采用给定的角色 3D 模型及其纹理&#xff0c;并以编程方式生成缝纫图案。 虽然我已经编写了一般摘要并将源代码上传到 GitHub&#xff0c;但我在这里编写了对使这一…

【强化学习】基于蒙特卡洛MC与时序差分TD的简易21点游戏应用

1. 本文将强化学习方法&#xff08;MC、Sarsa、Q learning&#xff09;应用于“S21点的简单纸牌游戏”。 类似于Sutton和Barto的21点游戏示例&#xff0c;但请注意&#xff0c;纸牌游戏的规则是不同且非标准的。 2. 为方便描述&#xff0c;过程使用代码截图&#xff0c;文末附链…

三天吃透Spring面试八股文

目录&#xff1a; Spring的优点Spring 用到了哪些设计模式&#xff1f;什么是AOP&#xff1f;AOP有哪些实现方式&#xff1f;Spring AOP的实现原理JDK动态代理和CGLIB动态代理的区别&#xff1f;Spring AOP相关术语Spring通知有哪些类型&#xff1f;什么是IOC&#xff1f;IOC的…

Pytorch简介

1.1 Pytorch的历史 PyTorch是一个由Facebook的人工智能研究团队开发的开源深度学习框架。在2016年发布后&#xff0c;PyTorch很快就因其易用性、灵活性和强大的功能而在科研社区中广受欢迎。下面我们将详细介绍PyTorch的发展历程。 在2016年&#xff0c;Facebook的AI研究团队…

SpringBoot 3.2.0 基于Spring Security+JWT实现动态鉴权

依赖版本 JDK 17 Spring Boot 3.2.0 Spring Security 6.2.0 工程源码&#xff1a;Gitee 为了能够不需要额外配置就能启动项目&#xff0c;看到配置效果。用例采用模拟数据&#xff0c;可自行修改为对应的ORM操作 编写Spring Security基础配置 导入依赖 <properties>&l…

(已解决)(pytorch指定了gpu但还是占用了一点0号gpu)以及错误(cuDNN error: CUDNN_STATUS_INTERNAL_ERROR)

文章目录 错误原因解决问题 错误原因 出现错误cuDNN error: CUDNN_STATUS_INTERNAL_ERROR&#xff0c;从这个名字就可以看出&#xff0c;出错原因其实有可能有很多种&#xff0c;我这里说一种比较常见的&#xff0c;就是&#xff1a;显存不足。 一个困惑点在于&#xff0c;在…

为什么 export 导出一个字面量会报错而使用 export default 不会报错

核心 其实总的来说就是 export 导出的是变量的句柄&#xff08;或者说符号绑定、近似于 C 语言里面的指针&#xff0c;C里面的变量别名&#xff09;&#xff0c;而 export default 导出的是变量的值。 需要注意的是&#xff1a;模块里面的内容只能在模块内部修改&#xff0c;…

联合办公行业即将走向寒冬?如何重拾创业者信心

近年来&#xff0c;联合办公行业固然经历了迅猛发展&#xff0c;但现在似乎遭遇了一个潜在的拐点。面对经济的下行压力&#xff0c;一些人士担忧联合办公行业可能会步入寒冬。就在这个关键时刻&#xff0c;如何重拾创业者的信心成为行业内急需解决的问题。 首先要认识到的是&am…

c语言-位操作符练习题

文章目录 前言一、n&(n-1)的运用场景(n为整数)二、&1 和 >>的应用场景总结 前言 本篇文章介绍利用c语言的位操作符解决一些练习题&#xff0c;目的是掌握各个位操作符的使用和应用场景。 表1.1为c语言中的位操作符 操作符含义&按位与|按位或^按位异或~按位…