分布式事务问题
1.何所谓分布式事务问题
咱这边主要讨论多服务多数据源
的事务问题。那像我们微服务常见就是典型的分布式事务问题,当然也存在一些非典型的分布式事务情形,但很少遇到就不展开讨论,先思考最主流的分布式事务的解决方案。我比较赞同这一句话“分布式事务数据不一致性产生的原因在于参与事务
的多个分支事务间
无法感知互相
的状态”
典型分布式事务问题
2.解决思想是什么,常见的解决方案有哪些,各适用于什么场景呢
上面提到,我们是赞同**“感知问题”
那句话的,那其实你思考一下,2PC和TCC是不是很相似呢?两阶段提交
的思想+一个协调器来协调各事务
**的状态
2PC
准备阶段+提交阶段
问题:
- 同步阻塞:事务参与者会在正式提交事务之前会一直占用相关的资源{也就是说如果收不到协调者的决策,就会一直卡着}
- 数据不一致:同样也是由于事务参与者收不到协调者的决策信息。
- 单点问题——如果协调器在准备(Prepare)阶段完成之后挂掉的话,事务参与者就会一直卡在提交(Commit)阶段。
补充:
3PC:主要是新增了一个
预提交阶段
,来减少同步阻塞
的时间,同时在协调者和事务参与者之间引入了[超时机制][can+pre阶段,一般进入第三阶段后都可成功],防止阻塞不释放的问题出现。但并没有很好解决阻塞问题,并且数据不一致依然存在,故使用也不算广泛
2PC
是一种基于**XA分布式事务
的方案,可以满足业务处理结果的正确性
,但最大的缺点是多分支环境下资源锁定范围大
,并发度低
,随着下游分支的增加,系统性能会越来越差。你提到的关于基于XA分布式事务方案的缺点是非常准确的。确实,虽然XA协议能够保证分布式事务的一致性和原子性,但它在多分支环境下会导致资源锁定范围大、并发度低,进而影响系统性能。以下是对这些缺点的具体分析以及可能的替代方案:
基于XA分布式事务的缺点
资源锁定范围大:
- 在二阶段提交的准备阶段,各参与者会锁定相关资源(如数据库表、行等),以确保事务的一致性。在事务提交或回滚完成之前,这些锁会一直持有。
- 随着事务分支数量的增加,锁定的资源范围扩大,容易导致系统中的其他事务无法访问被锁定的资源,造成资源争用和阻塞。
并发度低:
- 由于资源长时间被锁定,系统的并发处理能力受到限制。其他事务需要等待锁释放,导致整体系统吞吐量降低。
- 在高并发环境下,这种情况尤为明显,会严重影响系统性能。
性能随分支增加而下降:
- 随着事务分支数量的增加,协调和管理分布式事务的开销也会增加。更多的分支意味着更多的通信开销和等待时间。
- 事务管理器需要处理更多的准备和提交请求,增加了系统的负担。
替代方案
为了克服XA协议的这些缺点,可以考虑以下替代方案:
- 基于消息的最终一致性方案:
- 使用消息队列(如RabbitMQ、Kafka)来实现事务消息。事务的各个步骤通过异步消息进行通信,减少锁定资源的时间。
- 这种方案追求最终一致性,允许在短时间内存在数据不一致的情况,但最终会达到一致状态。
- TCC(Try-Confirm-Cancel)模式:
- TCC模式将事务拆分为三个阶段:Try(尝试执行)、Confirm(确认执行)、Cancel(取消执行)。
- 在Try阶段,尝试预留资源;在Confirm阶段,确认资源使用;在Cancel阶段,释放预留资源。
- TCC通过应用层面控制资源,减少数据库锁定时间,提高并发度。
- Saga模式:
- Saga是一种长事务管理模式,将全局事务拆分为一系列局部事务,每个局部事务有相应的补偿操作。
- 如果某个局部事务失败,调用相应的补偿操作回滚已经完成的局部事务,从而保持系统的一致性。
- Saga模式避免了长时间的锁定,提高了系统的并发处理能力。
TCC{比较流行}
Try-commit-cancel
2PC是依靠
数据库
的ACID来控制,而TCC是通过业务代码
来控制事务【所以侵入性较强,但不会有阻塞问题】
2PC追求的是强一致性,在两阶段提交的整个过程中,一直会持有数据库的锁。TCC 追求的是最终一致性,不会一直持有各个业务资源的锁。勉强理解吧,主要还是跟数据库的特性相互联系吧.——不用纠结这个,一般我也认为TCC是强一致性的
为什么2PC对资源的预处理会导致数据库锁定时间长,而TCC却不会
1.TCC处理方式
-- inventory table CREATE TABLE inventory ( product_id VARCHAR(255) PRIMARY KEY, stock INT, reserved INT DEFAULT 0 );
这是我们的数据库表,TCC的尝试阶段会直接操作数据库的reserved字段,进行库存预留。举个例子,本来你的库存数量是 100,你别直接 100 - 2 = 98,扣减这个库存!你可以把可销售的库存:100 - 2 = 98,设置为 98 没问题,然后在一个单独的冻结库存的字段里,设置一个 2。也就是说,有 2 个库存是给冻结了。
2.2PC处理方式
直接进行库存预操作:本来你的库存数量是 100,直接 100 - 2 = 98,扣减这个库存!只不过不提交{所以在这期间会一直持有这个锁,其他想更新也更新不了}。所以我们说2PC是利用数据库的特性来保障一致性
事务消息
参考-RocketMQ官网:https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage
分布式事务的诉求
分布式系统调用的特点为一个核心业务逻辑的执行,同时需要调用多个下游业务进行处理。因此,如何保证核心业务和多个下游业务的执行结果完全一致,是分布式事务需要解决的主要问题。
以电商交易场景为例,用户
创建订单这一核心操作
的同时会涉及到商铺库存扣减
,优惠券使用
,购物车状态
清空等多个子系统的变更。当前业务的处理分支包括:主分支订单系统更新:保存订单信息。
库存系统更新:
优惠券系统状态变更:
购物车系统状态变更:
传统XA事务方案:性能不足
为了保证上述四个分支的执行结果一致性,典型方案是基于XA协议的分布式事务系统来实现。将四个调用分支封装成包含四个独立事务分支的大事务。基于XA分布式事务的方案可以满足业务处理结果的正确性,但最大的缺点是多分支环境下资源锁定范围大,并发度低,随着下游分支的增加,系统性能会越来越差。
基于普通消息方案:一致性保障困难
将上述基于XA事务的方案进行简化,将订单系统变更作为本地事务,剩下的系统变更作为普通消息的下游来执行,事务分支简化成普通消息+订单表事务,充分利用
消息异步化的能力缩短链路
,提高并发度。该方案中消息下游分支和订单系统变更的主分支很容易出现不一致的现象,例如:
消息发送成功,订单没有执行成功,需要回滚整个事务。
订单执行成功,消息没有发送成功,需要额外补偿才能发现不一致。
消息发送超时未知,此时无法判断需要回滚订单还是提交订单变更。
基于Apache RocketMQ分布式事务消息:支持最终一致性
上述普通消息方案中,**
普通消息和订单事务无法保证一致
**的原因,本质上是由于普通消息无法像单机数据库事务一样,具备提交、回滚和统一协调的能力。而基于Apache RocketMQ实现的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。
关于下游多个消费者消费消息的问题,如何保证一致性
事务消息的生命周期
[重试策略][Apache RocketMQ 的消费重试主要解决的是业务处理逻辑失败导致的消费完整性问题,是一种为业务兜底的策略,不应该被用做业务流程控制,即失败发生的概率不大且非连续性]的目的
DLQ:
死信状态
。消费逻辑的最终兜底机制,若消息一直处理失败并不断进行重试,直到超过最大重试次数还未成功,此时消息不会再重试,会被投递至死信队列。您可以通过**消费死信队列的消息进行业务恢复
。
3.应用在订单方面
待施工,后续单独写一篇~~~~
TCC和事务消息的实现方案都可以进行;事务消息是最终一致性{通过重试机制和人工补偿来保证下游任务的消费},
参考资料:
1.javaGuide星球
2.RocketMQ官网:https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage
Saga:略