閱讀英文

共用方式為

Durable Functions 中的版本控制挑战和方法(Azure Functions)

在应用程序的生存期内,将不可避免地添加、删除和更改函数。 Durable Functions 允许以以前不可能的方式将函数链接在一起,并且此链接会影响如何处理版本控制。

重大更改的类型

有几个需要警惕的破坏性更改示例。 本文讨论最常见的方法。 所有这些背后的主主题是,新的和现有的函数编排都会受到函数代码更改的影响。

更改活动或实体函数签名

签名更改是指函数的名称、输入或输出中的更改。 如果对活动或实体函数进行此类更改,它可能会中断依赖于它的任何协调器函数。 这尤其适用于类型安全的语言。 如果更新业务流程协调程序函数以适应此更改,则可能会中断现有的正在进行的实例。

例如,假设我们有以下编排器函数。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

此简单函数采用 Foo 的结果并将其传递给 Bar。 假设我们需要将 Foo 的返回值从布尔值更改为 String,以支持更广泛的结果值。 结果如下所示:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

此更改会应用于业务流程协调程序函数的所有新实例,但会中断任何正在进行的实例。 例如,请考虑这样一种情况:业务流程实例调用名为 Foo 的函数并先后返回布尔值和检查点。 如果此时部署签名更改,则检查点实例在恢复和重播对 Foo 的调用时将立即失败。 发生此失败的原因是历史记录表中的结果是布尔值,但新代码尝试将其反序列化为字符串值,从而导致类型安全语言出现意外行为,甚至运行时异常。

此示例只是函数签名更改可以中断现有实例的多种不同方法之一。 通常,如果业务流程协调程序需要更改调用函数的方式,则更改可能会有问题。

更改协调器逻辑

另一类版本控制问题来自更改协调器功能代码,从而改变正在执行的实例的执行路径。

请考虑以下协调器函数:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

现在假设你想要进行更改,以在两个现有函数调用之间添加新的函数调用。

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    if (result)
    {
        await context.CallActivityAsync("SendNotification");
    }

    await context.CallActivityAsync("Bar", result);
}

此更改在 FooBar 之间添加了对 SendNotification 的新函数调用。 没有签名更改。 在从对“Bar”的调用中恢复现有实例时,将出现问题。 在重播过程中,如果对“Foo”的原始调用返回 ,则业务流程协调程序重播将调用不在其执行历史记录中的“SendNotification”true。 运行时检测到这种不一致,并引发非确定性业务流程错误,因为在它希望出现对 Bar 的调用时,出现了对 SendNotification 的调用。 向其他持久作添加 API 调用(例如创建持久计时器、等待外部事件、调用子业务流程等)时,可能会出现相同的问题。

缓解策略

下面是处理版本控制挑战的一些策略:

  • 不采取任何操作(不推荐)
  • 业务流程版本控制(在大多数情况下推荐)
  • 停止所有正在进行的实例
  • 并行部署

不做任何事情

版本控制最简单的方法是不执行任何操作,让正在进行的业务流程实例失败。 根据更改的类型,可能会发生以下类型的故障。

  • 协调过程可能会因为 非确定性协调过程 错误而失败。
  • 业务流程可能会无限期停滞,报告 Running 状态。
  • 如果删除某个函数,则尝试调用它的任何函数都可能会失败并出现错误。
  • 如果在计划运行函数后删除该函数,则应用可能会在 Durable Task Framework 引擎中遇到低级运行时故障,这可能会导致性能严重下降。

由于这些潜在的故障,不建议使用“无作为”策略。

业务流程版本控制

注意

业务流程版本控制目前为公共预览版。

业务流程版本控制功能允许不同版本的业务流程同时共存和执行,且不会出现冲突和非确定性问题,从而可以部署更新,同时允许外部业务流程实例在不手动干预的情况下完成。

使用业务流程版本控制:

  • 每个业务流程实例在创建时都会永久关联一个版本
  • 业务流程协调程序函数可相应检查其版本和分支执行情况
  • 运行更新的协调器功能版本的工作人员可以继续执行由旧版本创建的编排实例
  • 运行时会阻止运行旧版协调器函数的辅助角色执行新版的业务流程

建议将此策略用于需要支持中断性变更的应用程序,同时保持 零停机时间部署。 此功能目前可用于 .NET 隔离应用程序。

有关详细的配置和实现指南,请参阅 Durable Functions 中的业务流程版本控制

停止所有正在进行的实例

另一个选项是停止所有正在进行的实例。 如果您使用 Durable Functions 的默认 Azure 存储提供程序,可以通过清除内部 控制队列工作项队列 的内容来停止所有实例。 或者可以停止函数应用,删除这些队列,然后再次重启应用。 应用重启后,将自动重新创建队列。 以前的业务流程实例可能无限期地保持“正在运行”状态,但它们不会使日志与故障消息混乱,也不会对应用造成任何损害。 此方法非常适合快速原型开发,包括本地开发。

注意

此方法需要直接访问基础存储资源,并且可能不适合 Durable Functions 支持的所有存储提供程序。

并行部署

确保破坏性变更安全部署的最可靠方法是将它们与旧版本同时部署。 可以使用以下任一技术来完成此作:

  • 将所有更新部署为全新的函数,保持现有函数 as-is不变。 通常不推荐这样做,因为递归更新调用新函数版本的过程非常复杂。
  • 将所有更新部署为具有不同存储帐户的新函数应用。
  • 使用同一存储帐户部署函数应用的新的复本,并且任务中心名称已更新。 这会导致创建新版本的应用可使用的新存储项目。 旧版应用将继续使用以前的存储项目集执行。

建议使用并行部署技术来发布您的函数应用的新版本。

注意

有关并行部署策略的本指南使用特定于 Azure 存储的术语,但通常适用于所有支持的 Durable Functions 存储提供程序

部署槽

在 Azure Functions 或 Azure 应用服务中并行部署时,建议将新版本的函数应用部署到新的 部署槽位。 通过部署槽位,可以并行运行函数应用的多个副本,且仅其中一个槽位为活动生产槽位。 当准备好向现有基础结构公开新业务流程逻辑时,它可以像将新版本交换到生产槽一样简单。

注意

当你对业务流程协调程序函数使用 HTTP 和 Webhook 触发器时,此策略最有效。 对于非 HTTP 触发器(例如队列或事件中心),触发器定义应 派生自 在交换作过程中更新的应用设置。

后续步骤