美文网首页从多线程到分布式
从多线程到分布式(十三)Saga

从多线程到分布式(十三)Saga

作者: 吟游雪人 | 来源:发表于2023-08-28 13:53 被阅读0次

    我们根据分布式架构中组件的不同交互模式定义了3个维度的耦合约束:通信方式、一致性要求和协调方式


    图片.png

    Saga这个概念的出现早于微服务,最初是用来描述早期分布式系统中数据库锁的作用范围的。
    在网站saga模式中,将微服务的Saga模式描述为一系列本地事务,一个更新操作完成之后通过事件触发下一个更新。如何任何某个更新操作失败,Saga会执行一系列回滚操作,对已经发生的更新进行补偿。

    它实现分布式事务的思路是借助一种驱动流程机制,按顺序执行每个数据操作步骤,一旦出现失败,就倒序执行之前各步骤对应的“补偿”操作。这要求每个步骤涉及的服务提供与正向操作接口对应的补偿操作接口。使用Saga实现分布式事务的优点如下。
    ● 灵活:通过对一些基础服务进行组合/编排来完成各种业务需求。
    ● 数据库兼容性高:对每个服务使用何种数据库技术没有任何要求,服务甚至可以不使用数据库。使用Saga实现分布式事务的缺点如下。
    ● 要求实现数据补偿操作,增加了开发和维护的成本。
    ● 不符合ACID事务性:没有涉及隔离性(I)和持久性(D)。

    Saga粗分有两种实现方式
    1.引入协调者的集中编排
    集中编排使用编排器(有时称为协调器)来管理工作流状态、可选行为、错误处理、通知等与工作流相关的任务。它借用了管弦乐队中特有的概念,乐队指挥将总乐谱的不同部分分开演奏,最终效果还是同一支音乐。

    下面的例子,订单服务发起分布式事务,编排者负责驱动分布式事务流程,支付服务和库存服务负责提供数据操作的正向接口和补偿接口。


    图片.png

    每个服务负责自己的限界上下文、数据和行为。编排器组件通常不包含它所编排的工作流之外的任何领域行为。请注意,微服务架构的每个工作流都有单独的编排器,而不是有一个像企业服务总线ESB这样的全局编排器。微服务架构的主要目标之一是解耦,使用ESB之类的全局组件会创建一个不需要的耦合点。因此,微服务倾向于每个工作流都有一个编排器。

    如果世界上只存在正常流程,软件架构就会很容易。然而,软件架构的主要难点之一是如何处理异常场景。

    2.分散协作方式


    图片.png

    乍一看,分散协作式工作流似乎更简单,因为服务更少(没有了编排器),服务之间只需要简单地通过事件和命令(消息)通信。然而,与软件架构中的其他问题类似,架构的难点不在于如何处理正常流程,而是如何正确处理各种边际和错误场景。

    ● 服务解耦:Orchestration天然地将事务本身的驱动逻辑和众多基础服务解耦,而Choreography在不引入队列的前提下,容易出现服务间循环依赖的问题。
    ● 服务分层:Orchestration天然地将服务分成了组合/编排器和基础服务两个调用层级,有利于业务逻辑的扩展和重用。
    ● 数据解耦:对于某个步骤依赖前序多个步骤结果的业务场景,Choreography需要利用前序所有服务透传其他服务的数据,而Orchestration不需要。

    状态所有者一定是存在的,要么是充当协调人的编排器,要么是分散协作方案中的首问责任人。在分散协作解决方案中,没有了协调人会迫使服务之间进行更多通信。有时候这可能是一个非常合适的权衡。例如,如果工作流需要更高的伸缩能力并且错误场景很少,那么用处理错误的复杂性来换取更大规模的伸缩能力也许是值得的。
    此外,工作流中包含的语义复杂度越高,编排器就越实用。请记住,实现耦合不能降低语义耦合,只会把事情变得更复杂。
    分散协作的最佳使用场景是需要高响应性和高可伸缩性的工作流,并且要么没有复杂的错误场景,要么很少出现。这种通信方式能够完成很高的吞吐量
    集中编排最适合包含边界和错误条件的复杂工作流。虽然这种方式在可伸缩性方面不如分散协作,但在大多数情况下能够大大降低实现复杂度。

    采用Saga Orchestration势必要克服它的两个缺点,即要求提供数据补偿操作,以及没有实现ACID中的隔离性和持久性这两个约束。
    编排器既处理请求又编排工作流,并且对前两个服务的同步调用是成功的。然而,当它试图调用最后一个服务时却失败了(可能由于该服务返回错误代码或者基础设施的原因)。因为传统叙事Saga(sao)的目标是事务原子一致性,所以编排器必须利用补偿事务并请求其他两个服务撤销之前的操作,将整体状态撤回到事务开始之前的状态。
    模仿单体系统的事务协调方式(看上去很熟悉),加上独立的编排器对工作流进行编排。然而,它的缺点也是多种多样的。首先,给编排器加上事务性约束可能会影响运维架构,例如性能、可伸缩性、弹性等——编排器必须确保事务中的所有参与者都成功或失败,这会造成响应时间瓶颈。其次,实现分布式事务(例如补偿事务)的各种模式都不能完全应对各种各样的错误场景和边界条件,同时每个服务都需要支持撤销操作,增加了服务内在复杂性。实现这样的分布式事务存在许多困难,因此最好尽可能避免。

    1.实现数据补偿操作数据操作可分为Insert(新建)、Delete(删除)和Update(更新)三种,而Update又可细分为Full update(Replace,整体更新)和Partial update(Patch,部分更新)两种,它们对应的补偿操作分别如下。
    ● Insert:补偿操作是Delete,参数为数据的ID,要求在Insert操作之后记录下数据ID。
    ● Delete:补偿操作是Insert,参数为完整的数据,要求在Delete操作前记录下当前的完整数据。
    ● Full update:补偿操作也是一个Full update,参数为完整的数据,要求在原Full update操作前记录下当前的完整数据。
    ● Partial update:补偿操作可能是Partial update或者Full update,参数为改动前的部分数据或完整数据,要求在原Partial update操作前记录下当前的部分或完整数据。

    概括来说,集中编排式通信具备以下优点:中心化的工作流随着复杂性的增加,使用全局组件来管理状态和行为会更有价值。错误处理错误处理是大多数领域工作流的主要职责,由独立的组件来管理工作流状态会更合理。可恢复性因为编排器监视工作流的状态,当一个或多个领域服务短暂不可用的时候,架构师可以根据状态设计相应的重试机制。状态管理由于集中编排器的存在,所以工作流的状态随时可见,也可以很容易地和其他工作流集成。集中编排式通信有以下不足:响应性所有通信都必须经过编排器中转,这会导致编排器成为影响整体响应性的隐患。容错性虽然集中编排增强了域服务的可恢复性,但它为工作流引入了潜在的单一故障点,虽然这可以通过冗余解决,但会增加更多的复杂性。

    2.实现ACID中的隔离性和持久性约束隔离性关注的是控制并发的问题,即如何处理对同一条数据(同一个key)的并发操作。MySQL作为成熟的关系数据库之一,引入了多版本并发控制(MVCC)机制。遗憾的是,并非所有的数据库都支持这一特性,特别是近年来的NoSQL数据存储方案,大多不支持MVCC。在不引入多版本的前提下,控制并发的主要思路是去除并发,化并为串,其中主要有两类实现方法:抢占锁或使用队列。考虑到等待锁而产生的性能损耗及多锁顺序不一致有可能导致互锁问题,我们优先考虑使用队列来去除并发。持久性指成功提交到系统的事务不能中途丢失,即实现数据持久化。需要考虑的故障包括数据存储节点的故障和数据处理节点的故障。综上所述,为了符合ACID的约束,需要增加一个队列+持久化的技术方案来补足Saga的两个短板。

    图片.png

    工作状态的管理
    大多数工作流都有自己的内部状态,例如哪些元素已经执行,哪些还没有,元素的顺序,发生了哪些错误,重试记录等。对于集中编排式解决方案,很明显是编排器去维护那些状态(然而也有一些架构解决方案使用无状态编排器以便获得更好的弹性)。但是,分散协作式架构不存在某个组件单独管理整个工作流的状态,有很多种管理状态的方案,这里介绍三种最常见的。
    第一种是首问责任人模式,它将状态管理职责放在责任链中第一个被调用的服务上。
    第二种方法是根本不维护任何状态,而是通过查询各个服务来获得实时状态,这叫无状态分散协作式架构。相比之下,虽然这简化了第一个服务(首问责任人)的状态,但它极大地增加了网络开销,因为它依赖服务之间频繁通信来构建状态快照。
    第三种解决方案利用邮戳耦合,在服务之间发送的消息契约中保存工作流状态。每个领域服务只更新状态数据中的一小部分,并将其传递给职责链中的下一个服务。因此,消息的任何消费者都可以查看工作流的状态,而无须查询每个服务。

    越来越复杂的工作流导致性能下降,于是为了提高性能采用异步通信和分散协作的技术。然而,这种想法是一个很典型的例子,即没有考虑问题域的全部关联维度。孤立地看,异步通信确实能提高性能。但是,作为架构师,当它与其他架构维度(例如一致性和协调性)纠缠在一起时,我们不能再孤立地看它。

    相关文章

      网友评论

        本文标题:从多线程到分布式(十三)Saga

        本文链接:https://www.haomeiwen.com/subject/mupgmdtx.html