持久编排

Durable Functions 是 Azure Functions 的扩展。 可以使用 协调器函数 来协调函数应用中其他 Durable 函数的执行。 编排器函数具有以下特征:

  • 协调器函数使用过程式代码定义函数工作流。 不需要声明性架构或设计器。
  • 业务流程协调器函数可以同步和异步调用其他持久化函数。 调用函数的输出可以可靠地保存到局部变量。
  • 协调器函数是持久且可靠的。 函数“awaits”或“yields”时,会自动检查执行进度。 当进程回收或 VM 重新启动时,本地状态永远不会丢失。
  • 协调器函数可以长时间运行。 业务流程实例的总生命周期可以是秒、天、月或永不结束。

本文概述了业务流程协调程序函数,以及它们如何帮助你解决各种应用开发难题。 如果不熟悉 Durable Functions 应用中可用的函数类型,请先阅读 Durable Functions 类型 文章。

业务流程标识

业务流程的每个 实例 都有一个实例标识符(也称为 实例 ID)。 默认情况下,每个实例 ID 都是自动生成的 GUID。 但是,实例 ID 也可以是任何用户生成的字符串值。 每个业务流程实例 ID 在 任务中心内必须是唯一的。

以下是有关实例 ID 的一些规则:

  • 实例 ID 必须介于 1 到 100 个字符之间。
  • 实例 ID 不得以 @ 开头。
  • 实例 ID 不得包含/\#?字符。
  • 实例 ID 不得包含控制字符。

注释

通常建议尽可能使用自动生成的实例 ID。 用户生成的实例 ID 适用于业务流程实例与某些外部应用程序特定的实体(例如采购订单或文档)之间存在一对一映射的情况。

此外,字符限制规则的实际强制实施可能会因应用使用的 存储提供程序 而异。 为了确保正确的行为和兼容性,强烈建议遵循前面列出的实例 ID 规则。

编排的实例 ID 是大多数 实例管理操作的必需参数。 它们对于诊断也很重要,例如在 Application Insights 中 搜索业务流程跟踪数据 以进行故障排除或分析。 因此,建议将生成的实例 ID 保存到某些外部位置(例如数据库或应用程序日志),以便稍后可以轻松引用它们。

Reliability

协调程序函数通过使用事件溯源设计模式可靠地维护其执行状态。 Durable Task Framework 使用仅追加的存储来记录函数编排执行的完整动作系列,而不是直接存储编排的当前状态。 仅追加存储与“转储”完整运行时状态相比,具有许多优势。 优点包括提高性能、可伸缩性和响应能力。 你还可以获得事务数据的最终一致性,以及完整的审核轨迹和历史记录。 审计跟踪支持可靠的补偿措施。

Durable Functions 以透明方式使用事件溯源。 幕后,await(C#)或yield(JavaScript/Python)运算符在协调程序函数中将协调程序线程的控制权返回到 Durable Task Framework 调度程序。 对于 Java,没有特殊语言关键字。 相反,调用 .await() 任务会通过自定义 Throwable将控制权返回给调度程序。 然后,调度程序将协调器函数计划的任何新操作(例如调用一个或多个子函数或安排持久计时器)提交到存储空间。 透明提交操作通过将所有新事件追加到存储中来更新编排实例的执行历史记录,类似于仅追加日志的方式。 同样,提交作会在存储中创建消息来计划实际工作。 此时,可以从内存中卸载协调器函数。 默认情况下,Durable Functions 使用 Azure 存储作为其运行时状态存储,但也支持其他 存储提供程序

如果业务流程函数得到更多工作要做(例如收到响应消息或持久计时器过期),业务流程协调程序将从一开始就唤醒并重新执行整个函数以重新生成本地状态。 在重播期间,如果代码尝试调用函数(或执行任何其他异步工作),Durable Task Framework 会查阅当前业务流程的执行历史记录。 如果发现 活动函数 已执行并生成结果,它将重播该函数的结果,业务流程协调程序代码继续运行。 重播会一直持续到函数代码完成或计划新的异步操作为止。

注释

为了使重播模式正常工作且可靠,业务流程协调程序函数代码必须 具有确定性。 非确定性业务流程协调程序代码可能会导致运行时错误或其他意外行为。 有关业务流程协调程序函数的代码限制的详细信息,请参阅 业务流程协调程序函数代码约束 文档。

注释

如果业务流程协调程序函数发出日志消息,重播行为可能会导致发出重复的日志消息。 请参阅 “日志记录 ”主题,详细了解此行为发生的原因以及如何解决此问题。

调度历史记录

Durable Task Framework 的事件溯源行为与你编写的业务流程协调程序函数代码紧密耦合。 假设你有一个活动链业务流程协调程序函数,如以下业务流程协调程序函数:

注释

Azure Functions 的 Node.js 编程模型版本 4 已正式发布。 新的 v4 模型旨在为 JavaScript 和 TypeScript 开发人员提供更灵活、更直观的体验。 在迁移指南中详细了解 v3 和 v4 之间的差异。

在以下代码片段中,JavaScript (PM4) 表示编程模型 V4,这是新的体验。

[FunctionName("HelloCities")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

每当活动函数被调度时,Durable Task Framework 会将函数的执行状态保存到某种持久性存储后端(默认情况下为 Azure 表存储)。 此状态称为 命令历史记录

历史记录表

一般来说,Durable Task Framework 在每个检查点执行以下操作:

  1. 将执行历史记录保存到持久存储中。
  2. 为编排器要调用的函数入队消息。
  3. 为协调程序本身调度消息,例如耐用的计时器消息。

检查点完成后,协调器函数可以自由地从内存中释放,直到有更多工作要执行。

注释

Azure 存储在将数据保存到表存储和队列之间不提供任何事务性保证。 为了处理故障,Durable Functions Azure 存储 提供方使用 最终一致性 模式。 这些模式可确保在检查点中间发生崩溃或连接丢失时不会丢失任何数据。 备用存储提供程序(如 Durable Functions MSSQL 存储提供程序)可以提供更严格的一致性保证。

完成后,前面显示的函数的历史记录类似于 Azure 表存储中的下表(为了说明目的而缩写):

PartitionKey (InstanceId) 事件类型 时间戳 Input Name 结果 状态
eaee885b 执行已开始 2021-05-05T18:45:28.852Z null HelloCities
eaee885b OrchestratorStarted 2021-05-05T18:45:32.362Z
eaee885b 任务计划 2021-05-05T18:45:32.670Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:32.670Z
eaee885b TaskCompleted 2021-05-05T18:45:34.201Z “”“Hello Tokyo!””
eaee885b OrchestratorStarted 2021-05-05T18:45:34.232Z
eaee885b 任务计划 2021-05-05T18:45:34.435Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.435Z
eaee885b TaskCompleted 2021-05-05T18:45:34.763Z “”“Hello Seattle!””
eaee885b OrchestratorStarted 2021-05-05T18:45:34.857Z
eaee885b 任务计划 2021-05-05T18:45:34.857Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.857Z
eaee885b TaskCompleted 2021-05-05T18:45:34.919Z “”“Hello London!””
eaee885b OrchestratorStarted 2021-05-05T18:45:35.032Z
eaee885b OrchestratorCompleted 2021-05-05T18:45:35.044Z
eaee885b 执行已完成 2021-05-05T18:45:35.044Z “[”“Hello Tokyo!”“,”Hello Seattle!“”,“”Hello London!“]” 完成

关于列值的几点说明:

  • PartitionKey:包含编排的实例 ID。
  • EventType:表示事件的类型。 可 在此处找到所有历史记录事件类型的详细说明。
  • 时间戳:历史记录事件的 UTC 时间戳。
  • 名称:已调用的函数的名称。
  • 输入:函数的 JSON 格式输入。
  • 结果:函数的输出;即其返回值。

警告

虽然它作为调试工具很有用,但不要依赖此表。 随着 Durable Functions 扩展的发展,它可能会发生变化。

每次在等待任务完成后恢复函数时,Durable Task Framework 都会重新从头运行协调器函数。 每次重新运行时,都会咨询执行历史记录,以确定当前异步任务是否已完成。 如果执行历史记录显示任务已完成,框架将重播该任务的输出,并转到下一个任务。 此过程将继续执行,直到整个执行历史记录被重播。 重播当前执行历史记录后,本地变量将还原到其以前的值。

功能和模式

接下来的部分介绍编排器函数的功能和模式。

子编排

编排器函数可以调用活动函数,也可以调用其他编排器函数。 例如,可以从协调程序函数库中构建更大的编排。 或者,可以并行运行业务流程协调程序函数的多个实例。

有关详细信息和示例,请参阅 子业务流程 文章。

持久计时器

编排可以调度持久计时器来实现延迟或设置异步操作的超时处理。 在业务流程协调程序函数中使用持久计时器,而不是语言原生的“睡眠”API。

有关详细信息和示例,请参阅 Durable timers 文章。

外部事件

协调器函数可以等待外部事件来更新编排实例。 此 Durable Functions 功能通常用于处理人工交互或其他外部回调。

有关详细信息和示例,请参阅 “外部事件 ”一文。

错误处理

业务流程协调程序函数可以使用编程语言的错误处理功能。 编排代码支持现有的模式 try/catch

业务流程编排器函数还可以为它们调用的活动或子业务流程编排器函数添加重试策略。 如果活动或子业务流程协调程序函数失败并出现异常,则指定的重试策略可以自动延迟并重试执行,最多可以指定次数。

注释

如果协调器函数中存在未经处理的异常,协调实例将处于 Failed 状态。 编排实例一旦失败,将无法重试。

有关详细信息和示例,请参阅 错误处理 文章。

关键部分 (Durable Functions 2.x,当前仅限 .NET)

业务流程实例是单线程的,因此无需担心业务流程中的竞争条件。 但是,当编排与外部系统交互时,竞争条件是可能的。 若要在与外部系统交互时缓解竞争条件,协调器函数可以使用 .NET 中的方法定义关键节LockAsync

以下示例代码显示了定义关键部分的业务流程协调程序函数。 它使用 LockAsync 方法进入关键部分。 此方法需要传递一个或多个对 持久实体的引用,该实体可持久管理锁定状态。 只有一个此编排的实例可以在同一时间在临界区中执行代码。

[FunctionName("Synchronize")]
public static async Task Synchronize(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var lockId = new EntityId("LockEntity", "MyLockIdentifier");
    using (await context.LockAsync(lockId))
    {
        // critical section - only one orchestration can enter at a time
    }
}

获取 LockAsync 持久锁,并返回一个 IDisposable,该 IDisposable 在释放时结束关键部分。 此 IDisposable 结果可与 using 块一起使用,以获取关键部分的语法表示形式。 当业务流程协调程序函数进入关键部分时,只有一个实例可以执行该代码块。 在上一个实例退出关键节之前,将阻止尝试输入关键节的任何其他实例。

关键节功能也可用于协调对持久化实体的更改。 有关关键部分的详细信息,请参阅 Durable 实体“实体协调” 主题。

注释

Durable Functions 2.0 中提供了关键区段。 目前,只有 .NET 进程内协调实现这个功能。 实体和临界区在 Durable Functions 中尚不可用于 dotnet 隔离工作器。

调用 HTTP 终结点 (Durable Functions 2.x)

业务流程协调程序函数不允许执行 I/O,如 业务流程协调程序函数代码约束中所述。 此限制的典型解决方法是包装任何需要在活动函数中执行 I/O 的代码。 与外部系统交互的业务流程经常使用活动函数进行 HTTP 调用,并将结果返回到业务流程。

为了简化这种常见模式,业务流程协调程序函数可以使用 CallHttpAsync 该方法直接调用 HTTP API。

[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Makes an HTTP GET request to the specified endpoint
    DurableHttpResponse response = 
        await context.CallHttpAsync(HttpMethod.Get, url);

    if ((int)response.StatusCode == 400)
    {
        // handling of error codes goes here
    }
}

除了支持基本请求/响应模式外,该方法还支持自动处理常见的异步 HTTP 202 轮询模式,并支持使用 托管标识通过外部服务进行身份验证。

有关详细信息和详细示例,请参阅 HTTP 功能 文章。

注释

可在 Durable Functions 2.0 及更高版本中直接从业务流程协调程序函数调用 HTTP 终结点。

传递多个参数

无法将多个参数直接传递给活动函数。 建议传入对象或复合对象的数组。

在 .NET 中,也可以使用 ValueTuple 对象。 以下示例使用通过 C# 7 添加的 ValueTuple 的新功能:

[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string major = "ComputerScience";
    int universityYear = context.GetInput<int>();

    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations",
        (major, universityYear));
    return courseRecommendations;
}

[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
    // parse input for student's major and year in university
    (string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();

    // retrieve and return course recommendations by major and university year
    return new
    {
        major = studentInfo.Major,
        universityYear = studentInfo.UniversityYear,
        recommendedCourses = new []
        {
            "Introduction to .NET Programming",
            "Introduction to Linux",
            "Becoming an Entrepreneur"
        }
    };
}

后续步骤