分布式事务 知识点01

Seata模式

Seata关注的是微服务架构下的数据一致性问题,是整套的分布式事务解决方案。Seata框架包含两种模式:

  • AT模式,关注的是数据分片角度,关注DB访问的数据一致性,多服务下多DB数据访问的一致性
  • TCC模式,TCC模式主要是围绕业务拆分展开,当业务在横向扩展资源时,解决了服务之间调用的一致性,保证资源访问的事务性

AT模式

AT模式下会把每个DB当作一个Resource,数据库就是 DataSource Resource。业务通过标准的JDBC接口访问数据库资源,Seata框架会对所有请求进行拦截,做事务操作。

在每个事务提交时,Seata Resource Manager(RM 资源管理器)都会向Transaction Coorrdinator(TC 事务协调器)注册一个分支事务。

当请求链路调用完成后,发起方通知TC事务提交或者进行事务回滚,进入两阶段提交调用流程。

二阶段操作时,TC根据之前注册的分支事务回调对应参与者去执行对应资源的第二阶段。

每个资源都有全局唯一的资源ID,在初始化时用这个ID向TC注册,之后的事务协调过程中,TC就可以根据事务ID找到事务和资源的对应关系。事务协调过程中,每个事务的注册都会携带这个资源ID,这样TC可以通过资源ID在第二阶段调用时找到正确的资源了。

简单来说AT模式,就是把数据库当作一个Resource,本地事务提交时会去注册一个分支事务。

TCC模式

在Seata框架中,每组TCC接口当作一个Resource,称为TCC Resource。当然一组TCC接口可以是RPC,也可以是服务内JVM调用。

业务启动时,Seata框架自动扫描识别到对应的TCC接口及其调用方和发布方。

如果是事务的发布方,会在业务启动时向TC注册TC Resource,类似于DataSource Resource,每个资源有唯一的全局资源ID。

如果是事务的调用方,Seata框架给调用方加上切面,类似于AT模式,运行时拦截所有TCC接口调用。
每调用一次Try接口,切面会先向TC注册一个分支事务,然后才会执行原有的RPC调用。
当请求链路调用完成后,TC通过分支事务的资源ID回调正确的参与者去执行对应的TCC资源的Confirm或Cancel方法。

了解了框架模型后,可以知道框架本身会扫描TCC接口,注册资源,拦截接口调用,注册分支事务,之后回调第二阶段接口。

核心是TCC接口的实现逻辑。

TCC接口实现

在业务接入事务框架的TCC模式之后,大部分工作都是在考虑如何实现TCC服务上。

设计TCC接口需要注意业务逻辑的拆解和资源调用的隔离。

业务逻辑分解

需要将操作分成两阶段完成的方式,TCC=Try-Confirm-Cancel 相对于XA等传统模式,特征在于不依赖RM对分布式事务的支持,而是通过业务逻辑分解来实现分布式事务。

TCC模式对于业务系统存在假设,其对外提供的服务需要接受一些不确定性,外部对于业务逻辑的调用首先是个临时操作,外部调用对于后续的业务处理保留取消权。如果业务调用认为全局事务应该回滚,就需要取消之前的临时操作。如果业务调用认为全局事务可以提交,就会放弃之前临时操作的取消权。初步的临时操作最后都会被确认或取消。

TCC对假设抽象成以下逻辑:

  1. 初步操作Try:完成所有业务检查,预留必要的业务资源。
  2. 确认操作Confirm:真正执行业务逻辑,不做任何检查,只使用Try阶段预留的业务资源。所以只要try成功,confirm必须成功。同时confirm需满足幂等性,因为框架面对不确定性普遍会进行重试,以保证事务提交并只成功一次。
  3. 取消操作Cancel:释放Try阶段预留的资源,同样,cancel操作需要满足幂等性。

每个账户或商户有一个账号及其可用余额。交易逻辑涉及到交易,充值,转账,退款等这些都是对账户进行加钱和扣钱。

于是可以把账务系统拆分成两套TCC接口,两个TCC Resource,一个加钱TCC接口,一个扣钱TCC接口。

扣钱TCC

A转账30元给B,A的余额需要从100元减去30元,余额就是所谓的业务资源。

按照TCC原则,第一阶段需要检查并预留业务资源:

  • 检查:在TCC资源的Try接口中检查A是否有足够的余额
  • 预留:然后预留余额资源啊,并扣除30元

由于业务资源已经在第一阶段的try接口里面扣除了,第二阶段的confirm接口可以什么都不做,是个空实现。
cancel接口需要把try接口里面扣除的30元还给账户,进行资源释放。

加钱TCC

第一阶段的try接口不能直接给账户加钱,因为如果加钱之后,账户的余额就会被使用了。因此真正的加钱操作需要放到confirm接口中。
第一阶段的try接口不需要预留任何资源,可以设计为空实现。
Cancel接口没有资源需要释放,所以也可以是个空实现。
真正提交时,执行confirm接口增加可用余额。

事务并发控制

Seata框架本身提供两阶段原子提交,保证分布式事务原子性。事务的隔离则是交给了业务逻辑来实现。隔离的本质就是控制并发,防止并发事务操作相同资源引起结果错乱。

以经典的转账为例,当用户发起交易时,首先检查用户资金,资金充足,扣除交易金额,增加卖家资金,完成交易。
如果没有事务隔离,用户发起两笔交易,两笔交易都认为资金充足,实际上只够一笔交易,结果两笔交易都支付成功,导致资损。

所以并发控制是业务逻辑正确执行的保证,如果采用基于数据库的两阶段锁控制并发访问,需要在事务中一直持有数据库资源锁到整个事务执行结束,如果在分布式架构下,锁需要持有到事务第二阶段结束,由于锁的持有时间过长,会导致并发能力的下降。

因此TCC模式的隔离思想体现在通过业务改造实现。

第一阶段结束之后,从底层数据库资源层面加锁过度到上层业务层面的加锁,从而降低底层数据库锁资源,放宽分布式事务锁协议,将锁粒度降到最低,更大限度提高并发性能。

如果A账户有100元,事务T1需要扣除30元,事务T2需要扣除20元,出现了并发。
TCC对于这种操作,在第一阶段Try操作中,需要利用数据库资源层面加锁,检查账户可用余额,如果余额充足,则预留业务资源,扣除本次交易金额,一阶段结束后,虽然数据库层面资源锁释放了,但是这笔资金被业务隔离,不允许本次事务之外的其他并发事务动用。

需要在模型中增加冻结金额字段,用来表示账户中多少金额处于冻结状态。

优化之后的TCC模型里面的扣钱TCC逻辑如下:

  • try接口不再直接扣除账户可用余额,而是真正预留资源,冻结部分空用余额,也就相应减少了可用金额。
  • confirm接口不再是空操作,而是使用try接口预留的业务资源,将冻结金额扣除。
  • cancel接口中,释放预留资源,把try里面冻结的金额扣除,增加可用金额。

加钱TCC逻辑不涉及冻结金额的使用,无需修改。

优化后的模型可以规整的看到预留资源,使用资源,释放资源的过程。

并发控制逻辑如下:

  • 事务T1在第一阶段try操作中,先锁定账户,检查账户可用余额,如果余额充足,预留业务资源,减少可用金额,增加冻结金额。
  • 并发的事务T2,类似的需要加锁,检查余额,减少可用余额,增加冻结余额。

在第二阶段各自事务使用第一阶段try锁定的冻结金额资源即可。
所以第一层面的是通过数据库层面的锁,预留业务资源,冻结金额。通过业务隔离方式将这部分资源加锁,不允许本地事务之外的其他并发事务调用,保证事务在第二阶段正确顺利执行。

所以整个TCC模式核心是进行业务逻辑拆分,拆成两个阶段,try,confirm,cancel。try进行资源检查,资源预留,confirm使用资源,cancel接口释放预留资源。
并发控制采用数据库锁和业务加锁组合方式实现,由于业务加锁特性不影响性能,可以降低数据库锁粒度,提高并发能力。

TCC异常处理

在面对分布式系统需要面对的网络超时,重发,宕机等不可用问题时,事务框架往往有不同的问题,最常见的有:空回滚,幂等,悬挂。

因此在TCC接口里面需要处理这三类异常。

空回滚

就是对于一个分布式事务,在没有调用TCC资源try方法的情况下,调用了第二阶段的cancel方法,cancel方法需要识别出这是一个空回滚,然后返回成功。

什么情况会返回空回滚呢?

在进行RPC调用时,Seata框架会进行切面拦截请求,进行分支事务注册,先向TC注册分布式事务,然后执行RPC调用逻辑。
如果RPC调用逻辑有问题,比如调用方机器宕机,网络异常,会造成RPC调用失败,也就是未能成功执行Try方法。但事务已经开启,需要推进到终态,因此TC会回调第二阶段cancel接口,从而形成空回滚。

解决空回滚需要额外的一个事务控制表,其中有分布式事务id和分支事务id,第一阶段try方法里面插入一条记录,表示一阶段执行了。cancel接口读取该记录,如果记录存在,正常回滚。如果记录不存在,执行空回滚。

幂等

事务框架里面幂等的目的是为了解决,同一个分布式事务里面同一个分支事务,调用该分支事务的第二阶段接口,因此TCC里面的二阶段提交的confirm和cancel接口需要保证幂等,不会重发使用或者释放资源。幂等控制没有做好的话,很有可能导致资损等问题。

什么样情况会造成重复提交呢?

悬挂

悬挂就是对于一个分布式事务,第二阶段cancel接口比try接口先执行,因为允许空回滚,cancel接口认为try接口没有执行,空回滚执行返回成功,seata框架认为,分布式事务第二阶段接口已经执行成功,整个分布式事务就结束了。

但是此时有可能真正的try方法才真正执行,预留业务资源,由于try过程中会加锁预留资源,并且只有当前事务可以使用,但seata框架认为分布式事务已经结束,就会出现第一阶段预留的业务资源没人能够处理,这种情况属于悬挂。

在RPC调用时,先注册分支事务,在执行RPC调用,如果此时RPC调用网络阻塞,通常RPC调用是有超时时间的,RPC超时以后,发起方通知TC回滚该事务,可能回滚完成后,RPC请求才到达参与者,真正执行,从而造成悬挂。

为了防止悬挂,如果第二阶段完成,一阶段就不能在继续了,因此一阶段执行时,需要先检查二阶段释放已经执行完成,如果执行完成,则一阶段不再执行。否则可以正常执行。

同样依赖于事务控制表,在二阶段执行时插入一条事务控制记录,状态为回滚,这样当一阶段执行时,先读取该记录,如果存在,就认为二阶段已执行。否则认为二阶段没有执行。

异步化

TCC模型把两阶段拆分成了两个独立的阶段,通过资源业务锁定方式进行关联。资源锁定好处是,不会阻塞其他事务第一阶段对于相同资源的继续使用,也不会影响第二阶段的正确执行,理论上说,只要业务允许,事务的二阶段什么时候执行都可以,反正资源已经锁定了,不会被其他事务锁定该资源。

对于一些资源锁定,但是资源执行间隔比较久的业务场景来说,可以在第一阶段后,认为本次交易环节完成,并向用户和商户返回支付成功结果,并不需要马上执行二阶段的confirm操作,可以降低热点数据性能问题,在业务低峰期慢慢消化,异步的执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值