

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Saga 编排模式
<a name="saga-orchestration"></a>

## 意图
<a name="saga-orchestration-intent"></a>

Saga 编排模式使用中央协调器（*编排工具*）来帮助维护跨多个服务的分布式事务中的数据完整性。在分布式事务中，可以在事务完成之前调用多项服务。当服务将数据存储在不同的数据存储中时，要维护这些数据存储之间的数据一致性可能会很困难。

## 动机
<a name="saga-orchestration-motivation"></a>

*事务*是一个可能涉及多个步骤的单个工作单元，其中要么完全执行所有步骤，要么不执行任何步骤，从而使数据存储保持其一致状态。术语*原子性、一致性、隔离和持久性（ACID）*定义了事务的属性。关系数据库提供 ACID 事务以维护数据一致性。

为了维护事务的一致性，关系数据库使用两阶段提交（2PC）方法。这包括“准备阶段”**和**“提交阶段”。
+ 在准备阶段，协调过程要求事务的参与进程（参与方）承诺要么提交事务，要么回滚事务。
+ 在提交阶段，协调过程会要求参与方提交事务。如果参与方不同意在准备阶段提交，则事务将被回滚。

在遵循 database-per-service设计模式的分布式系统中，两阶段提交不是一种选择。这是因为每个事务分布于不同的数据库中，并且没有单个控制器可以协调类似于关系数据存储中两阶段提交的过程。在这种情况下，一种解决方案是使用 saga 编排模式。

## 适用性
<a name="saga-orchestration-applicability"></a>

在以下情况下使用 saga 编排模式：
+ 您的系统要求在跨多个数据存储的分布式事务中保持数据完整性和一致性。
+ 数据存储没有 2PC 来提供 ACID 事务，因此在应用程序边界内实现 2PC 是一项复杂的任务。
+ 您有 NoSQL 数据库，而这些数据库不提供 ACID 事务，您需要在单个事务中更新多个表。

## 问题和注意事项
<a name="saga-orchestration-issues"></a>
+ **复杂性**：补偿事务和重试会增加应用程序代码的复杂性，从而带来维护开销。
+ **最终一致性**：本地事务的顺序处理可实现最终一致性，这对于需要强一致性的系统来说可能是挑战。您可以通过设定业务团队对一致性模型的期望，或通过切换到提供强一致性的数据存储来解决此问题。
+ **幂等性**：Saga 参与方需要具有幂等性，以便在意外崩溃和编排工具故障导致暂时性故障时允许重复执行。
+ **事务隔离**：Saga 缺少事务隔离。事务的并行编排可能会导致数据陈旧。建议使用语义锁定来处理此类场景。
+ **可观测性**：可观测性是指详细的日志记录和跟踪，以排查执行和编排过程中的问题。当 saga 参与方的数量增加导致调试变得复杂时，这一点变得很重要。
+ **延迟问题**：当 saga 由几个步骤组成时，补偿性事务可能会增加整体响应时间的延迟。在这种情况下，要避免同步调用。
+ **单点故障**：编排工具可能成为单点故障，因为它协调整个事务。在某些情况下，由于这个问题，首选 saga 编配模式。

## 实施
<a name="saga-orchestration-implementation"></a>

### 高级架构
<a name="saga-orchestration-implementation-high-level-arch"></a>

在下面的架构图中，saga 编排工具有三个参与方：订单服务、库存服务和付款服务。完成事务需要三个步骤：T1、T2 和 T3。saga 编排工具知道这些步骤并按要求的顺序运行它们。当步骤 T3 失败（付款失败）时，编排工具会运行补偿事务 C1 和 C2，将数据恢复到初始状态。

![\[Saga 编排工具高级架构\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-1.png)


当事务分布在多个数据库中时，您可以使用 [AWS Step Functions](https://aws.amazon.com/step-functions/) 来实现 saga 编排。

### 使用 AWS 服务实施
<a name="saga-orchestration-implementation-aws-services"></a>

示例解决方案使用 Step Functions 中的标准工作流程来实现 saga 编排模式。

![\[使用 Step Functions 实现 saga 工作流程\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-2.png)


当客户调用 API 时，也会调用 Lambda 函数，并在 Lambda 函数中进行预处理。该函数启动了 Step Functions 工作流程，以开始处理分布式事务。如果不需要预处理，则无需使用 Lambda 函数即可[直接从 API Gateway 启动 Step Functions 工作流程](https://serverlessland.com/patterns/apigw-sfn)。

使用 Step Functions 可以缓解单点故障问题，这是 saga 编排模式的实施所固有的。Step Functions 具有内置容错特性，可在每个亚马逊云科技区域的多个可用区中维护服务容量，以保护应用程序免受单个计算机或数据中心故障的影响。这有助于同时确保服务本身及其运行的应用程序工作流程的高可用性。

#### Saga Step Functions 工作流程
<a name="saga-orchestration-implementation-sfn"></a>

Step Functions 状态机允许您为模式实施配置基于决策的控制流要求。Step Functions 工作流程调用用于下单、库存更新和付款处理的各个服务以完成事务，并发送事件通知以便进一步处理。Step Functions 工作流程充当编排工具来协调事务。如果工作流程包含任何错误，则编排工具会运行补偿性事务，以跨服务维护数据完整性。

下图显示了 Step Functions 工作流程中运行的步骤。`Place Order`、`Update Inventory` 和 `Make Payment` 步骤指示了成功之路。下订单、更新库存并处理付款，然后再将 `Success` 状态返回给调用方。

`Revert Payment`、`Revert Inventory` 和 `Remove Order` Lambda 函数表示当工作流程中的任何步骤失败时，编排工具运行的补偿性事务。如果工作流程在 `Update Inventory` 步骤失败，则编排工具会在向调用方返回 `Fail` 状态之前调用 `Revert Inventory` 和 `Remove Order` 步骤。这些补偿事务确保数据完整性得到维护。库存恢复到其原始水平，将订单恢复。

![\[Saga Step Functions 工作流程\]](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-3.png)


### 代码示例
<a name="saga-orchestration-implementation-sample-code"></a>

以下示例代码显示了如何使用 Step Functions 创建 saga 编排工具。要查看完整的代码，请参阅此示例的[GitHub存储库](https://github.com/aws-samples/saga-orchestration-netcore-blog)。

#### 任务定义
<a name="saga-orchestration-task"></a>

```
var successState = new Succeed(this,"SuccessState");
var failState = new Fail(this, "Fail");

var placeOrderTask = new LambdaInvoke(this, "Place Order", new LambdaInvokeProps
{
    LambdaFunction = placeOrderLambda,
    Comment = "Place Order",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var updateInventoryTask = new LambdaInvoke(this,"Update Inventory", new LambdaInvokeProps
{
    LambdaFunction = updateInventoryLambda,
    Comment = "Update inventory",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var makePaymentTask = new LambdaInvoke(this,"Make Payment", new LambdaInvokeProps
{
    LambdaFunction = makePaymentLambda,
    Comment = "Make Payment",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var removeOrderTask = new LambdaInvoke(this, "Remove Order", new LambdaInvokeProps
{
    LambdaFunction = removeOrderLambda,
    Comment = "Remove Order",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(failState);

var revertInventoryTask = new LambdaInvoke(this,"Revert Inventory", new LambdaInvokeProps
{
    LambdaFunction = revertInventoryLambda,
    Comment = "Revert inventory",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(removeOrderTask);

var revertPaymentTask = new LambdaInvoke(this,"Revert Payment", new LambdaInvokeProps
{
    LambdaFunction = revertPaymentLambda,
    Comment = "Revert Payment",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(revertInventoryTask);

var waitState = new Wait(this, "Wait state", new WaitProps
{
    Time = WaitTime.Duration(Duration.Seconds(30))
}).Next(revertInventoryTask);
```

#### Step function 和状态机定义
<a name="saga-step"></a>

```
var stepDefinition = placeOrderTask
                .Next(new Choice(this, "Is order placed")
                    .When(Condition.StringEquals("$.Status", "ORDER_PLACED"), updateInventoryTask
                        .Next(new Choice(this, "Is inventory updated")
                            .When(Condition.StringEquals("$.Status", "INVENTORY_UPDATED"),
                                makePaymentTask.Next(new Choice(this, "Is payment success")
                                    .When(Condition.StringEquals("$.Status", "PAYMENT_COMPLETED"), successState)
                                    .When(Condition.StringEquals("$.Status", "ERROR"), revertPaymentTask)))
                            .When(Condition.StringEquals("$.Status", "ERROR"), waitState)))
                    .When(Condition.StringEquals("$.Status", "ERROR"), failState));

var stateMachine = new StateMachine(this, "DistributedTransactionOrchestrator", new StateMachineProps {
    StateMachineName = "DistributedTransactionOrchestrator",
    StateMachineType = StateMachineType.STANDARD,
    Role = iamStepFunctionRole,
    TracingEnabled = true,
    Definition = stepDefinition
});
```

### GitHub 存储库
<a name="saga-orchestration-implementation-github-repo"></a>

有关此模式示例架构的完整实现，请参见 GitHub 存储库，网址为[https://github.com/aws-samples/saga-orchestration-netcore-blog](https://github.com/aws-samples/saga-orchestration-netcore-blog)。

## 参考博客文章
<a name="saga-orchestration-blog"></a>
+ [使用 Saga 编排模式构建无服务器分布式应用程序](https://aws.amazon.com/blogs/compute/building-a-serverless-distributed-application-using-a-saga-orchestration-pattern/)

## 相关内容
<a name="saga-orchestration-resources"></a>
+ [Saga 编配模式](saga-choreography.md)
+ [事务发件箱模式](transactional-outbox.md)

## 视频
<a name="saga-orchestration-videos"></a>

以下视频讨论了如何使用 AWS Step Functions实现 saga 编排模式。


