业务流程协调程序函数将其他函数的执行作为基于代码的工作流进行协调。 编排器函数具有以下特征:
- 它们使用过程代码定义函数工作流。 不需要声明性架构或设计器。
- 它们可以同步和异步调用其他函数。 调用函数的输出可以保存到局部变量。
- 它们设计为耐用且可靠。 当函数调用
await或yield运算符时,执行进度会自动保存为检查点。 进程回收或 VM 重新启动时,本地状态不会丢失。 - 它们可以长时间运行。 业务流程实例的总生命周期可以是秒、天或月,也可以将实例配置为永不结束。
本文概述了业务流程协调程序函数,以及它们如何帮助你解决不同的应用开发难题。
有关Durable Functions应用中可用的函数类型的信息,请参阅 可计算任务编程模型。
持久任务 SDK 提供与 Durable Functions 相同的编排功能,但作为由 持久任务调度器 支持的独立应用程序运行。
业务流程标识
业务流程的每个 实例 都有一个实例标识符,也称为 实例 ID。 默认情况下,每个实例 ID 都是自动生成的全局唯一标识符(GUID)。 但是,实例 ID 也可以是任何用户生成的字符串值。 每个业务流程实例 ID 在 任务中心内必须是唯一的。
以下规则适用于实例 ID:
- 它们必须介于 1 到 100 个字符之间。
- 他们绝不能以
@开始。 - 它们不得包含
/、\或#?字符。 - 它们不得包含控制字符。
注释
尽可能使用自动生成的实例 ID。 用户生成的实例 ID 适用于业务流程实例与外部应用程序特定的实体(例如采购订单或文档)之间存在一对一映射的情况。
注释
根据应用使用的 存储提供程序 ,字符限制规则的实际强制实施可能会有所不同。 为了帮助确保正确的行为和兼容性,请遵循前面的实例 ID 规则。
编排的实例 ID 是大多数 实例管理操作的必需参数。 实例 ID 对于诊断也很重要。 例如,在 Application Insights 中 搜索业务流程跟踪数据 以进行故障排除或分析时,可以使用它们。 因此,将生成的实例 ID 保存到外部位置,以便稍后轻松引用它们,例如数据库或应用程序日志。
编排的实例 ID 是大多数 实例管理操作的必需参数。 实例 ID 对于诊断也很重要,因此,将生成的实例 ID 保存到外部位置,以便稍后轻松引用它们,例如数据库或应用程序日志。
Reliability
协调器函数使用事件源设计模式来可靠地维护其执行状态。 Durable Task Framework 使用仅追加的存储来记录函数编排执行的完整动作系列,而不是直接存储编排的当前状态。 与 导出 完整运行时状态相比,追加写入存储具有许多优势。 优点包括提高性能、可伸缩性和响应能力。 还可以获得事务数据、完整审核线索和历史记录的最终一致性。 审计跟踪支持可靠的补偿措施。
Durable Task Framework 以透明方式使用事件溯源。 在幕后,编排器函数在 C# 中使用 await 运算符,在 JavaScript 和 Python 中使用 yield 运算符。 这些运算符将协调器线程的控制返回给 Durable Task Framework 调度程序。 在 Java 中,调用任务上的 .await() 会通过 Throwable 的自定义实例将控制权返回给调度程序。 然后,调度程序将协调程序函数计划的任何新的操作提交至存储。 示例动作包括调用一个或多个子函数或安排一个持久性计时器。 将所有新事件追加到存储中,透明提交操作以类似于仅追加日志的方式更新编排实例的执行历史记录。 同样,提交操作会在存储系统中创建消息以安排实际工作。 此时,可以从内存中卸载协调器函数。
默认情况下,Durable Functions使用 Azure Storage 作为运行时状态存储,但还支持其他 storage 提供程序。
当业务流程函数执行更多工作(例如,收到响应消息或持久计时器过期)时,业务流程协调程序从一开始就唤醒并重新执行整个函数以重新生成本地状态。 在重播期间,如果代码尝试调用函数(或执行任何其他异步工作),Durable Task Framework 会查阅当前业务流程的执行历史记录。 如果发现活动已执行并生成结果,则会重播该函数的结果,业务流程协调程序代码继续运行。 重播会继续进行,直到函数代码完成或计划新的异步工作。
注释
若要使重播模式正常工作且可靠,业务流程协调程序函数代码必须 具有确定性。 不确定的业务流程协调程序代码可能会导致运行时错误或其他意外行为。 有关业务流程协调程序函数的代码限制的详细信息,请参阅 Orchestrator 函数代码约束。
注释
如果业务流程协调程序函数发出日志消息,重播行为可能会导致发出重复的日志消息。 若要了解发生此行为的原因以及如何解决此问题,请参阅 应用日志记录。
调度历史记录
Durable Task Framework 的事件溯源行为与你编写的业务流程协调程序函数代码紧密耦合。 假设你有一个活动协调器函数,类似于以下示例。
隔离工作者模型
[Function("HelloCities")]
public static async Task<List<string>> Run(
[OrchestrationTrigger] TaskOrchestrationContext 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"));
// Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
return outputs;
}
进程内模型
[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"));
// Return ["Hello Tokyo!", "Hello Seattle!", "Hello London!"].
return outputs;
}
using Microsoft.DurableTask;
[DurableTask]
public class HelloCities : TaskOrchestrator<object?, List<string>>
{
public override async Task<List<string>> RunAsync(TaskOrchestrationContext context, object? input)
{
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"));
return outputs;
}
}
每当计划活动函数时,Durable Task Framework 会在各种检查点保存函数的执行状态。 在每个检查点,框架将状态保存到持久存储后端。 此状态称为 编排历史。
历史记录表
通常,Durable Task Framework 在每个检查点执行以下操作:
- 将执行历史记录保存到持久存储。
- 为编排器要调用的函数入队消息。
- 供协调器自身使用的消息入队,例如持久计时器消息。
检查点完成后,可以从内存中移除协调器函数,直到有更多任务需要执行为止。
注释
Azure Storage在保存数据时,不提供关于表存储和队列之间数据一致性的任何事务性保证。 为了处理故障,Durable Functions Azure Storage 提供程序使用 ventual consistency 模式。 这些模式有助于确保在检查点中间发生崩溃或连接丢失时不会丢失任何数据。 替代存储提供程序(如 Durable Functions Microsoft SQL Server (MSSQL) 存储提供程序可能会提供更严格的一致性保证。
当前面显示的函数完成时,其历史记录类似于表Storage下表中的数据。 条目为了说明而被缩写。
| 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 | 任务已完成 | 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 | 任务已完成 | 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 | 任务已完成 | 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:事件的类型。 有关所有历史事件类型的详细说明,请参阅 持久化任务框架历史事件。
- 时间戳:历史记录事件的协调世界时时间戳。
- 输入:函数的 JSON 格式输入。
- 名称:调用的函数的名称。
- 结果:函数的输出,特别是其返回值。
警告
此表作为调试工具很有用,但其格式和内容可能会随着Durable Functions扩展的发展而变化。
每当函数在等待任务完成后恢复时,Durable Task Framework 都会从头开始重新运行业务流程协调程序函数。 每次重新运行时,都会咨询执行历史记录,以确定当前异步任务是否已完成。 如果执行历史记录显示任务已完成,框架将重播该任务的输出,并转到下一个任务。 此过程继续,直到重播整个执行历史记录。 重播当前执行历史记录后,本地变量将还原到其以前的值。
功能和模式
以下部分介绍协调器函数的特征和模式。
子编排
编排器函数可以调用活动函数,也可以调用其他编排器函数。 例如,可以从协调程序函数库中构建更大的编排。 或者,可以并行运行业务流程协调程序函数的多个实例。
有关详细信息和示例,请参阅Durable Functions(Azure Functions)中的子业务流程。
持久计时器
协调程序可以计划 持久计时器 以实现延迟或设置异步操作的超时处理。 在业务流程协调程序函数中使用持久计时器,而不是语言原生 sleep API。
有关详细信息和示例,请参阅 Durable Functions (Azure Functions) 中的 Timers。
外部事件
协调器函数可以等待外部事件来更新编排实例。 此Durable Functions功能通常用于处理人工交互或其他外部回调。
有关详细信息和示例,请参阅在持久化函数 (Azure Functions) 中处理外部事件。
错误处理
业务流程协调程序函数可以使用编程语言的错误处理功能。 编排代码支持现有的模式 try/catch 。
业务流程编排器函数还可以为它们调用的活动或子业务流程编排器函数添加重试策略。 如果活动或子业务流程协调程序函数失败并出现异常,则指定的重试策略可以自动延迟并重试执行,最多可以指定次数。
注释
如果编排器函数中存在未经处理的异常,编排实例将进入 Failed 状态完成。 协调实例失败后无法重试。
有关详细信息和示例,请参阅处理 Durable Functions(Azure Functions)中的错误。
关键部分(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 结果可与 using 块一起使用,以获取关键部分的语法表示形式。 当业务流程协调程序函数进入关键部分时,只有一个实例可以执行该代码块。 在上一个实例退出关键节之前,将阻止尝试进入关键节的任何其他实例。
关键节功能也可用于协调对持久化实体的更改。 有关关键部分的详细信息,请参阅 实体协调。
注释
Durable Functions 2.0 中提供了关键部分。 目前,仅 .NET 内部协调实现此功能。 对于 .NET 独立进程工作器的编排,实体和关键部分在 Durable Functions 中尚不可用。
对 HTTP 终结点的调用(Durable Functions 2.x)
协调器函数不允许执行I/O操作,如协调器函数代码约束中所述。 此限制的典型解决方法是将需要进行 I/O 操作的任何代码封装在活动函数中。 与外部系统交互的业务流程经常使用活动函数进行 HTTP 调用,并将结果返回到业务流程。
为了简化这种常见模式,业务流程协调程序函数可以使用 CallHttpAsync 该方法直接调用 HTTP API。
隔离工作者模型
[Function("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
Uri url = context.GetInput<Uri>();
// Make an HTTP GET request to the specified endpoint.
DurableHttpResponse response = await context.CallHttpAsync(
method: HttpMethod.Get,
uri: url,
content: null,
retryOptions: null);
if ((int)response.StatusCode == 400)
{
// Handle error codes.
}
}
进程内模型
[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
Uri url = context.GetInput<Uri>();
// Make an HTTP GET request to the specified endpoint.
DurableHttpResponse response =
await context.CallHttpAsync(HttpMethod.Get, url);
if ((int)response.StatusCode == 400)
{
// Handle error codes.
}
}
除了支持基本请求/响应模式外,该方法还支持自动处理常见的异步 HTTP 202 轮询模式。 它还支持使用 托管标识对外部服务进行身份验证。
有关详细信息和详细示例,请参阅 HTTP 功能。
注释
在 Durable Functions 2.0 及更高版本中,可以从协调器函数直接调用 HTTP 终结点。
多个参数
无法将多个参数直接传递给活动函数。 建议传入对象或复合对象的数组。
隔离工作者模型
在 .NET 中,可以使用记录类型或 ValueTuple 对象传递多个参数。
public record CourseInfo(string Major, int UniversityYear);
[Function("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context, int universityYear)
{
CourseInfo courseInfo = new("ComputerScience", universityYear);
object courseRecommendations = await context.CallActivityAsync<object>(
"CourseRecommendations", courseInfo);
return courseRecommendations;
}
进程内模型
在 .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;
}
在.NET中,可以使用记录类型或元组将多个参数作为单个复合对象传递。
using Microsoft.DurableTask;
public record LocationInfo(string City, string State);
[DurableTask]
public class GetWeatherOrchestration : TaskOrchestrator<object?, string>
{
public override async Task<string> RunAsync(TaskOrchestrationContext context, object? input)
{
var location = new LocationInfo("Seattle", "WA");
string weather = await context.CallActivityAsync<string>("GetWeather", location);
return weather;
}
}