分布式事务+ServiceCom Pack Saga介绍

分布式事务+ServiceCom Pack Saga介绍

理论

事务

事务,通常指数据库事务,包含了一些列对数据库的读/写操作。事务提供以下保障:1)为数据库操作序列提供了一个从失败中恢复到正常状态的方法,保证数据库异常时的数据一致性。2)当多个应用程序在并发访问数据库时提供一个隔离方法,以防止彼此的操作互相干扰。

随着中间件技术和的多样化,事务的概念延伸到了更广泛的范围, 如redis缓存事务, MQ的事务消息。在微服务架构的情况下,不同服务可能使用不同的技术栈,不同的存储机制等。因此事务可以使用更宽泛的概念: 针对目标对象或者资源提供事务性,而不仅是数据库。

图1 MQ事务消息交互图

注: redis事务只支持一定程度的原子性,事务命令及Lua脚本不支持异常回滚。 

ACID

  1. 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对资源的操作要么全部被执行,要么都不执行。
  2. 一致性(Consistency):事务应确保资源的状态从一个一致状态转变为另一个一致状态。一致状态的具体含义是数据库中的数据应满足完整性约束。
  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  4. 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

事务隔离级别及其应对问题

事务并发问题:

脏读:多事务并发时,读取到其他事务未提交的数据,这些数据并不是最终数据,可能会回滚。

不可重复度:在同一交易的不同时刻读取到的同一批数据不一致。

幻读:幻读和不可重复读都是指读取数据的不一致, 不可重复读是表示数据内容的不一致,幻读是指数据量的差异,比如第一次查询只有10条数据, 处理业务后再查询可能是少于或多于10条数据。

隔离级别:

Read Uncommited(读未提交):事务可以看到其他事务未提交的结果。

Read Committed(读取提交内容):事务只能看到已提交的事务的数据。

Repeatable Read(可重复读): 同一事务内对同一数据看到的内容是一致的。

Serializable(串行化): 强制事务串行。

隔离级别

脏读

不可重复读

幻读

Read Uncommited

Read Committed

X

Repeatable Read

X

X

Serializable

X

X

X

MySQL的默认隔离级别是: Repeatable, Mysql通过MVCC(Multi-Version Concurrency Control,多版本并发控制)来解决幻读问题, 原理是记录事务发生时的事务版本, 对于新事务版本产生的数据不进行读取,无锁并发方案。Mysql的Read Committed是通过共享锁实现。

Spring Transaction的默认隔离级别是:Isolation.DEFAULT,代表使用数据库的默认隔离级别。

MYSQL MVCC

当前读和快照读

当前读: 读取记录的的最新版本,并对记录加上排他锁保证其他并发事务无法修改当前记录。比如: select lock in share mode, select for update, insert/delete/update等

快照读:隔离级别不是串行级别下的读, 不加锁的非阻塞读。快照读就是基于MVCC实现。如普通Select。

MYSQL MVCC 实现原理

MVCC是解决读-写冲突的无锁并发控制,为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。

MYSQL MVCC实现主要包括:

  1. 记录的三个隐藏字段:DB_TRX_ID=最近更新这条记录的事务ID;DB_ROLL_PTR=指向回滚段的指针,行记录上是所有旧版本,在undo中通过链表的形式保存;DB_ROW_ID=单调自增的行标识。
  2. Undo 日记
  3. ReadView: 事务进行快照读时产生的读视图,当事务执行快照读时,系统维护事务创建时相关记录的当前事务ID。进行数据读取时检查记录的当前事物ID是否于快照时事务ID一致,如一致则读取,否则从undo日记的历史版本中读取指定版本数据。

事务的嵌套

事务的嵌套是指事务中存在子事务嵌套的场景。嵌套事务相对于平面事务由如下优势:1) 同一事务可以并发运行,提高事务内的并发度。 2)子事务可以独立提交和放弃,而父事务可以决定是否放弃子事务。

Spring 事务传播方式(Propagation):

说明

REQUIRED

如果当前存在事务,则加入该事务;否则,新建一个事务

SUPPORTS

支持当前事务,如果当前没有事务,则不使用事务

MANDATORY

强制使用当前事务,如果当前没有事务,则抛出异常

REQUIRES_NEW

如果当前存在事务,则挂起该事务,并开启一个新的事务,直到新事务完成;然后恢复之前的事务

NOT_SUPPORTED

以非事务方式运行,如果当前存在事务,则挂起该事务

NEVER

以非事务方式运行,如果当前存在事务,则抛出异常

NESTED

如果当前存在事务,则嵌套事务执行;否则和 REQUIRED 同样处理

CAP理论

CAP理论(CAP theorem):指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  1. 一致性(Consistency):所有节点访问同一份最新的数据副本.
  2. 可用性(Availability):每次请求都能获取到正确的响应——但是不保证获取的数据为最新数据)
  3. 分区容错性(Partition tolerance):分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项

分布式事务

分布式事务是指在分布式环境下执行的事务。

分布式事务有现存的管理标准: X/Open Distributed Transaction Processing(DTP) Model( X/Open XA,  https://en.wikipedia.org/wiki/X/Open_XA XA 使用两阶段提交协议。主要缺点是 两阶段提交协(2PC)是一个阻塞协议:其他服务器需要等待事务管理器发出关于是否提交或中止每个事务的决定。如果事务管理器在事务等待其出现异常,所有服务参与方将在持有数据库锁的状态下被堵塞,直到事务管理器再次恢复并发布命令。长时间持有锁可能会破坏使用相同数据库的其他应用程序的问题。每一个事务参与方的异常都会造成事务的整体回滚,降低了系统的可用性。

两阶段提交协议(2PC):两阶段提交由阶段1-投票阶段和阶段2-执行阶段组成;

投票阶段:协调者向所有参与者发送询问是否能够提交请求的请求。参与者根据自身业逻辑进行投票,如果投yes票,则在执行前保存所有对象,如果投No票, 参与者立即放弃事务。

执行阶段:协调者收集所有投票,如果所有投票是YES,则协调则向所有参与者发送doCommit请求,否则发送doAbort请求;参与者在收到请求后,如果是doCommit请求则提交,并发送haveCommitted消息确认。

图2 2PC协议流程简图

分布式事务的协调者:执行分布式事务请求的服务器需要在协调者的协调下执行事务动作。协调者需要为每一个事务生成一个唯一标识,通过唯一标识将事务的参与者关联起来。

SAGA、 TCC、 AT

XA模式两阶段模式中第一阶段的事务不进行提交,持续占用数据库的直到所有事务整体提交后才释放,性能较差,除了特殊极强要求一致性的场景下适用,其他场景无法满足实际需求。

针对一阶段的事务的性能延伸除了SAGA、TCC、AT等模式。

SAGA:第一本地事务直接提交,如果成功无需任何操作, 失败则进行业务补偿,事务间没有隔离性。

TCC: 真对SAGA的隔离性问题,讲事务拆分为try/confirm/cancel 三个接口;Try负责资源检查和预留; Confirm: 业务执行和提交; Cancel: 预留资源释放,回滚接口。

图3 TCC模式流程

AT:AT是从XA模式演进而来,两者都是非业务感知的分布式事务(TCC和SAGA则需通过业务来实现分布式事务),需要数据库的支持。 MySQL、Oracle、PostgreSQL和 TiDB等数据库都支持AT模式。整体机制:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。第二阶段异步化,通过回滚日记进行补偿。

https://imgconvert.csdnimg.cn/aHR0cDovLzViMDk4OGU1OTUyMjUuY2RuLnNvaHVjcy5jb20vaW1hZ2VzLzIwMTkwODI1Lzc0ZDFlNjNiNGU1YzQ2M2Q5NWQzMThlY2YzMzQzMDlhLmpwZWc?x-oss-process=image/format,png

图4 AT模式流程

图5 分布式事务模式比较

根据CAP理论, 分布式系统必须再AP和CP之间做出选择,XA协议是CP选择下的事务方案, 而AP选择下则是SAGA, 最终一致性。

TC(Transaction Coordinator): 事务协调者。管理全局事务的状态,调度分支事务的提交和回滚、TC指分布式事务框架提供的独立服务。

TM (Transaction Manager):事务管理者。用于开启、提交或回滚事务。

RM(Resource Manager): 资源管理器。分支事务的资源管理, 向TC注册分支事务,上报分支事务的状态,接受TC的命令对分支事务进行提交或回滚。

TM和RM通常是应用服务、一个是分布式事务的发起者、一个是分布式事务的参与者。

分布式事务框架

Seata: 支持AT、TCC、SAGA、XA模式, 阿里开源, 支持dubbo、springcloud

ByteTCC:支持TCC模式美团开源, 支持dubbo、springcloud,

Apache/Servicecomb-pack:支持TCC、SAGA, 华为开源, 支持dubbo、springcloud

https://github.com/apache/servicecomb-pack

Hmily:支持TCC模式,支持Dubbo、Spring Cloud,个人开源

SAGA

SAGA

1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇论文Sagas,讲述的是如何处理long lived transaction(长活事务)。

SAGA原理是将一个长事务拆解成多个可以独立运行的子是事务,每个子事务都是一个保持数据库原子事务的特性,在单个服务上进行事务操作。每个子事务包括一个事务Ti和一个补偿Ci。由的协调者编排子事务Ti和Ci的执行。

SAGA对事务的控制由协调者实现,对子事务的异常可以选择重试,或回滚之前成功事务的选择。

问题

隔离性

SAGA子事务在不同服务内都是一个完整的事务, 因此SAGA长事务之间缺乏隔离性,不同的的SAGA长事务对数据的影响都是相互可见的。

SAGA不具备隔离性,隔离性问题的解决职责从数据库转移到了应用服务上,需要根据实际的业务场景选择实施不同的资源隔离策略。如:悲观锁和乐观锁、更新版本(将更新进行版本化、可排序更新。)、交换式更新(将更新设计为可以按任何顺序执行)等策略。

回滚

SAGA相比TCC的缺点是缺少预留动作。以发送邮件为例,在TCC模式下,先保存草稿(Try)再发送(Confirm),撤销的话直接删除草稿(Cancel)就行了。而Saga则就直接发送邮件了(Ti),如果要撤销则得再发送一份邮件说明撤销(Ci)

实施SAGA时,服务的参与方已经在服务内完成了各自的事务, 无法自动回滚,业务程序需要精心设计自己的回滚补偿逻辑。

SAGA通讯和协调机制

《微服务架构设计模式》一书中对Saga实现进行了两点阐释:

  1. SAGA参与方和协调者之间应该通过消息代理进行通讯,提供解耦和保证完成的特性(消息中间件保证消息的传递性,及时接受者暂时不可用,服务恢复后能继续消费消息)

使用消息中间件是一项最佳实践关键,并不是必须的,但协作者之间,业务事务之间解耦独立进行是需要保证的。ServiceComb Pack Saga使用grpc作为Saga参与者和协调者的通讯机制。

  1. SAGA 协调模式

SAGA的协调模式是指如何协调事务参与者各自的工作。

SAGA协调逻辑有两种方式:

  1. 协同式: 把SAGA的决策和执行顺序逻辑分布在SAGA的每一个参与方中, 通过交换事件的方式来进行沟通。换句话说由参与者各自决定事务的走向,何时发送何消息给何目标。

ServiceComb Pack Saga的协作方式就是采用协同方式,只是参与方和事务之间的关系统一由Alpha进行管理, 相应的回滚补偿消息的推送也由Alpha负责发送到需要进行补偿的业务参与者。

  1. 编排式: 把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中,Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体操作。编排模式类似于部署模式中的服务编排,优点是对长事务有更清晰的管理,可以明确事务参与者之间的依赖关系,能保证业务事务的执行顺序和范围。 缺点就是需要编写专门的编排逻辑。

https://pics5.baidu.com/feed/7acb0a46f21fbe09cfd20461c2910a3a8644ad36.png@f_auto?token=eee43d93a64cf53f813e9c8839c744b4

图6 SAGA通讯示例

SAGA消息的一个流程示例:

1. Order服务在接收到订单时,设置APPROVAL_PENDING状态,创建一个Order并发布OrderCreated事件。

2. Consumer服务消费OrderCreated事件,验证消费者是否可以下订单,并发布ConsumerVerified事件。

3. Kitchen服务消费OrderCreated事件,验证订单,在CREATE_PENDING状态下创建出菜单,并发布TicketCreated事件。

4. Accounting服务消费TicketCreated和ConsumerVerified事件,向消费者的信用卡收费,并发布信用卡授权失败事件。

5. Kitchen服务使用信用卡授权失败事件并将出菜单的状态更改为REJECTED。

6. 订单服务消费信用卡授权失败事件,并将订单状态更改为已拒绝。

ServiceComb Pack Saga介绍

Apache ServiceComb Pack 是一个微服务应用的数据最终一致性解决方案。ServiceComb Pack支持Saga 以及TCC两种分布式事务协调协议实现。 本文只介绍Saga实现。

ServiceComb-pack github地址:https://github.com/apache/servicecomb-pack。

ServiceComb Pack 架构

图7 ServiceComb Pack架构

ServiceComb Pack Saga 主要组件包括:Omega(TM,TR)、Trasport和Alpha(TC)

Omega

Omega 是一个内嵌到服务类的Saga代理, 主要包含服务参与Saga的相关组件。

  1. 事务注解模块(Transaction Annotation) 以及 事务拦截器(Transaction Interceptor);
  2. 分布式事务执行相关的事务上下文(Transaction Context);
  3. 事务回调(Transaction Callback) ,事务执行器 (Transaction Executor);
  4. 负责与Alpha进行通讯的事务传输(Transaction Transport)模块。

Transport

Omega与Alpha之间的通讯协议实现,  使用GRPC。

Alpha

Alpha是Saga 中的协调者角色,负责工作包括:

  1. 通过事务传输(Transaction Transport)接收Omega上传的事件, 并将事件存在事件存储(Event Store)模块中。
  2. Alpha通过事件API (Event API)对外提供事件查询服务。
  3. Alpha会通过事件扫描器(Event Scanner)对分布式事务的执行事件信息进行扫描分析,识别超时的事务,并向Omega发送相关的指令来完成事务协调的工作。
  4. Alpha支持多实例对外提供高可用架构, Alpha集群管理器(Alpha Cluster Manger)负责管理Alpha集群实例之前的协调。管理终端(Manage console)提供对分布式事务的执行的监控。

图8 Alpha、Omega、和Service的关系。

Omega工作原理

图9 Omega工作原理

当服务收到请求时,omega拦截请求并提取请求信息中的全局事务ID作为其自身的全局事务ID,并生成本地的事务ID,本地事务预处理阶段,Omega向Alpha发送事务开始事件,后处理阶段Omega向Alpha记录事务结束事件。 每个成功的子事务都有一对对应的开始和结束事件。

事务内服务通讯

图10 ServiceComb Saga事务内服务之间通讯示例

在服务提供方,Omega会拦截请求头中事务上下文信息, 在消费方,Omega会往请求头中注入事务上下文信息。事务全局ID如同链路追踪的trace_id将事务的所有参与方串联起来,而事务本地ID将事务事件和参与者建立关系。

异常和超时

图11 异常补偿用例图

异常场景下omega会向alpha上报异常事件,alpha向该全局事务内其它已完成的子事务发送补偿指令。

图12 超时补偿用例图

Alpha定时扫描检测超时,中断超时的全局事务,alpha向该全局事务内其它已完成的子事务发送补偿指令。

ServiceComb Pack Saga Omega实现分析

Alpha 是一个SpringBoot应用,作为服务可以独立启动直接使用,可以无缝接入微服务服务注册中心作为一个独立服务注册供Omega客户端使用。

      

本节只分析Omega的实现细节。

ServiceComb Pack Saga的使用非常简单,具体示例见官方github的demo。

事务开启

服务在一个方法上添加@SagaStart开启Saga事务。Omega通过AOP机制实现对@SagaStart注解方法的拦截。切面定义类SagaStartAspect处理逻辑包括: 1. 初始化事务上下文:生成全局事务ID和本地事务ID。2. 调用sagaStartAnnotationProcessor进行前置处理。3. 执行本地方法。 4. 调用sagaStartAnnotationProcessor进行后置处理。

代码片段1: sagaStartAnnotationProcessor前置和后置处理

    AlphaResponse preIntercept(int timeout) {

        try {

            return this.sender.send(new SagaStartedEvent(this.omegaContext.globalTxId(), this.omegaContext.localTxId(), timeout));

        } catch (OmegaException var3) {

            throw new TransactionalException(var3.getMessage(), var3.getCause());

        }

    }

    void postIntercept(String parentTxId) {

        AlphaResponse response = this.sender.send(new SagaEndedEvent(this.omegaContext.globalTxId(), this.omegaContext.localTxId()));

        if (response.aborted()) {

            throw new OmegaException("transaction " + parentTxId + " is aborted");

        }

    }

事务参与

服务方法上添加@Compensable注解的方法被调用时将加入到长事务中。

@Compensable(compensationMethod = "xxxxRollback"): 该注解可以指定重试次数、超时时间、重试间隔和补偿方法。实现方式同样是使用AOP机制, 切面类为TransactionAspect。切面主要工作内容:1. 生成本地事务ID。2. 根据Compensable配置选择不同的策略来调用业务方法。策略包括:1)DefaultRecovery:调用CompensableInterceptor在执行业务方法前进行前置和后续处理,发送TxStartedEvent、TxEndedEvent或者TxAbortedEvent事件到Alpha.  2) ForwardRecovery:在DefaultRecovery基础上, 增加重试机制。

Cmopensable和CompensationMethod方法需保持入参一直。

节点通讯及OmegaContext传递实现

Omega代理和Alpha之间通过grpc接口调用, org.apache.servicecomb.pack:pack-contract-grpc 中定义了grpc接口描述和调用代理类。

Omega需要在服务间传递事务上下文:事务的Global事务ID和本地事务ID,实现方式:

  1. 服务消费者端往请求中注入事务上下文信息:

如果是Feign调用,则通过FeignClientRequestInterceptor 拦截器注入。

代码所在模块:org.apache.servicecomb.pack:omega-transport-feign

代码片段2 feign请求注入Omega上下文信息:

public class FeignClientRequestInterceptor implements RequestInterceptor {

   ……

    public void apply(RequestTemplate input) {

        if (this.omegaContext != null && this.omegaContext.globalTxId() != null) {

            input.header("X-Pack-Global-Transaction-Id", new String[]{this.omegaContext.globalTxId()});

            input.header("X-Pack-Local-Transaction-Id", new String[]{this.omegaContext.localTxId()});

            LOG.debug("Added {} {} and {} {} to request header", new Object[]{"X-Pack-Global-Transaction-Id", this.omegaContext.globalTxId(), "X-Pack-Local-Transaction-Id", this.omegaContext.localTxId()});

        } else {

            LOG.debug("Cannot inject transaction ID, as the OmegaContext is null or cannot get the globalTxId.");

        }

    }

}

       如使用resttemplate调用 ,则通过TransactionClientHttpRequestInterceptor 继承ClientHttpRequestInterceptor 在请求中注入。

       代码所在模块:org.apache.servicecomb.pack:omega-transport-resttemplate。

      

  1. 服务提供方:

TransactionHandlerInterceptor 拦截器在请求处理前将请求头中的omega上下文信息加载到OmegaContext中。(通过实现org.springframework.web.servlet.HandlerInterceptor接口拦截)

补偿方法执行

每一个子事务发送事务开始事件中包含补偿方法及子事务参数信息,由Alpha负责在需要进行补偿的时候向长事务内已完成的子事务节点发送补偿事件(包括补偿方法信息和参数)。

GrpcCompensateStreamObserver实现io.grpc.stub.StreamObserver接口,负责处理Alpha发送到Omega的报文, 补偿也是SAGA唯一的报文。执行逻辑在CompensationMessageHandler 中实现:调用注册的所有@compesable注解对象执行指定的补偿方法。

补偿执行上下文:记录所有参与子事务Bean和补偿方法。登记实现类CompensableAnnotationProcessor (实现 BeanPostProcessor接口)通过反射分析所有@Compensable的方法进行登记。

代码片段3 MethodCheckingCallback中进行补偿上下文注册:

Method signature = this.bean.getClass().getDeclaredMethod(each, method.getParameterTypes());

String key = this.getTargetBean(this.bean).

getClass().getDeclaredMethod(each, method.getParameterTypes()).toString();

this.callbackContext.addCallbackContext(key, signature, this.bean);

Omega和Alpha连接管理和重试

LoadBalanceContextBuilder负责建立负载均衡连接,sagaLoadBalanceSender在负载均衡上线文中,使用FastestSender来挑选最快的连接往Alpha发送事件, 具体流程如下:

  1. 初始时Omega获取集群alpha集群的所有服务器,同每一个节点建立通讯,加入连接池,每一个通道都设置值为0的标志表示空闲可用。
  2. 获取连接总是获取标志值最小,且不等于Long.MAX_VALUE的连接,并在处理完成后将标志值改为命令处理时间。如果无法从正常连接池遍历获取到可用连接, 则从异常恢复队列中获取重连的可用连接。(避免遍历错过的异常连接,而且本次不能获取到已恢复的连接而造成的连接获取异常)。
  3. 如果连接同Alpha通讯失败,则将改连接标志值设置为Long.MAX_VALUE。
  4. 连接异常处理任务会对连接进行重连,重连成功后的连接放入重连队列,同时将连接在正常连接池中的标志恢复为0。
  5. 获取连接发送失败后将尝试重新获取新的可用连接向Alpha发送命令。直到没有可用连接则返回SAGA通讯失败异常。

        

Alpha加入微服务集群后, Omega可以通服务注册中心获取所有的Alpha节点。通过引入不同模块支持不同的服务注册中心产品。包括:

omega-spring-cloud-consul-starter

omega-spring-cloud-eureka-starter

omega-spring-cloud-nacos-starter

omega-spring-cloud-zookeeper-starter

ServiceComb Pack Saga Hello world

  1. 安装NACOS(可选)
  2. 部署ALPHA

java -Dloader.path=./plugins -Dspring.datasource.url="jdbc:mysql://127.0.0.1:3306/saga?useSSL=false" -Dspring.datasource.username=root -Dspring.datasource.password=123456 -Dspring.cloud.nacos.discovery.enabled=true -Dspring.cloud.nacos.discovery.serverAddr=127.0.0.1:8848 -Dnacos.client.enabled=true -Dspring.profiles.active=mysql -jar alpha-server-0.8.0-SNAPSHOT-exec.jar

  1. 微服务应用端:
    1. POM 依赖

        <dependency>

            <groupId>org.apache.servicecomb.pack</groupId>

            <artifactId>omega-spring-starter</artifactId>

            <version>${pack.version}</version>

        </dependency>

        <dependency>

            <groupId>org.apache.servicecomb.pack</groupId>

            <artifactId>omega-spring-cloud-nacos-starter</artifactId>

            <version>${pack.version}</version>

        </dependency>

        <dependency>

            <groupId>org.apache.servicecomb.pack</groupId>

            <artifactId>omega-transport-resttemplate</artifactId>

            <version>${pack.version}</version>

        </dependency>

        <dependency>

            <groupId>org.apache.servicecomb.pack</groupId>

            <artifactId>omega-transport-feign</artifactId>

            <version>${pack.version}</version>

        </dependency>

    1. 应用配置

alpha:

  cluster:

    register:

      type: nacos

omega:

  spec:

    names: saga

    1. 应用代码

交易流程

  1. TransService 写入交易记录
  2. Transservice 调用accout服务,对消费账户-钱, 收款账户+钱。

 示例代码截图

相关表

txevent: Saga事件记录

txtimeout:Saga事务时间记录

tcc_global_tx_event: tcc模式全局事务事件表

tcc_participate_event: tcc模式参与事务事件表

tcc_tx_event: tcc模式事件记录表

command: alpha 命令处理表, 赔偿事件调用记录

Tcc模式

@TccStart

@Participate(confirmMethod = "confirm", cancelMethod = "cancel")

confirm和cancel方法的参数列表应该与@Participate方法一样,需是幂等。

omega:
  spec:
    names: tcc

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值