【问题标题】:How can I version Cadence workflows?如何对 Cadence 工作流程进行版本控制?
【发布时间】:2020-04-19 01:23:11
【问题描述】:

Cadence 工作流程必须具有确定性,这意味着如果使用相同的输入参数执行工作流程,则预期会产生完全相同的结果。

当我作为一个新的 Cadence 用户了解上述要求时,我想知道在需要进行确定性破坏性更改时如何长期维护工作流程。

一个示例场景是,您有一个连续执行 Activity1 和 Activity2 的工作流,然后您需要更改这些活动的顺序,以便工作流在 Activtiy1 之前执行 Activity2。还有许多其他方法可以进行像这样的破坏性更改,我想了解如何处理这些更改。

这在工作流可以长时间运行(例如几天、几周甚至几个月)的情况下尤其重要!

【问题讨论】:

    标签: cadence-workflow


    【解决方案1】:

    显然,这可能是新的 Cadence 开发人员最常问的问题之一。 Cadence 工作流程必须为deterministic algorithms。如果工作流算法不是确定性的,Cadence 工作人员在尝试重放历史记录时(即在工作人员故障恢复期间)将面临遇到非确定性工作流程错误的风险。

    有两种方法可以解决这个问题:

    • 创建全新的工作流程:这是最幼稚的方法 版本控制工作流程。这种方法听起来很简单:随时 你需要改变你的工作流程的算法,你做一个 原始工作流程的副本并按照您想要的方式对其进行编辑,给它 像 MyWorkflow_V2 这样的新名称并开始用于所有新实例 往前走。如果您的工作流程不是很长,您的 现有的工作流程将在某个时候“耗尽”,您将能够 完全删除旧版本。另一方面,这 方法可以很快变成维护的噩梦 显而易见的原因。
    • 使用 GetVersion() API 派生工作流逻辑:Cadence 客户端具有 一个名为 GetVersion 的函数,它告诉你什么版本的 工作流当前正在运行。您可以使用返回的信息 通过这个函数来决定你的工作流算法的版本 需要使用。换句话说,您的工作流程既有旧的,也有 新算法并行运行,您可以选择 适合您的工作流实例的正确版本,以确保它们运行 确定性地。

    以下是基于 GetVersion() 方法的示例。假设您要在工作流程中更改以下行:

    err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
    

    err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
    

    这是一项重大更改,因为它运行的是 bar 活动而不是 foo。如果您只是在不担心确定性的情况下进行更改,那么您的工作流程将无法在需要时重播,并且会遇到 nondeterministic workflow 错误。正确进行此更改的正确方法是更新工作流程,如下所示:

    v :=  GetVersion(ctx, "fooChange", DefaultVersion, 1)
    if v  == DefaultVersion {
       err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
    } else {
       err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
    }
    

    GetVersion 函数接受 4 个参数:

    • ctx 是标准的上下文对象
    • “fooChange” 是人类可读的 ChangeID 或您在工作流算法中所做的语义更改,会破坏确定性
    • DefaultVersion 是一个常量,仅表示 Version 0。在其他 话,第一个版本。它作为 minSupportedVersion 传递 GetVersion 函数的参数
    • 1 是您当前可以处理的 ma​​xSupportedVersion 工作流代码。在这种情况下,我们的算法可以支持工作流 从 DefaultVersionVersion 1(含)的版本。

    当此工作流的新实例首次到达上述 GetVersion() 调用时,该函数将返回 ma​​xSupportedVersion 参数,以便您可以运行最新版本的工作流算法。同时,它还会将该版本号记录在工作流历史记录中(内部称为标记事件),以便将来记住。稍后重放此工作流程时,Cadence 客户端将继续返回相同的版本号,即使您传递了不同的 ma​​xSupportedVersion 参数(即,如果您的工作流程有更多版本)。

    如果在历史回放期间遇到 GetVersion 调用,并且历史没有之前记录的标记事件,则该函数将返回 DefaultVersion,并假设 “ fooChange” 从未存在于此工作流实例的上下文中。

    如果您需要在工作流程的同一步骤中再进行一项重大更改,您只需像这样更改上面的代码:

    v :=  GetVersion(ctx, "fooChange", DefaultVersion, 2) // Note the new max version
    if v  == DefaultVersion {
       err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
    } else if v == 1 {
       err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
    } else { // This is the Version 2 logic
       err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
    }
    

    当您对放弃对 版本 0 的支持感到满意时,您可以像这样更改上面的代码:

    v :=  GetVersion(ctx, "fooChange", 1, 2) // DefaultVersion is no longer supported
    if v == 1 {
       err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
    } else { 
       err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil)
    }
    

    在此更改之后,如果您的工作流代码为具有 DefaultVersion 版本的旧工作流实例运行,Cadence 客户端将引发错误并停止执行。

    最终,您可能希望摆脱所有以前的版本,只支持最新版本。一种选择是完全摆脱 GetVersion 调用和 if 语句,只需一行代码即可完成正确的操作。但是,将 GetVersion() 调用保留在其中实际上是一个更好的主意,原因有两个:

    1. GetVersion() 可以让您更好地了解问题所在,如果您的 worker 尝试重放旧工作流实例的历史记录。 而不是调查一个神秘事件的根本原因 非确定性工作流错误,你会知道失败是 由该位置的工作流版本控制引起的。
    2. 如果您需要对同一步骤进行更多重大更改 工作流算法,您将能够重用相同的变更 ID 和 继续遵循与上述相同的模式。

    考虑到上面提到的两个原因,当需要放弃支持所有旧版本时,您应该更新您的工作流代码,如下所示:

    GetVersion(ctx, "fooChange", 2, 2) // This acts like an assertion to give you a proper error
    err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil) 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-08
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 2017-03-22
      • 2011-10-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多