《Spring Cloud学习笔记:分布式事务Seata》

解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题

在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。 

1. 初识Seata

Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

Seata 可能给是目前已知最可靠的分布式事务解决方案。

官网地址:Seata | Seata,其中的文档、播客中提供了大量的使用说明、源码分析。

Seata 中封装了4种分布式事务模式,分别是 AT 模式、TCC 模式、Sega 模式 和 XA 模式。 

2. Seata的架构

其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼此的执行状态,因此解决分布式事务的思想非常简单:

  • 就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败接口,大多数的分布式事务框架都是基于这个理论来实现的。 
  • Seata的开发者坚定认为:一个分布式事务是由若干个本地事务组成的。

Seata也不例外,在Seata的事务管理中有三个重要的角色:

  • TC(Transaction Coordinator)- 事务协调者:维护全局和分支事务的状态,协调驱动全局事务提交或回滚。
  • TM(Transaction Manager)- 事务管理器:定义全局事务的范围、开启一个全局事务、提交或回滚一个全局事务。
  • RM(Resource Manager)- 资源管理器{事务参与者}:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚 => 执行每个事务分支的操作。

作为一个分布式事务,它肯定也会有一个入口方法,在这个入口方法当中,一定会去调用多个其它的微服务,每调一个微服务,这个微服务不就是一个分支事务,因此将来调了多少个微服务,将来我们这个全局事务就包含多少个分支事务,因此在这个入口方法里就定义了全局事务的范围了,而TM就会去监控这个入口的方法或者说是代理这个入口方法,这样一来,TM就知道了全局事务里面总共有多少个分支事务,整个范围就确定下来了,当入口方法被执行时,TM会首先拦截当前的这个执行,会去向TC发起一个请求,去注册全局事务,接下来,就可以去执行这个入口的业务逻辑了,去调用每一个微服务,到了微服务里面每个分支事务就要开始执行了,此时RM就排上用场了。。。 

Seata的工作架构如图所示: 

其中,TC 为单独部署的 Server 服务端,TC服务则是事务协调中心,是一个独立的微服务,是一个独立的JVM进程,里面不包含任何业务代码,需要单独部署;TM RM 为嵌入到应用中的 Client 客户端,引入到参与事务的微服务依赖中即可,将来TMRM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。 

在 Seata 中,一个分布式事务的生命周期如下: 

  • TM 请求 TC 开启一个全局事务,TC 会生成一个 XID 作为该全局事务的编号。

XID - 全局事务ID,会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。 

  • RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。
  • TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
  • TC 驱动 RM 将 XID 对应的自己的本地事务进行提交还是回滚。

微服务集成Seata

  • 参与分布式事务的每一个微服务都需要集成Seata,为了方便各个微服务集成Seata,我们需要把Seata配置共享到Nacos,因此模块不仅仅要引入Seata依赖,还要引入Nacos依赖。

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>
  <!--sentinel-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  </dependency>
  • 由于TC服务将来有可能搞集群,所以将来微服务肯定是要去注册中心获取TC服务地址。 

将原来的@Transactional注解改为Seata提供的@GlobalTransactional注解@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。 

Seata是如何解决分布式事务的? 

Seata基于上述架构提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模型,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

无论哪种方案,都离不开TC,也就是事务的协调者。 

3. XA模式(性能最差):强一致性的事务

  • XA规范是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持。 

XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。  

  • 两阶段提交协议(2PC)和三阶段提交协议(3PC)是一种用于实现强一致性的分布式事务协议。

两阶段提交 - Two-phaseCommit - 2PC - 两阶段提交协议

第一阶段 - Prepare准备阶段或预提交阶段:执行各个本地事务,即执行分支事/业务sql,但不提交

  • 在准备阶段,事务管理器首先通知所有资源管理器开启事务,接着TC事务协调者通知每个RM事务参与者执行本地事务
  • 本地事务执行完成后报告事务执行状态给TC事务协调者,此时事务不提交,继续持有数据库锁

二阶段 - Commit或Rollback提交阶段:提交各个本地事务

TC事务协调者基于一阶段的报告来判断下一步操作:

  • 如果一阶段都成功,则通知所有RM事务参与者,提交事务
  • 如果一阶段任意一个参与者失败,则通知所有RM事务参与者fallback回滚事务

Seata的XA模型

  • Seata对原始的XA模式做了简单的封装和改造(大差不差),以适应自己的事务模型,基本架构如图:

XA模式的优缺点? 

XA模式的优点:

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点:2PC存在的问题

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差;
  • 依赖关系型数据库实现事务
  • 同步阻塞问题 / 性能问题: 各个事务在未提交时属于阻塞状态,其它事务想要操作行数据只能等待,使分布式事务出现高延迟和性能低下
  • 网络分区导致的数据不一致问题:例如A、B两个本地事务,二阶段,也就是提交阶段时,当事务管理器向参与者发送提交事务的请求之后,一个提交成功,一个由于网络异常等原因导致事务提交失败,那此时就会导致整个系统出现数据不一致的情况,解决方案:可设置统一的提交或者回滚,或者感知到异常做兜底业务处理
  • 单点故障问题:各个本地事务的提交或回滚全部由XA的Server端控制,Server端一旦宕机各个本地事务都会处于阻塞状态,解决方案:做集群,但集群只能保证重新选出新的Server端控制者,本地事务阻塞的问题是无法解决的
  • 由两阶段提交可能会出现的各个问题衍生出三阶段提交

三阶段提交 - 3PC - 三阶段提交协议

  • 一阶段:测试网络通信,并且询问各个本地事务是否进行事务操作
  • 二阶段:执行各个本地事务操作,但不提交
  • 三阶段:提交各个本地事务,执行commit或者rollback 

由于三阶段提交协议3PC非常难实现,目前市面主流的分布式事务解决方案都是2PC协议。 

Seata实现XA模式

Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下: 

1. 修改每个参与事务的微服务的application.yml文件,开启XA模式:

seata:
  data-source-proxy-mode: XA #开启数据源代理的XA模式

2. 给发起全局事务的入口方法添加@GlobalTransactional注解

3. 重启服务并测试 

4. AT模式

  • AT模式同样也是分阶段提交的事务模型,不过却弥补了XA模型中资源锁定周期过长的缺陷。 
  • AT模式也是一种无侵入的分布式事务解决方案,阿里的Seata框架,实现了该模式。 

在AT模式下,用户只需关注自己的"业务SQL",用户的"业务SQL"作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。

一阶段:

  • 在一阶段,Seata(RM)会拦截"业务SQL",首先解析SQL语义,找到"业务SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image"原快照,然后执行"业务SQL"更新业务数据,在业务数据更新完成之后,再将其保存成"after image"新快照  =>  记录undo_log数据快照,最后生成行锁,以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性;
  • 各个本地事务申请全局锁,申请成功则提交本地事务  =>  提交异步化,非常快速的完成;

AT模式的脏写问题 

一阶段本地事务提交前,需要确保先拿到全局锁:

  • 拿不到全局锁,不能提交本地事务 => 这是为了解决在多线程并发访问AT模式的分布式事务时,有可能出现的脏写问题,先拿到全局锁,避免同一时刻有另外一个事务来操作当前数据

全局锁的作用?

  • 基于锁的原理,对于同一个行数据,一个时刻只能有一个分布式事务执行,提供互斥的作用

为什么全局锁不在本地事务开始时直接加锁?

  • 为了提高性能,减少全局锁对数据库的占用时间。 

全局锁带来的性能问题?

  • 因为有全局锁的存在,所有的分布式事务只能串行执行,不能并发的操作数据库当中的数据。

二阶段:

第二阶段根据阶段一的结果来判断 

二阶段提交:大多数情况下全局事务都是执行成功 

  • 如果每一个分支事务都成功,则二阶段提交  =>  二阶段如果是提交的话,因为"业务SQL"在一阶段已经提交至数据库,事务已经结束,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。 

二阶段回滚:回滚通过一阶段的回滚日志进行反向补偿 

  • 如果有任意分支事务失败,则二阶段回滚 => 二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的"业务SQL",还原业务数据,回滚方式便是用"before image"还原业务数据,根据快照恢复到更新前数据,然后删除快照;
  • 但在还原前要首先要数据校验 - 校验脏写,对比"数据库当前业务数据"和"after image",如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写(说明数据被当前全局之外的动作做了修改)=>  出现丢失更新,出现脏写就需要转人工处理。 
  • 分支事务提交后,遇到异常回滚时,其它的本地事务修改了数据,此时Seata会默认抛出异常

事务1在等事务2释放数据库锁,事务二在等事务一释放全局锁,它两互相等待,产生了死锁:
  • 在Seata内部,全局锁的重试它是有一个限制的,默认是30次,间隔10毫秒,也就是最多等待300毫秒,超出范围则放弃,并回滚本地事务,释放本地DB数据库锁。  

本地事务为什么在一阶段提交? 

  • 为了提高性能,AT模式是基于2PC的,但2PC的一阶段并不会真正提交本地事务,也就意味着一直持有资源不释放,性能较差;
  • 但AT模式在一阶段提交了本地事务,其它本地事务就可以执行,但全局锁还没有释放吗,同时还有undo_log image的记录,来保证后续有异常仍然能够进行事务回滚,提高性能。 

AT模式的隔离级别?

  • 读未提交,因为当一阶段本地事务提交之后,其它的分布式事务已经看到提交海购的数据,但因为有全局锁的存在,此时全局锁还没有释放,只能读到数据不能写数据。 

AT模式的优缺点: 

AT模式的优点: 

  • 一阶段直接完成事务的提交,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • AT模式的一阶段、二阶段提交和回滚均由Seata框架自动生成,用户只需编写"业务SQL",便能轻松接入分布式事务,AT模式是一种对业务无任何侵入的分布式事务解决方案  =>  没有代码侵入,框架自动完成回滚或提交。 
  • 可见,AT模式使用起来更加简单,无业务侵入,性能更好,因此企业90%的分布式事务都可以用AT模式来解决。 

AT模式的缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式要好很多 

5. AT模式与XA模式的区别? 

  • XA模式一阶段不提交事务,锁定资源;而AT模式一阶段直接提交,不锁定资源;
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚;
  • XA模式强一致,AT模式弱一致(最终一致);

6. TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复 

TCC模式,全称Try-Confirm-Cancel,通过名称也能看出来其流程主要有三个步骤 - 两个阶段,TCC实际上是服务化的两阶段提交协议:

  • 事务发起方在一阶段执行Try方法,在二阶段提交执行Confirm方法,二阶段回滚执行Cacel方法。    

TCC三个方法描述: 

  • 预处理Try:实现业务检查(一致性)和资源预留(隔离)=>  比如冻结金额
  • 确认 / 提交Confirm:做业务确认和提交操作  =>  要求Try成功Confirm一定要能成功
  • 撤销 / 回滚Cancel:Cancel阶段是在业务执行操作需要回滚的状态下执行分支事务的业务取消操作,预留资源释放,Cancel实现一个与Try相反的操作,即业务回滚操作

TCC执行流程: 

  • TM事务管理器首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM事务管理器将会发起所有分支事务的Cancel执行分支事务的业务取消操作,若try操作全部执行成功,TM事务管理器将会发起所有分支事务的Confirm操作,其中Confirm / Cancel操作若执行失败,TM会进行重试。 
  • 通常情况下,采用TCC则认为Confirm阶段是不会出错的,即:只要Try成功,Confirm一定成功,若Confirl阶段真的出错了,需要引入重试机制或人工处理。
  • 通常情况下,采用TCC则认为Cancel阶段也是一定成功的,在TCC中,Try和Confirm失败了都要调用Cancel,若Cancel阶段真的出错了,需引入重试机制或人工处理。
一般有以下几种处理手段:
  1. 记录日志&发送报警:将错误信息记录下来,方便后续分析和处理,并及时通知相关人员进行处理
  2. 自动重试:在一定程度上,可以通过自动重试的方式尝试多次执行Cancel操作,直到成功为止
  3. 人工干预:如果重试多次还是不成功,可以报警,然后进行人工干预,可以尝试手动执行Cancel擦偶哦或者进行数据修复等

分支事务成功的情况:

分支事务失败的情况 :

TM事务管理器  

  • TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当TM的角色,TM独立出来是为了成为公用组件,是为了考虑结构和软件复用。
  • TM事务管理器在发起全局事务时会生成全局事务记录,全局事务ID - XID会贯穿整个分布式事务调用链条,用户记录事务上下文、追踪和记录状态,由于Confirm和Cancel失败需要进行重试,因此需要将接口实现为幂等性,幂等性是指同一个操作无论请求多少次,其结果都相同。

TCC模式  VS  AT模式

  • 整个实现TCC的过程,实际上是没有用到全局锁的,这是和AT模式的一个大的区别,TCC模式更多是利用本地行锁或者乐观锁、状态区分的形式来实现资源隔离,另外,相比AT模式,也无需生成数据快照  =>  TCC模式比AT模式性能要高很多因为没有全局锁的限制,所以其速度飞快提升,因此TCC模式也适用于对性能有较高的分布式事务场景
  • TCC是基于业务层面的,三个阶段的操作都需要自己编写代码来实现,衍生出来的问题就是跨部门协调很麻烦
  • TCC模式需要自己实现代码,因此相比AT模式,TCC模式对业务代码有一定的侵入性
  • TCC模式无需依赖于数据库事务性,而是依赖补偿操作,因此可以用于非事务型数据库
  • TCC模式有软状态,事务是最终一致
  • TCC模式适用于对性能有较高要求的业务场景
  • TCC模式支持跨服务,而Seata的AT模式是不能跨服务的,但是因为TCC的二阶段代码都是我们自己实现的,所以跨服务跨平台都可以自主实现
  • TCC模式需要考虑Confirm、Cancel接口的幂等性设计

TCC 的实践经验 

蚂蚁金服TCC实践,总结以下注意事项:

➢ 业务模型分2阶段设计
➢ 并发控制
➢ 允许空回滚
➢ 防悬挂控制
➢ 幂等控制

1. TCC设计 - 业务阶段分2阶段设计

用户接入TCC,最重要的是考虑如何将自己的业务模型拆成两阶段来实现,实现TCC的三个方法:

  •  以"扣钱"的的场景为例,在接入TCC之前,对A账户的扣钱,只需要一条update更新账户余额的SQL语句便能完成;但是在接入TCC之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆分成两阶段,实现TCC的三个方法,并且保证一阶段Try成功的话,二阶段Confirm一定能成功。

2. TCC 设计 - 允许空回滚: 

  • 在没有调用TCC资源的Try方法的情况下,来调用二阶段的Cancel方法,因此Cancel方法需要识别出来这是一个空回滚,然后直接返回成功,让事务管理器TM认为已回滚,否则会不断重试,而Cancel又没有对应的业务数据可以进行回滚,可能导致Cancel一直失败,最终导致整个分布式事务失败。
  • 出现原因就是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。
  • 解决思路的关键就是要识别出来这个空回滚,思路很简单,就是需要知道一阶段是否执行,如果执行,那就是正常回滚;如果没执行,那就是空回滚,可以再额外增加一张分支事务记录表,其中有全局事务ID和分支事务ID,第一阶段Try方法里会插入一条记录,表示一阶段执行了,Cancel接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

3. TCC 设计 - 防悬挂控制(拒绝业务悬挂): 

  • 悬挂的意思是:Cancel比Try接口先执行
  • 对于已经空回滚的业务,按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,此时的Try接口不应该执行,但之前被阻塞的Try操作恢复,继续执行Try,就永远不可能Confirm或Cancel,事务一直处于中间状态,这就是业务悬挂。
  • 执行Try操作时,应当判断Cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的Try操作,避免悬挂  =>   防悬挂控制
  • 思路:在Cancel空回滚成功之前先记录该条事务的全局事务ID - XID或业务主键,标识这条记录已经回滚过,Try接口先检查这条事务的XID或业务主键,如果已经标记为回滚成功过,则不执行Try的业务操作。

4. TCC 设计 - 幂等控制: 

  • 所谓幂等就是操作一次和操作多次的执行效果是一样的。
  • 比如:我们的库存扣减操作,如果某一步操作报错,导致需要回滚重试,结果每次重试都会重复扣减库存,那这样肯定是不对的。
  • 所以为了保证TCC二阶段的Confirm、Cancel的提交重试机制不会引发数据的不一致,要求二阶段的Confirm和Cancel接口保证幂等,这样就不会使得我们的资源发生重复消耗,如果幂等控制没有做好,很有可能导致数据不一致等严重问题。

解决思路:在上述"分支事务记录"中增加执行状态,每次执行前都查询该状态,通过该状态字段来判断是否执行过。

声明TCC接口

  • TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明:@LocalTCC、@TwoPhaseBusinessAction。

7. Saga模式

Saga模式是Seata提供的长事务解决方案,由蚂蚁金服主要贡献,也分为两个阶段:

如图:T1~T3都是正向的业务流程,都对应着一个冲正逆向操作C1~C3
  • 一阶段:直接提交本地事务
  • 二阶段:成功则什么都不做;失败则通过编写反向补偿业务来执行逆向回滚操作 

Saga和TCC分布式事务的区别?

  • Sage和TCC有些类似,都是补偿型事务,与TCC不同的是,Saga不需要Try,而是直接进行Confirm、Cancel操作。

Saga模式的优缺点:

优势:

  • 一阶段直接提交本地事务,无锁,高性能 
  • 事件驱动架构,不会阻塞和等待,参与者可异步执行,高吞吐
  • 补偿服务即正向服务的"反向",易于理解,易于实现

缺点:

  • 软状态持续事件不确定,时效性差,属于最终一致性
  • 没有锁,不保证隔离性,没有事务隔离,会有脏写
  • Saga正向服务与补偿服务也需要业务开发者手动实现,因此也是有业务代码侵入的

8. 四种模式对比

我们从以下几个方面来对比四种实现:

  • 一致性:能否保证事务的一致性?强一致还是最终一致?

  • 隔离性:事务之间的隔离性如何?

  • 代码侵入:是否需要对业务代码改造?

  • 性能:有无性能损耗?

  • 场景:常见的业务场景

9. Seata的高可用异地容灾架构模型 

Seata的TC服务作为分布式事务核心,一定要保证集群的高可用性和异地容灾。

  • 搭建TC服务集群非常简单,启动多个TC服务,注册到Nacos注册中心即可。 

但集群并不能确保100%安全,万一集群所在机房故障怎么办?

  • 所以如果要求较高,一般都会做异地多机房容灾,比如一个TC集群在上海,另一个TC集群在杭州。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Cloud Alibaba Seata 是一款优秀的分布式事务解决方案,它可以帮助开发人员在分布式环境下处理复杂的事务操作。在分布式系统中,由于多个服务之间存在依赖关系,因此需要对服务之间的事务进行协调和管理,以确保数据的一致性和完整性。以下是对Spring Cloud Alibaba Seata处理分布式事务的需求分析: 1. 事务管理:在分布式系统中,需要对多个服务之间的事务进行管理和协调,以确保数据的一致性和完整性。Spring Cloud Alibaba Seata提供了全局事务管理功能,可以跨多个服务进行事务管理。 2. 分布式事务的隔离性:在分布式系统中,需要确保不同服务之间的事务操作是独立的,互相之间没有影响。Spring Cloud Alibaba Seata提供了分布式事务的隔离性功能,可以确保不同服务之间的事务操作是独立的。 3. 并发控制:在分布式系统中,由于多个服务之间存在依赖关系,因此可能会出现并发冲突的情况。Spring Cloud Alibaba Seata提供了并发控制功能,可以确保多个服务之间的并发操作不会冲突。 4. 事务回滚:在分布式系统中,如果某个服务的事务操作失败,需要对整个事务进行回滚。Spring Cloud Alibaba Seata提供了事务回滚功能,可以确保在分布式环境下的事务回滚操作是可靠的。 5. 可靠性:在分布式系统中,需要确保事务操作是可靠的,不会出现数据丢失或者数据不一致的情况。Spring Cloud Alibaba Seata提供了高可靠性的事务管理功能,可以确保分布式事务操作的可靠性和安全性。 综上所述,Spring Cloud Alibaba Seata是一款非常强大的分布式事务解决方案,可以帮助开发人员在分布式环境下处理复杂的事务操作,确保数据的一致性和完整性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Surpass余sheng军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值