聊聊分布式解决方案Saga模式

聊聊,分布式,解决方案,saga,模式 · 浏览次数 : 326

小编点评

## Saga 模式模式的实现方式比较 **集中式实现** * 优点: * 简洁易维护 * 可以使用 DSL 来定义状态机 * 缺点: * 需要增加协调器,可能增加了系统复杂度 * 添加新的事务步骤时比较麻烦 **去中心化实现** * 优点: * 减少系统复杂度 * 提高性能 * 缺点: * 需要使用消息机制进行沟通 * 难以维护,需要在各个参与者之间同步状态 **两种方案的比较** | 特征 | 集中式实现 | 去中心化实现 | |---|---|---| | 简洁性 | 简洁易维护 | 复杂 | | 性能 | 较低 | 较高 | | 可扩展性 | 高 | 低 | | 状态机复杂性 | 低 | 高 | | 维护性 | 低 | 高 | **其他建议** * 在实现 Saga 模式时,需要考虑如何处理事务的失败情况。 * 可以使用消息队列中间件来处理 Saga 中的中间事务。 * 可以使用事件驱动的方式进行事务协调,以减少对中间协调器的依赖。

正文

Saga模式

Saga模式使用一系列本地事务来提供事务管理,而一个本地事务对应一个Saga参与者,在Saga流程里面每一个本地事务只操作本地数据库,然后通过消息或事件来触发下一个本地事务,如果其中一个本地事务失败了,Saga就会执行一系列补偿事务来实现回滚操作。(补偿事务简单来讲就是对之前本地事务做的修改导致不一致的情况执行反向操作来消除掉不一致的状态)。

image

上图左侧是正常的事务流程,当执行事务T3时出现异常,则开始反向执行右边的事务补偿,其中C3是T3的补偿,C2是T2的补偿,C1是T1的补偿,将T3,T2,T1已经修改的数据做补偿处理。

实现分析

对Saga事务流程进行排序,当Ti事务完成之后,需要决定下一步要怎么进行。如果成功执行T(i+1)分支,如果失败,则执行C(i-1)分支。这类似一个工作流或是状态机的概念。从实现来看,有两种方式:

集中式实现

集中式协调器负责服务调用以及事务协调(Orchestration)即编排实现:集中式协调器负责服务调用以及事务协调。Saga提供一个控制类,其方便参与者之间的协调工作。事务执行的命令从控制类发起,按照逻辑顺序请求Saga的参与者,从参与者那里接受到反馈以后,控制类在发起向其他参与者的调用。所有Saga的参与者都围绕这个控制类进行沟通和协调工作。

image

去中心化实现

分布式的实现方式——通过事件驱动的方式进行事务协调(Choreography)即协同实现:Saga参与者(子事务)之间的调用、分配、决策和排序,通过交换事件进行进行。是一种去中心化的模式,参与者之间通过消息机制进行沟通,通过监听器的方式监听其他参与者发出的消息,从而执行后续的逻辑处理。由于没有中间协调点,靠参与者自己进行相互协调。

image

实现比对

我个人认为在计算机的世界里没有银弹!任何的解决方案只能说是合适与不合适,而没有完美的契合并解决。

如上两种解决方式都有一定的弊端;对于集中式的实现方式,其弊端如下:

  • 必须额外实现一个协调器,相当于增加了系统复杂度
  • 需要考虑协调器自身发生故障时应对措施

分布式的实现方式,其弊端如下:

  • 添加新的事务步骤时比较麻烦,需要确定哪个Saga参与者订阅了哪个事件。
  • 有可能出现循环依赖的问题,每一个Saga参与者都可能订阅其他参与者的事件。
  • 集成测试异常复杂,需要运行所有服务来模拟事务。

实现方式

目前看到市面上已经有很多的saga实现,他们都具备saga的基本功能。

这些实现,可以大致可以分为两类

状态机实现
Seata

这一类的典型实现有seata的saga,他引入了一个DSL语言定义的状态机,允许用户做以下操作:

在某一个子事务结束后,根据这个子事务的结果,决定下一步做什么
能够把子事务执行的结果保存到状态机,并在后续的子事务中作为输入
允许没有依赖的子事务之间并发执行。

  • 优点:
    功能强大,事务可以灵活自定义

  • 缺点:
    状态机的使用门槛非常高,需要了解相关DSL,可读性差,出问题难调试。官方例子是一个包含两个子事务的全局事务,Json格式的状态机定义大约有95行,较难入门。
    接口入侵强,只能使用特定的输入输出接口参数类型,在云原生时代,对强类型的gRPC不友好(gRPC协议,在TM拿不到用户自定义的输入输出pb文件,因此无法解析结果中的字段)

Masstransit Saga State Machines

Masstransit是一个免费、开源的.NET 分布式应用框架。其功能之一就是提供了强大的状态机编排能力。通过集成消息队列中间件,基于C#高效易用的语法,支持了状态机的编排。其使用语法示例如下

///// 下单 初始化 → 已初始化
///// 翻译:当前状态是Initial且执行OrderProcessInitializationEvent事件时,Then(然后)执行xxxx,最后将状态转换(TransitionTo)为OrderProcessInitializedState

During(Initial,
    When(OrderProcessInitializationEvent)
        .Then(x => {
            x.Saga.OrderStartDate = DateTime.Now;
        })
        .TransitionTo(OrderProcessInitializedState));

///// 库存 已初始化 → 校验库存
///// 翻译:当前状态是OrderProcessInitializedState且执行CheckProductStockEvent事件时,Then(然后)执行xxxx,最后将状态转换(TransitionTo)为CheckProductStockState

During(OrderProcessInitializedState,
    When(CheckProductStockEvent)
    .Then(x => {
        System.Console.WriteLine(x.Message.OrderId);
        })
        .TransitionTo(CheckProductStockState));

///// 支付 校验库存 → 支付
During(CheckProductStockState,
    When(TakePaymentEvent)
        .TransitionTo(TakePaymentState));

///// 订单 支付 → 创建订单
During(TakePaymentState,
    When(CreateOrderEvent).Then(x => {
        System.Console.WriteLine(x.Message.OrderId);
    })
        .TransitionTo(CreateOrderState));

///// 创建订单失败
DuringAny(When(CreateOrderFaultEvent)
    .TransitionTo(CreateOrderFaultedState)
    .Then(context => context.Publish<Fault<TakePaymentEvent>>(new {context.Message})));

///// 支付失败
DuringAny(When(TakePaymentEventFaultEvent)
    .TransitionTo(TakePaymentFaultedState)
    .Then(context => context.Publish<Fault<CheckProductStockEvent>>(new {context.Message})));

///// 校验库存失败
DuringAny(When(CheckProductStockFaultEvent)
    .TransitionTo(CheckProductStockFaultedState)
    .Then(context => context.Publish<Fault<OrderProcessInitializationEvent>>(new {context.Message})));

///// 下单失败
DuringAny(When(OrderProcessInitializationFaultEvent)
    .TransitionTo(OrderProcessInitializedFaultedState)
    .Then(context => context.Publish<OrderProcessFailedEvent>(new {OrderId = context.Saga.CorrelationId})));

///// 下单流程失败
DuringAny(When(OrderProcessFailedEvent)
    .TransitionTo(OrderProcessFailedState));

流程逻辑:当客户端请求下单服务时,业务逻辑正常执行,执行成功后发布事件到消息队列,状态机监听到对应的订单事件后,修改当前状态,发布事件标识成功或失败,订单服务业务监听事件,响应状态的调整(一般是标识或回滚业务)。

image

  • 优点
    方便简单,而且强大,流程编排能力很强。

  • 缺点:引入了rabbitmq,有中间件依赖。

可参考实现:
使用 Masstransit中的 Request/Response 与 Courier 功能实现最终一致性
分布式事务 | 基于MassTransit的StateMachine实现Saga编排式分布式事务

非状态机实现

这一类的实现有eventuate的saga,dtm的saga。

在这一类的实现中,没有引入新的DSL来实现状态机,而是采用函数接口的方式,定义全局事务下的各个分支事务。

优点:

简单易上手,易维护

缺点:
难以做到状态机的事务灵活自定义

ACID与Saga

image

与聊聊分布式解决方案Saga模式相似的内容:

聊聊分布式解决方案Saga模式

### Saga模式 Saga模式使用一系列本地事务来提供事务管理,而一个本地事务对应一个Saga参与者,在Saga流程里面每一个本地事务只操作本地数据库,然后通过消息或事件来触发下一个本地事务,如果其中一个本地事务失败了,Saga就会执行一系列补偿事务来实现回滚操作。(补偿事务简单来讲就是对之前本

聊聊Seata分布式解决方案AT模式的实现原理

### 什么是Seata分布式事务解决方案 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。 ### AT模式 AT模式目前来看是Seata框架独有的一种模式,其它的分布式框架上

聊聊分布式事务一致性与本地消息表

我个人比较推崇本地消息表模式来实现最终一致性。首先本地消息表的设计不仅可以解决事务一致性的问题,对于消息队列常见问题中的消息丢失与消息幂等其实都是可以通过本地消息表来解决;其带来的好处是多重的。 ### 什么是分布式事务一致性 大白话就是对数据源进行拆分后,多库多机器的多数据库事务一致性问题。因为此

聊聊Excel解析:如何处理百万行EXCEL文件

如何恰当地处理数据量庞大的Excel文件,避免内存溢出问题?本文将对比分析业界主流的Excel解析技术,并给出解决方案。

[转帖]两万字详解InnoDB的锁

https://juejin.cn/post/7094049650428084232 前言 大家好,我是捡田螺的小男孩。本文将跟大家聊聊InnoDb的锁,以及如何分析和解决死锁问题,希望对大家有帮助哈。 为什么需要加锁呢? InnoDB的七种锁介绍 一条SQL是如何加锁的 RR隔离级别下的加锁规则

聊聊不太符合常规思维的动态规划算法

摘要:大部分动态规划能解决的问题,都可以通过回溯算法来解决,只不过回溯算法解决起来效率比较低,时间复杂度是指数级的。动态规划算法,在执行效率方面,要高很多。 本文分享自华为云社区《深入浅出动态规划算法》,作者:嵌入式视觉。 一,动态规划概念 动态规划比较适合用来求解最优问题,比如求最大值、最小值等等

聊聊多任务学习

最近翻译的一篇分享中,主要讲解了多任务学习的各个方面,很多的专业术语与概念都不清楚,因此简单的整理了下相关的知识,做个笔记。 ### 概述 现在大多数机器学习任务都是单任务学习。对于复杂的问题,也可以分解为简单且相互独立的子问题来单独解决,然后再合并结果,得到最初复杂问题的结果。这样做看似合理,其实

聊聊数据库事务内嵌TCP连接

最近再看项目代码,发现很多的service里面,喜欢在事务内部再去调用HTTP请求,简单分析下此种方式的利弊与解决策略。 概述 在数据库内部嵌套TCP连接(一般是HTTP调用或是RPC远程调用)。 @Transactional(rollbackFor = Exception.class) publi

学了这么久的高并发编程,连Java中的并发原子类都不知道?

摘要:保证线程安全是 Java 并发编程必须要解决的重要问题,本文和大家聊聊Java中的并发原子类,看它如何确保多线程的数据一致性。 本文分享自华为云社区《学了这么久的高并发编程,连Java中的并发原子类都不知道?这也太Low了吧》,作者:冰 河。 今天我们一起来聊聊Java中的并发原子类。在 ja

聊聊什么是分布式事务

### 概述 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,以上是百度百科的解释。 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失