我如何对 Cadence 工作流程进行版本控制?
How can I version Cadence workflows?
Cadence 工作流必须是确定性的,这意味着如果使用相同的输入参数执行工作流,则预计会产生完全相同的结果。
当我作为一名新的 Cadence 用户了解上述要求时,我想知道在需要破坏确定性的更改时如何在长期 运行 中维护工作流程。
一个示例场景是,您有一个连续执行 Activity1 和 Activity2 的工作流,然后您需要更改这些活动的顺序,以便工作流在 Activtiy1 之前执行 Activity2。还有许多其他方法可以像这样进行破坏确定性的更改,我想了解如何处理这些更改。
这在工作流程可以 运行 长时间(例如几天、几周甚至几个月)的情况下尤为重要!
显然,这可能是新 Cadence 开发人员提出的最常见问题之一。 Cadence 工作流程需要 deterministic algorithms。如果工作流算法不是确定性的,Cadence worker 在尝试重放历史时(即在 worker 故障恢复期间)将面临遇到 nondeterministic workflow 错误的风险。
有两种方法可以解决这个问题:
- 创建全新的工作流程:这是最幼稚的方法
版本控制工作流程。该方法听起来很简单:随时
你需要改变你的工作流程的算法,你做一个
复制你的原始工作流程并按照你想要的方式编辑它,给它
一个新名称,例如 MyWorkflow_V2 并开始用于所有新实例
往前走。如果您的工作流程不是很长寿,您的
现有的工作流程将在某个时候“耗尽”,您将能够
完全删除旧版本。另一方面,这
方法可以很快变成维护噩梦
显而易见的原因。
- 使用 GetVersion() API 分叉工作流逻辑:Cadence 客户端有
一个名为 GetVersion 的函数,它告诉您版本是什么
工作流程目前 运行ning。您可以使用 returned 的信息
通过这个函数来决定你的工作流算法的版本
需要使用。换句话说,您的工作流程同时具有旧的和
新算法 运行ning 并排,您可以选择
适合您的工作流程实例的正确版本,以确保它们 运行
确定性地。
下面是一个基于 GetVersion() 方法的示例。假设您要更改工作流程中的以下行:
err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
到
err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
这是一个重大变化,因为它 运行 是 bar activity 而不是 foo。如果您只是简单地进行更改而不用担心确定性,您的工作流将无法在需要时重播,并且它们将陷入 不确定性工作流 错误。正确进行此更改的正确方法是按如下方式更新工作流程:
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 是一个常量,仅表示 版本 0。在其他情况下
话,第一个版本。它作为 minSupportedVersion 传递
GetVersion 函数的参数
- 1 是您当前可以处理的 maxSupportedVersion
工作流代码。在这种情况下,我们的算法可以支持工作流
版本从 DefaultVersion 到 Version 1(含)。
当此工作流的新实例首次调用上面的 GetVersion() 时,该函数将 return maxSupportedVersion 参数,以便您可以 运行你的工作流算法的最新版本。同时,它还会在工作流历史记录中记录该版本号(内部称为 Marker Event),以便将来记住。稍后重放此工作流程时,即使您传递了不同的 maxSupportedVersion 参数,Cadence 客户端也会保持 returning 相同的版本号(即,如果您的工作流程有更多版本) .
如果在历史回放期间遇到 GetVersion 调用并且历史没有之前记录的标记事件,该函数将 return 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)
}
此更改后,如果您的工作流代码 运行s 用于具有 DefaultVersion 版本的旧工作流实例,Cadence 客户端将引发错误并停止执行。
最终,您可能想要摆脱所有以前的版本,只支持最新版本。一种选择是完全摆脱 GetVersion 调用和 if 语句,只用一行代码来做正确的事情。然而,将 GetVersion() 调用保留在那里实际上是一个更好的主意,原因有二:
- GetVersion() 让您更好地了解哪里出了问题,如果您
worker 尝试重播旧工作流实例的历史记录。
而不是调查一个神秘的根本原因
不确定的工作流错误,你会知道失败是
由此位置的工作流版本控制引起。
- 如果您需要对您的同一步骤进行更多重大更改
工作流算法,您将能够重用相同的 Change 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)
Cadence 工作流必须是确定性的,这意味着如果使用相同的输入参数执行工作流,则预计会产生完全相同的结果。
当我作为一名新的 Cadence 用户了解上述要求时,我想知道在需要破坏确定性的更改时如何在长期 运行 中维护工作流程。
一个示例场景是,您有一个连续执行 Activity1 和 Activity2 的工作流,然后您需要更改这些活动的顺序,以便工作流在 Activtiy1 之前执行 Activity2。还有许多其他方法可以像这样进行破坏确定性的更改,我想了解如何处理这些更改。
这在工作流程可以 运行 长时间(例如几天、几周甚至几个月)的情况下尤为重要!
显然,这可能是新 Cadence 开发人员提出的最常见问题之一。 Cadence 工作流程需要 deterministic algorithms。如果工作流算法不是确定性的,Cadence worker 在尝试重放历史时(即在 worker 故障恢复期间)将面临遇到 nondeterministic workflow 错误的风险。
有两种方法可以解决这个问题:
- 创建全新的工作流程:这是最幼稚的方法 版本控制工作流程。该方法听起来很简单:随时 你需要改变你的工作流程的算法,你做一个 复制你的原始工作流程并按照你想要的方式编辑它,给它 一个新名称,例如 MyWorkflow_V2 并开始用于所有新实例 往前走。如果您的工作流程不是很长寿,您的 现有的工作流程将在某个时候“耗尽”,您将能够 完全删除旧版本。另一方面,这 方法可以很快变成维护噩梦 显而易见的原因。
- 使用 GetVersion() API 分叉工作流逻辑:Cadence 客户端有 一个名为 GetVersion 的函数,它告诉您版本是什么 工作流程目前 运行ning。您可以使用 returned 的信息 通过这个函数来决定你的工作流算法的版本 需要使用。换句话说,您的工作流程同时具有旧的和 新算法 运行ning 并排,您可以选择 适合您的工作流程实例的正确版本,以确保它们 运行 确定性地。
下面是一个基于 GetVersion() 方法的示例。假设您要更改工作流程中的以下行:
err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil)
到
err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil)
这是一个重大变化,因为它 运行 是 bar activity 而不是 foo。如果您只是简单地进行更改而不用担心确定性,您的工作流将无法在需要时重播,并且它们将陷入 不确定性工作流 错误。正确进行此更改的正确方法是按如下方式更新工作流程:
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 是一个常量,仅表示 版本 0。在其他情况下 话,第一个版本。它作为 minSupportedVersion 传递 GetVersion 函数的参数
- 1 是您当前可以处理的 maxSupportedVersion 工作流代码。在这种情况下,我们的算法可以支持工作流 版本从 DefaultVersion 到 Version 1(含)。
当此工作流的新实例首次调用上面的 GetVersion() 时,该函数将 return maxSupportedVersion 参数,以便您可以 运行你的工作流算法的最新版本。同时,它还会在工作流历史记录中记录该版本号(内部称为 Marker Event),以便将来记住。稍后重放此工作流程时,即使您传递了不同的 maxSupportedVersion 参数,Cadence 客户端也会保持 returning 相同的版本号(即,如果您的工作流程有更多版本) .
如果在历史回放期间遇到 GetVersion 调用并且历史没有之前记录的标记事件,该函数将 return 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)
}
此更改后,如果您的工作流代码 运行s 用于具有 DefaultVersion 版本的旧工作流实例,Cadence 客户端将引发错误并停止执行。
最终,您可能想要摆脱所有以前的版本,只支持最新版本。一种选择是完全摆脱 GetVersion 调用和 if 语句,只用一行代码来做正确的事情。然而,将 GetVersion() 调用保留在那里实际上是一个更好的主意,原因有二:
- GetVersion() 让您更好地了解哪里出了问题,如果您 worker 尝试重播旧工作流实例的历史记录。 而不是调查一个神秘的根本原因 不确定的工作流错误,你会知道失败是 由此位置的工作流版本控制引起。
- 如果您需要对您的同一步骤进行更多重大更改 工作流算法,您将能够重用相同的 Change 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)