个人博客网站制作图片,微信小程序教程,谷歌搜索引擎为什么国内用不了,网站建设会议报道参考
本文参考https://zhuanlan.zhihu.com/p/648556608#xff0c;在小徐的基础上做了个人的笔记。 分布式事务场景
事务核心特性
在聊分布式事务之前#xff0c;我们先理清楚有关于 “事务” 的定义.
事务 Transaction#xff0c;是一段特殊的执行程序#xff0c;其需…参考
本文参考https://zhuanlan.zhihu.com/p/648556608在小徐的基础上做了个人的笔记。 分布式事务场景
事务核心特性
在聊分布式事务之前我们先理清楚有关于 “事务” 的定义.
事务 Transaction是一段特殊的执行程序其需要具备如下四项核心性质 当涉及到事务处理时有四个核心要素它们被称为事务的ACID四大特性即原子性Atomicity、一致性Consistency、隔离性Isolation和持久性Durability。这些特性在关系型数据库范围内通常较容易实现因为数据和操作都在同一个数据库内。然而当一个事务涉及到跨越不同的数据库、服务或存储组件时这个问题就变得更加复杂和有趣这正是我们今天要重点讨论的“分布式事务”领域所涉及的问题。
分布式事务概念
由于数据库的拆分或分布式架构微服务不可避免的带来了分布式事务的问题。如下为当前针对分布式事务的工程实践和处理方式。
基于业务逻辑和应用场景最小化分布式事务边界 言外之意就是说应该在设计阶段尽可能规避没必要的分布式事务场景。基于 XA 的强一致性事务 XA模式是传统的强一致性分布式事务解决方案性能较低且锁资源竞争突出。XA的实现方式存在长事务风险且锁资源严重。在实际业务中使用较少本文不做更多讨论。追求最终一致性的柔性事务 柔性事务通过放宽对强一致性要求而是通过反向补偿来达到最终一致性同时换取系统吞吐量的提升和缓解锁资源竞争。目前Seata 框架提供了多种事务管理模式来支持柔性事务的落地实现。
虚拟业务场景设计
下面我们通过一个常见的场景问题引出有关于分布式事务的话题.
假设我们在维护一个电商后台系统每当在处理一笔来自用户创建订单的请求时需要执行两步操作
从账户系统中扣减用户的账户余额从库存系统中扣减商品的剩余库存
从业务流程上来说这个流程需要保证具备事务的原子性即两个操作需要能够一气呵成地完成执行要么同时成功要么同时失败不能够出现数据状态不一致的问题比如发生从用户账户扣除了金额但商品库存却扣减失败的问题。
然而从技术流程上来讲两个步骤是相对独立的两个操作底层涉及到的存储介质也是相互独立的因此无法基于本地事务的实现方式。 分布式事务的实现确实面临着很高的难度但在业界已经提出了一套被广泛认可并应用的解决方案。这些解决方案将在后续的章节中介绍。在此之前我们需要明确在分布式事务的实现中所谓的数据状态一致性需要做出妥协 数据状态一致性在分布式事务中我们所谈论的数据状态一致性指的是数据的最终一致性而不是即时一致性。即时一致性通常在分布式系统中难以实现因为网络延迟和不同组件之间的通信可能导致即时一致性变得不切实际。因此在分布式环境中我们更关注确保数据最终达到一致的状态即经过一段时间后系统的各个节点都会收敛到相同的数据状态。 百分之百的一致性无法保证分布式事务中的一个根本挑战是无法百分之百地保证数据状态的一致性。这是因为分布式系统的稳定性和一致性受到网络环境的影响以及与第三方系统的交互等多种因素的影响。即使采用了复杂的分布式事务协议和机制也难以消除所有可能的故障和不一致性。
因此分布式事务的实现需要在数据一致性和系统性能之间寻找平衡。通常情况下分布式系统会采用某种程度的最终一致性同时尽力减小数据不一致性的发生概率。这可能涉及到使用分布式事务协议、分布式锁、版本控制等技术手段以确保在大多数情况下数据状态是一致的。但在特殊情况下仍然需要处理可能的不一致性问题并设计恢复机制来纠正这些问题。因此分布式事务的实现需要权衡各种因素以满足系统的要求和可用性目标。 事务消息方案
首先一类偏狭义的分布式事务解决方案是基于消息队列 MessageQueue后续简称 MQ实现的事务消息 Transaction Message.
RocketMQ 简介
RocketMQ 是阿里基于 java 实现并托管于 apache 基金会的顶级开源消息队列组件其中事务消息 TX Msg 也是 RocketMQ 现有的一项能力. 本章将主要基于 RocketMQ 针对事务消息的实现思路展开介绍.
RocketMQ github 地址https://github.com/apache/rocketmq kafka KafkaApache Kafka是一种高吞吐量、分布式、持久性的消息传递系统最初由LinkedIn开发并且后来成为了Apache软件基金会的一个顶级项目。Kafka旨在处理大量数据流并支持实时数据流处理应用程序。 Kafka的典型用例包括日志聚合、事件溯源、监控和度量、实时数据分析、日志流式处理、电子商务订单处理等。它在大规模数据处理、实时数据流和事件驱动架构中广泛使用。
基于 MQ 实现分布式事务
我们知道在 MQ 组件中通常能够为我们保证的一项能力是投递到 MQ 中的消息能至少被下游消费者 consumer 消费到一次即所谓的 at least once 语义.
基于此MQ 组件能够保证消息不会在消费环节丢失但是无法解决消息的重复性问题. 因此倘若我们需要追求精确消费一次的目标则下游的 consumer 还需要基于消息的唯一键执行幂等去重操作在 at least once 的基础上过滤掉重复消息最终达到 exactly once 的语义. 依赖于 MQ 中 at least once 的性质我们简单认为只要把一条消息成功投递到 MQ 组件中它就一定被下游 consumer 端消费端至少不会发生消息丢失的问题.
倘若我们需要执行一个分布式事务事务流程中包含需要在服务 A 中执行的动作 I 以及需要在服务 B 中执行的动作 II此时我们可以基于如下思路串联流程
以服务 A 作为 MQ 生产方 producer服务 B 作为 MQ 消费方 consumer服务 A 首先在执行动作 I执行成功后往 MQ 中投递消息驱动服务 B 执行动作 II服务 B 消费到消息后完成动作 II 的执行
对上述流程进行总结其具备如下优势
服务 A 和服务 B 通过 MQ 组件实现异步解耦从而提高系统处理整个事务流程的吞吐量当服务 A 执行 动作 I 失败后可以选择不投递消息从而熔断流程保证不会出现动作 II 执行成功而动作 I 执行失败的不一致的问题基于 MQ at least once 的语义服务 A 只要成功消息的投递就可以相信服务 B 一定能消费到该消息至少服务 B 能感知到动作 II 需要执行的这一项情报依赖于 MQ 消费侧的 ack 机制可以实现服务 B 有限轮次的重试能力. 即当服务 B 执行动作 II 失败后可以给予 MQ bad ack从而通过消息重发的机制实现动作 II 的重试提高动作 II 的执行的成功率
与之相对的上述流程也具备如下几项局限性
问题 1服务 B 消费到消息执行动作 II 可能发生失败即便依赖于 MQ 重试也无法保证动作一定能执行成功此时缺乏令服务 A 回滚动作 I 的机制. 因此很可能出现动作 I 执行成功而动作 II 执行失败的不一致问题问题 2在这个流程中服务 A 需要执行的操作有两步1执行动作 I2投递消息. 这两个步骤本质上也无法保证原子性即可能出现服务 A 执行动作 I 成功而投递消息失败的问题. 本地事务消息投递
上面的小节中聊到的服务 A 所要执行的操作分为两步本地事务消息投递. 这里我们需要如何保证这两个步骤的执行能够步调统一呢下面不妨一起来推演一下我们的流程设计思路
首先这两个步骤在流程中一定会存在一个执行的先后顺序我们首先来思考看看不同的组织顺序可能会分别衍生出怎样的问题
组合 I先执行本地事务后执行消息投递 组合 I 的优势
消息投递成功与本地事务一致当使用组合 I 策略时可以确保消息的投递与本地事务的执行是一致的。这意味着只有在本地事务执行成功时消息才会被投递。这可以防止消息投递成功但本地事务失败的情况。熔断机制如果本地事务执行失败您可以主动停止或熔断消息的投递动作。这可以防止错误的消息被发送降低了系统可能面临的问题。
组合 I 的劣势
消息投递失败可能导致消息丢失虽然组合 I 确保了消息投递与本地事务的一致性但在某些情况下消息投递可能会失败。例如即使本地事务成功但消息投递由于网络或其他问题而失败导致消息丢失。此时由于本地事务已经提交要执行回滚操作会非常复杂和昂贵。
组合 II先执行消息投递后执行本地事务 组合 II 的优势
避免不必要的本地事务如果消息投递失败您可以避免执行不必要的本地事务。这可以提高系统的效率因为不会浪费资源在本地事务上除非消息可以被成功投递。
组合 II 的劣势
消息投递成功可能导致问题尽管组合 II 确保了本地事务与消息投递的一致性但在某些情况下消息投递成功可能导致问题。例如如果消息成功发送后本地事务一直无法成功执行那么可能会出现数据不一致或其他问题。
对上面对流程进行梳理总结实现思路是基于本地事务包裹消息投递操作的实现方式对应执行步骤如下
首先 begin transaction开启本地事务在事务中执行本地状态数据的更新完成数据更新后不立即 commit transaction执行消息投递操作倘若消息投递成功则 commit transaction倘若消息投递失败则 rollback transaction 这个流程乍一看没啥毛病重复利用了本地事务回滚的能力解决了本地修改操作成功、消息投递失败后本地数据修正成本高的问题.
然而这仅仅是表现. 上述流程实际上是经不住推敲的其中存在三个致命问题
本地事务中夹杂第三方组件的IO操作在本地事务中执行与第三方组件的IO操作可能引发长事务的风险。长事务可能会导致数据库锁定、性能问题和资源浪费。为了缓解这个问题可以考虑将IO操作与数据库事务解耦将其移到事务之外或者采用异步处理方法。消息投递可能因超时或其他问题导致异常当消息在实际上已成功投递但生产者未能获得投递响应时可能会导致本地事务被误回滚的问题。为了避免这种情况可以实现幂等性操作确保消息处理具有幂等性以便在重试消息时不会引发问题。事务提交失败可能导致无法回滚消息如果在执行事务提交操作时发生失败数据库修改操作会回滚但已经发送的MQ消息无法回收。这可能导致数据不一致性。为了处理这个问题可以采用两阶段提交2PC或者分布式事务管理器来确保事务操作的一致性包括数据库和消息的一致性。
事务消息原理TX Msg
我们以 RocketMQ 中 TX Msg 的实现方案为例展开介绍。首先抛出结论TX Msg 能保证我们做到在本地事务执行成功的情况下后置的投递消息操作能以接近百分之百的概率被发出. 其实现的核心流程为
生产方 producer 首先向 RocketMQ 生产一条半事务消息此消息处于中间态会暂存于 RocketMQ 不会被立即发出producer 执行本地事务如果本地事务执行成功producer 直接提交本地事务并且向 RocketMQ 发出一条确认消息如果本地事务执行失败producer 向 RocketMQ 发出一条回滚指令倘若 RocketMQ 接收到确认消息则会执行消息的发送操作供下游消费者 consumer 消费倘若 RocketMQ 接收到回滚指令则会删除对应的半事务消息不会执行实际的消息发送操作此外在 RocketMQ 侧针对半事务消息会有一个轮询任务倘若半事务消息一直未收到来自 producer 侧的二次确认则 RocketMQ 会持续主动询问 producer 侧本地事务的执行状态从而引导半事务消息走向终态。 在 TX Msg 的实现流程中能够保证 前面小节中谈及的各种 bad case 都能被很好地消化
倘若本地事务执行失败则 producer 会向 RocketMQ 发出删除半事务消息的回滚指令因此保证消息不会被发出倘若本地事务执行成功 则 producer 会向 RocketMQ 发出事务成功的确认指令因此消息能够被正常发出倘若 producer 端在发出第二轮的确认或回滚指令前发生意外状况导致第二轮结果指令确实. 则 RocketMQ 会基于自身的轮询机制主动询问本地事务的执行状况最终帮助半事务消息推进进度.
总结一下 保证本地事务成功后消息投递接近百分之百的概率RocketMQ的TX Msg机制确保了在本地事务执行成功的情况下消息会以接近百分之百的概率被成功发出。这是因为只有在本地事务成功后才会向RocketMQ发送确认消息从而触发消息的真正发送。 事务性保障RocketMQ的TX Msg允许生产者执行本地事务确保了消息发送与事务的一致性。如果本地事务失败消息不会被发送从而维护了数据的一致性。 回滚机制如果本地事务执行失败RocketMQ会接收到回滚指令然后删除对应的半事务消息而不会执行实际的消息发送操作。这意味着即使本地事务失败不会导致消息被误发送保持了数据的一致性。 轮询任务RocketMQ会定期轮询半事务消息的状态如果长时间未收到本地事务的二次确认RocketMQ会主动询问生产者本地事务的执行状态确保半事务消息能够最终达到终态。
RocketMQ 中半事务消息轮询流程示意如下 最后我们再回过头把 RocketMQ TX Msg 的使用交互流程总结梳理如下 事务消息局限性
现在我们就来总结梳理一下TX Msg 中存在的几项局限性
流程高度抽象TX Msg 把流程抽象成本地事务投递消息两个步骤. 然而在实际业务场景中分布式事务内包含的步骤数量可能很多因此就需要把更多的内容更重的内容糅合在所谓的“本地事务”环节中上游 producer 侧可能会存在比较大的压力不具备逆向回滚能力倘若接收消息的下游 consumer 侧执行操作失败此时至多只能依赖于 MQ 的重发机制通过重试动作的方式提高执行成功率但是无法从根本上解决下游 consumer 操作失败后回滚上游 producer 的问题. 这一点正是 TX Msg 中存在的最大的局限性.
关于上面第二点我们再展开谈几句. 我们知道并非所有动作都能通过简单的重试机制加以解决.
打个比方倘若下游是一个库存管理系统而对应商品的库存在事实上已经被扣减为 0此时无论重试多少次请求都是徒然之举这就是一个客观意义上的失败动作.
而遵循正常的事务流程后置操作失败时我们应该连带前置操作一起执行回滚然而这部分能力在 TX Msg 的主流程中并没有予以体现.
要实现这种事务的逆向回滚能力就必然需要构筑打通一条由下游逆流而上回调上游的通道这一点并不属于 TX Msg 探讨的范畴.