业务流程协调程序函数代码约束

Durable Functions 是 Azure Functions 的一个扩展,用于构建有状态应用。 可以使用业务流程协调程序函数来协调函数应用中其他持久函数的执行。 业务流程协调程序函数带有状态且可靠,可以长时间运行。

业务流程协调程序代码约束

业务流程协调程序函数使用事件溯源来确保执行的可靠性,并保留本地变量状态。 业务流程协调程序代码的重播行为针对可在业务流程协调程序函数中编写的代码类型创建约束。 例如,业务流程协调程序函数必须具有确定性:业务流程协调程序函数将重播多次,每次必须生成相同的结果。

使用确定性 API

本部分提供一些简单的指导原则,用于帮助确保代码是确定性的。

业务流程协调程序函数可以采用目标语言调用任何 API。 但是,必须确保业务流程协调程序函数只调用确定性 API。 确定性 API 是一种在输入相同时始终返回同一值的 API,不管何时调用,也不管调用频率如何。

以下部分提供有关应避免使用的 API 和模式的指导,因为它们不具有确定性。 这些限制仅适用于业务流程协调程序函数。 其他函数类型没有此类限制。

注意

下方介绍了多种代码约束类型。 很遗憾此列表并不全面,且某些用例可能未涵盖。 编写业务流程协调程序代码时要考虑的最重要事项是使用的 API 是否具有确定性。 适应这种思维方式后,就很容易了解哪些 API 可以安全使用,哪些不可以,而无需参考此记录列表。

日期和时间

返回当前日期或时间的 API 不具有确定性,不应在业务流程协调程序函数中使用。 这是因为每次业务流程协调程序函数重播都会生成不同的值。 应改用 Durable Functions 等效 API 来获取当前日期或时间,这在重播中会保持一致。

请勿使用 DateTime.NowDateTime.UtcNow 或等效 API 获取当前时间。 还应避免 Stopwatch 等类。 对于 .NET 进程内业务流程协调程序函数,请使用 IDurableOrchestrationContext.CurrentUtcDateTime 属性来获取当前时间。 对于 .NET 独立业务流程协调程序函数,请使用 TaskOrchestrationContext.CurrentDateTimeUtc 属性来获取当前时间。

DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);

GUID 和 UUID

返回随机 GUID 或 UUID 的 API 是非确定性的,因为每次重播时它们生成的值都不相同。 根据所使用的语言,用于生成确定性 GUID 或 UUID 的内置 API 可能可用。 否则,请使用活动函数返回随机生成的 GUID 或 UUID。

请勿使用 Guid.NewGuid() 之类的 API 来生成随机 GUID。 请改用上下文对象的 NewGuid() API 生成一个可安全进行业务流程协调程序重播的随机 GUID。

Guid randomGuid = context.NewGuid();

注意

使用业务流程上下文 API 生成的 GUID 是类型 5 UUID

随机数

使用活动函数将随机数返回到业务流程协调程序函数。 就重播来说,活动函数的返回值始终是安全的,因为这些值会保存到业务流程历史记录中。

或者,可以在业务流程协调程序函数中直接使用具有固定种子值的随机数生成器。 只要为每次业务流程重播生成相同的数字序列,此方法就是安全的。

绑定

业务流程协调程序函数不得使用任何绑定,即使是业务流程客户端实体客户端绑定也不行。 始终使用客户端或活动函数中的输入和输出绑定。 这一点很重要,因为可能会多次重播业务流程协调程序函数,从而导致外部系统的非确定性和重复 I/O。

静态变量

避免在业务流程协调程序函数中使用静态变量,因为其值可能随时间而变,导致非确定性的运行时行为。 改为使用常数,或者将静态变量的使用限制为活动函数。

注意

除了业务流程协调程序函数之外,由于各种原因,在 Azure Functions 中使用静态变量也可能出现问题,因为无法保证静态状态会在多个函数执行中持续存在。 除非在非常特定的用例中,例如在活动或实体函数中尽量在内存中缓存,否则应避免使用静态变量。

环境变量

请勿在业务流程协调程序函数中使用环境变量。 其值可能随时间而变,导致非确定性的运行时行为。 如果业务流程协调程序函数需要环境变量中定义的配置,必须将配置值作为输入或作为活动函数的返回值传递到业务流程协调程序函数中。

网络和 HTTP

使用活动函数进行出站网络调用。 如果需要从业务流程协调程序函数进行 HTTP 调用,则也可使用持久性 HTTP API

线程阻止 API

阻止“sleep”之类的 API 可能导致业务流程协调程序函数出现性能和缩放问题,应该避免使用。 在 Azure Functions 消耗计划中,它们甚至可能导致不必要的执行时收费。 在适用的情况下使用阻止 API 的替代方案。 例如,使用持久计时器创建可安全重播的延迟,不计入业务流程协调程序函数的执行时间。

异步 API

除了业务流程触发器的上下文对象定义的操作之外,业务流程协调程序代码不得启动任何异步操作。 例如,不得在 .NET 中使用 Task.RunTask.DelayHttpClient.SendAsync,不得在 JavaScript 中使用 setTimeoutsetInterval。 业务流程协调程序函数应仅使用持久 SDK API 计划异步工作,例如计划活动函数。 应在活动函数内执行任何其他类型的异步调用。

异步 JavaScript 函数

始终将 JavaScript 业务流程协调程序函数声明为同步生成器函数。 不能将 JavaScript 业务流程协调程序函数声明为 async,因为 Node.js 运行时无法保证异步函数具有确定性。

Python 协同例程

不能将 Python 业务流程协调程序函数声明为协同例程。 换句话说,不能使用 async 关键字声明 Python 业务流程协调程序函数,因为协同例程语义与 Durable Functions 重播模型不一致。 始终将 Python 业务流程协调程序函数声明为生成器,意味着希望 context API 使用 yield 而不是 await

.NET 线程 API

Durable Task Framework 在单个线程上运行业务流程协调程序代码,不能与任何其他线程交互。 在工作池线程上运行异步延续,业务流程的执行可能会导致非确定性的执行或死锁。 因此,在绝大多数情况下,业务流程协调程序函数不应使用线程 API。 例如,不得在业务流程协调程序函数中使用 ConfigureAwait(continueOnCapturedContext: false)。 这可确保任务延续在业务流程协调程序函数的原始 SynchronizationContext 上运行。

注意

Durable Task Framework 尝试检测业务流程协调程序函数中意外使用非业务流程协调程序线程的情况。 如果发现违规,该框架将引发 NonDeterministicOrchestrationException 异常。 但是,此检测行为不会捕获所有违规,因此请不要对它有依赖。

版本控制

持久性业务流程可能会连续运行数天、数月、数年,甚至会永久执行。 对 Durable Functions 应用进行代码更新时,如果此类更新影响尚未完成的业务流程,则可能会中断业务流程的重播行为。 因此,在更新代码时请仔细进行计划,这很重要。 有关如何进行代码版本控制的更详细说明,请参阅版本控制一文。

持久任务

注意

本部分介绍 Durable Task Framework 的内部实现详细信息。 在不了解这些信息的情况下也可以使用 Durable Functions。 本部分旨在帮助读者了解重播行为。

可在业务流程协调程序函数中安全等待的任务有时称为“持久任务”。 这些任务由 Durable Task Framework 创建和管理。 示例是由 .NET 业务流程协调程序函数中的 CallActivityAsyncWaitForExternalEventCreateTimer 返回的任务。

可以在 .NET 中使用 TaskCompletionSource 对象的列表对这些持久任务进行内部管理。 在重播期间,这些任务在业务流程协调程序代码执行过程中予以创建。 在调度程序枚举相应历史记录事件时,这些任务将会完成。

这些任务通过单个线程以同步方式执行,直至重播完所有历史记录。 在历史记录重播结束时尚未完成的任务会执行相应的操作。例如,可能会将一条消息排队,以调用活动函数。

本部分的运行时行为说明应可帮助你理解业务流程协调程序函数为何不能在非持久任务中使用 awaityield。 有两个原因:调度程序线程无法等待该任务完成,并且该任务发出的任何回调可能会损坏业务流程协调程序函数的跟踪状态。 进行某些运行时检查是为了帮助检测此类违规。

若要详细了解 Durable Task Framework 如何执行业务流程协调程序函数,请查阅 GitHub 上的持久任务源代码。 具体而言,请参阅 TaskOrchestrationExecutor.csTaskOrchestrationContext.cs

后续步骤