事务处理(结合分布式事务)

一般的事务处理,分为一下几种:

  • 本地事务
  • 共享事务
  • 全局事务
  • 分布式事务

本地事务

常见的本地事务,有数据库的事务, redis事务。即是一种单服务,单数据源的提交形式

共享事务

这是一个伪需求,实际上多服务,单数据源的场景很少见

全局事务

这是单服务, 多数据源的场景,一种强一致性的事务解决方案

有以下实现:

2PC: 两阶段提交

提交过程: 准备、提交

缺点:单点问题, 性能问题(同步阻塞), 一致性风险

3PC: 三阶段提交

提交过程: CanCommit, PreCommit, DoCommit

缺点: 单点问题和回滚性能有所改善, 一致性问题依旧存在

分布式事务

谈起分布式,不得不说CAP理论

CAP理论

  • C: 一致性
  • P:分区容忍性
  • A: 可用性

三者最多只能满足其中的两项,这是一种多服务,多数据源的场景

同时,根据CAP理论引申出了BASE理论

  • BA: 基本可用(Basically Available)
  • Soft State: 软状态, 状态可以有一段时间不同步
  • Eventually Consistent:最终一致性, 最终数据是一致的就可以,不是时刻都保持一致

从而,基于不同的一致性需求产生了不同的分布式事务解决方案,追求强一致的两阶段提交、追求最终一致性的柔性事务和事务消息

最终一致性(base理论): AP without C

需要考虑的因素:

  • 锁定资源时长(吞吐量,阻塞状态);
  • 协调者单点故障;
  • 脑裂(参与者接受指令不一致);

实现方式

可靠消息队列

 可靠消息的最终一致性,最大努力通知,支持的mq有阿里的rocket mq

要解决问题:消息丢失和重复消费问题

消息丢失场景

  • MQ 自动应答机制导致的消息丢失; 订阅消息事件的服务在接收服务投递的消息后,消息中间件(如 RabbitMQ)默认是开启消息自动应答机制,当系统消费了消息,消息中间件就会删除这个持久化的消息,因此你要采取编程的方式手动发送应答。

  • 高并发场景下的消息积压导致消息丢失

队列双向确认

 

消息重复消费场景

消息日志表:两个字段,消息 ID 和消息执行状态 (幂等性保证的前提)

想要解决“消息丢失”和“消息重复消费”的问题,有一个前提条件就是要实现一个全局唯一 ID 生成的技术方案。

在分布式系统中,全局唯一 ID 生成的实现方法有:

  • 数据库自增主键
  • UUID
  • Redis
  • Twitter-Snowflake 算法

 

XA事务

业务无侵入, 原理类似2PC

 AT事务

业务无侵入, 基于SAGA数据补偿来代替回滚的思路

在业务数据提交时,自动拦截所有SQL,分别保存SQL对数据修改前后结果的快照,生成行锁,通过本地事务一起提交到操作的数据源中,这就相当于自动记录了重做和回滚日志。

AT 事务是参照了 XA 两段提交协议来实现的,但针对 XA 2PC 的缺陷,即在准备阶段,必须等待所有数据源都返回成功后,协调者才能统一发出 Commit 命令而导致的木桶效应(所有涉及到的锁和资源,都需要等到最慢的事务完成后才能统一释放),AT 事务也设计了针对性的解决方案,即是:自动记录了重做和回滚日志,同时基于 支持本地 ACID 事务 的 关系型数据库:

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。

  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。

  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

不过需要注意读隔离和写隔离的问题,以确保数据补偿能成功

写隔离

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

  • 拿不到 全局锁 ,不能提交本地事务。

  • 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理

AT缺点:

1. 脏读: 默认隔离级别是读未提交

2. 脏写:当在本地事务提交之后、分布式事务完成之前,该数据被补偿之前又被其他操作修改过,即出现了脏写(DirtyWirte)

优化:

使用全局锁来实现读写隔离机制,来避免脏读和脏写的发生,所以,基于这种补偿方式,分布式事务中所涉及的每一个数据源都可以单独提交,然后立刻释放锁和资源。

AT事务这种异步提交的模式,相比2PC极大地提升了系统的吞吐量水平。而使用的代价就是大幅度地牺牲了隔离性,甚至直接影响到了原子性。因为在缺乏隔离性的前提下,以补偿代替回滚不一定总能成功(脏写)

TCC

Try、Confirm、 Cancel, 原理类似3PC

 

      业务侵入, Try:尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好事务需要用到的所有业务资源(冻结金额,库存等,保障隔离性)

      Confirm:确认执行阶段,不进行任何业务检查,直接使用Try阶段锁定的资源来完成业务处理。注意,Confirm阶段可能会重复执行,因此需要满足幂等性。

       Cancel:取消执行阶段,释放Try阶段锁定的业务资源。注意,Cancel阶段也可能会重复执行,因此也需要满足幂等性

适用场景

适合用于需要强隔离性的分布式事务中,基于TCC实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为Try 、Confirm 、 Cancel三个接口,所以代码实现复杂度相对较高, 是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为“预留业务资源”和“确认 / 释放消费资源”两个子过程, 才能够有效避免“超售”的问题。TCC 最主要的限制在Try阶段需要锁定资源,如果依赖外部系统的资源,这个锁定资源的操作就显得不可控了。

SAGA

业务无侵入,一种全新的思路: 基于数据补偿代替回滚, 能提高长时间事务效率,避免大事务长时间锁定数据库的资源。

事务基于数据补偿代替回滚的解决思路,与 TCC 相比,SAGA 不需要为资源设计冻结状态和撤销冻结的操作,补偿操作往往要比冻结操作容易实现得多。

但由于 Saga 事务不保证隔离性,  在极端情况下可能由于脏写无法完成回滚操作

比如举一个极端的例子, 分布式事务内先给用户A充值, 然后给用户B扣减余额, 如果在给A用户充值成功, 在事务提交以前, A用户把余额消费掉了, 如果事务发生回滚, 这时则没有办法进行补偿了。

实践中一般的应对方法是:

  • 业务流程设计时遵循“宁可长款, 不可短款”的原则, 长款意思是客户少了钱机构多了钱, 以机构信誉可以给客户退款, 反之则是短款, 少的钱可能追不回来了。所以在业务流程设计上一定是先扣款。

  • 有些业务场景可以允许让业务最终成功, 在回滚不了的情况下可以继续重试完成后面的流程, 所以状态机引擎除了提供“回滚”能力还需要提供“向前”恢复上下文继续执行的能力, 让业务最终执行成功, 达到最终一致性的目的。

过程:

1. 定义一系列子事务:Ti

2. 定义一系列子事务的补偿动作:Ci

数据恢复策略

正向恢复

正向恢复的执行模式为:T1,T2,…,Ti(失败),Ti(重试)…,Ti+1,…,Tn。

反向恢复

反向恢复的执行模式为:T1,T2,…,Ti(失败),Ci(补偿),…,C2,C1。

流程示意图:

 

实践: 基于某些分布式事务中间件实现(Seata

综合对比下几种分布式事务解决方案

  • 一致性保证:XA > TCC = SAGA > 事务消息
  • 业务友好性:XA > 事务消息 > SAGA > TCC
  • 性 能 损 耗:XA > TCC > SAGA = 事务消息

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JYCJ_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值