你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
Durable Functions 是 Azure Functions 的扩展,提供在无服务器计算环境中运行有状态函数的方法。 在持久函数应用中,可以使用 业务流程协调程序函数 来协调其他持久函数的执行。 编排器函数具有以下特征:
- 它们使用过程代码定义函数工作流。 不需要声明性架构或设计器。
- 它们可以同步和异步调用其他持久函数。 调用函数的输出可以保存到局部变量。
- 它们设计为耐用且可靠。 当函数调用
await或yield运算符时,执行进度会自动保存为检查点。 进程回收或虚拟机重新启动时,本地状态不会丢失。 - 它们可以长时间运行。 业务流程实例的总生命周期可以是秒、天或月,也可以将实例配置为永不结束。
本文概述了业务流程协调程序函数,以及它们如何帮助你解决各种应用开发难题。 有关 Durable Functions 应用中可用的函数类型的信息,请参阅 Durable Functions 类型和功能。
业务流程标识
业务流程的每个 实例 都有一个实例标识符,也称为 实例 ID。 默认情况下,每个实例 ID 都是自动生成的全局唯一标识符(GUID)。 但是,实例 ID 也可以是任何用户生成的字符串值。 每个业务流程实例 ID 在 任务中心内必须是唯一的。
以下规则适用于实例 ID:
- 它们必须介于 1 到 100 个字符之间。
- 他们绝不能以
@开始。 - 它们不得包含
/、\或#?字符。 - 它们不得包含控制字符。
注释
我们通常建议尽可能使用自动生成的实例 ID。 用户生成的实例 ID 适用于业务流程实例与外部应用程序特定的实体(例如采购订单或文档)之间存在一对一映射的情况。
此外,根据应用使用的 存储提供程序 ,字符限制规则的实际强制实施可能会有所不同。 为了帮助确保正确的行为和兼容性,我们强烈建议遵循前面的实例 ID 规则。
编排的实例 ID 是大多数 实例管理操作的必需参数。 实例 ID 对于诊断也很重要。 例如,在 Application Insights 中 搜索业务流程跟踪数据 以进行故障排除或分析时,可以使用它们。 因此,建议将生成的实例 ID 保存到外部位置,以便稍后轻松引用它们。 位置示例包括数据库或应用程序日志。
Reliability
业务流程协调程序函数使用 事件溯源 设计模式来帮助可靠地维护其执行状态。 Durable Task Framework 使用仅限追加的存储来记录函数业务流程执行的一系列完整操作,而不是直接存储业务流程的当前状态。 与 导出 完整运行时状态相比,追加写入存储具有许多优势。 优点包括提高性能、可伸缩性和响应能力。 你还可以获得事务数据的最终一致性,以及完整的审核轨迹和历史记录。 审计跟踪支持可靠的补偿措施。
Durable Functions 以透明方式使用事件溯源。 在幕后,编排器函数在 C# 中使用 await 运算符,在 JavaScript 和 Python 中使用 yield 运算符。 这些运算符将协调器线程的控制返回给 Durable Task Framework 调度程序。 在 Java 函数中,没有特殊的语言关键字。 相反,通过自定义实例 Throwable 调用 .await() 任务会将控制权交还给调度程序。 然后,调度程序将协调程序函数计划的任何新的操作提交到存储。 示例动作包括调用一个或多个子函数或安排一个持久性计时器。 透明提交操作通过将所有新事件追加到存储中来更新业务流程实例的执行历史记录,就像只追加日志一样。 同样,提交作会在存储中创建消息来计划实际工作。 此时,可以从内存中卸载协调器函数。 默认情况下,Durable Functions 使用 Azure 存储作为其运行时状态存储,但也支持其他 存储提供程序。
如果业务流程函数得到更多工作要做(例如收到响应消息或持久计时器过期),业务流程协调程序将从一开始就唤醒并重新执行整个函数以重新生成本地状态。 在重播期间,如果代码尝试调用函数(或执行任何其他异步工作),Durable Task Framework 会查阅当前业务流程的执行历史记录。 如果发现 活动函数 已执行并生成结果,它将重播该函数的结果,并且业务流程协调程序代码继续运行。 重播会继续进行,直到函数代码完成或计划新的异步工作。
注释
为了帮助重播模式正常工作且可靠,业务流程协调程序函数代码必须 具有确定性。 不确定的业务流程协调程序代码可能会导致运行时错误或其他意外行为。 有关业务流程协调程序函数的代码限制的详细信息,请参阅 Orchestrator 函数代码约束。
注释
如果业务流程协调程序函数发出日志消息,重播行为可能会导致发出重复的日志消息。 有关发生此行为的原因以及如何解决此问题的详细信息,请参阅 应用日志记录。
调度历史记录
Durable Task Framework 的事件溯源行为与你编写的业务流程协调程序函数代码紧密耦合。 假设你有一个活动链编排器函数,如下所示的编排器函数。
注释
Azure Functions 的 Node.js 编程模型版本 4 已正式发布。 v4 模型旨在为 JavaScript 和 TypeScript 开发人员提供更灵活、更直观的体验。 有关 v3 和 v4 之间的差异的详细信息,请参阅 迁移指南。
在以下代码片段中,JavaScript (PM4) 表示编程模型 v4,即新体验。
[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;
}
每当计划活动函数时,Durable Task Framework 会在各种检查点保存函数的执行状态。 在每个检查点,框架将状态保存到持久化存储的后端,默认使用 Azure 表存储。 此状态称为 编排历史。
历史记录表
一般来说,Durable Task Framework 在每个检查点执行以下操作:
- 将执行历史记录保存到持久存储中。
- 将业务流程协调程序想要调用的函数的相关消息排队。
- 供协调器自身使用的消息入队,例如持久计时器消息。
检查点完成后,协调器函数可以从内存中移出,直到需要再次执行任务。
注释
保存数据时,Azure 存储不提供有关表存储和队列之间的数据一致性的任何事务性保证。 为了处理故障,Durable Functions Azure 存储 提供方使用 最终一致性 模式。 这些模式有助于确保在检查点中间发生崩溃或连接丢失时不会丢失任何数据。 备用存储提供程序(例如 Durable Functions Microsoft SQL Server (MSSQL) 存储提供程序)可能会提供更严格的一致性保证。
当前面显示的函数完成时,其历史记录类似于表存储中下表中的数据。 这些条目的缩写是为了说明目的。
| PartitionKey (InstanceId) | 事件类型 | 时间戳 | Input | Name | 结果 | 状态 |
|---|---|---|---|---|---|---|
| eaee885b | 执行已开始 | 2021-05-05T18:45:28.852Z | null | HelloCities | ||
| eaee885b | OrchestratorStarted | 2021-05-05T18:45:32.362Z | ||||
| eaee885b | TaskScheduled | 2021-05-05T18:45:32.670Z | SayHello | |||
| eaee885b | OrchestratorCompleted | 2021-05-05T18:45:32.670Z | ||||
| eaee885b | TaskCompleted | 2021-05-05T18:45:34.201Z | “”“Hello Tokyo!”” | |||
| eaee885b | OrchestratorStarted | 2021-05-05T18:45:34.232Z | ||||
| eaee885b | TaskScheduled | 2021-05-05T18:45:34.435Z | SayHello | |||
| eaee885b | OrchestratorCompleted | 2021-05-05T18:45:34.435Z | ||||
| eaee885b | TaskCompleted | 2021-05-05T18:45:34.763Z | “”“Hello Seattle!”” | |||
| eaee885b | OrchestratorStarted | 2021-05-05T18:45:34.857Z | ||||
| eaee885b | TaskScheduled | 2021-05-05T18:45:34.857Z | SayHello | |||
| eaee885b | OrchestratorCompleted | 2021-05-05T18:45:34.857Z | ||||
| eaee885b | TaskCompleted | 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:事件的类型。 有关所有历史记录事件类型的详细说明,请参阅 Durable Task Framework History Events。
- 时间戳:历史记录事件的协调世界时时间戳。
- 输入:函数的 JSON 格式输入。
- 名称:调用的函数的名称。
- 结果:函数的输出,特别是其返回值。
警告
此表可以作为调试工具使用。 但请记住,随着 Durable Functions 扩展的发展,其格式和内容可能会发生变化。
每次在等待任务完成后恢复函数时,Durable Task Framework 都会重新从头运行协调器函数。 每次重新运行时,都会咨询执行历史记录,以确定当前异步任务是否已完成。 如果执行历史记录显示任务已完成,框架将重播该任务的输出,并转到下一个任务。 此过程会持续到整个历史记录被重播为止。 重播当前执行历史记录后,本地变量将还原到其以前的值。
功能和模式
以下部分介绍协调器函数的特征和模式。
子业务流程
编排器函数可以调用活动函数,也可以调用其他编排器函数。 例如,可以从协调程序函数库中构建更大的编排。 或者,可以并行运行业务流程协调程序函数的多个实例。
有关详细信息和示例,请参阅 Durable Functions(Azure Functions)中的子编排。
持久计时器
协调程序可以计划 持久计时器 以实现延迟或设置异步操作的超时处理。 在业务流程协调程序函数中使用持久计时器,而不是语言原生 sleep API。
有关详细信息和示例,请参阅 Durable Functions 中的计时器(Azure Functions)。
外部事件
协调器函数可以等待外部事件来更新编排实例。 此 Durable Functions 功能通常用于处理人工交互或其他外部回调。
有关详细信息和示例,请参阅在 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。
[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 对象传递多个参数。 以下示例使用 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;
}
[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
// Parse the input for the student's major and year in university.
(string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();
// Retrieve and return course recommendations by major and university year.
return new
{
major = studentInfo.Major,
universityYear = studentInfo.UniversityYear,
recommendedCourses = new []
{
"Introduction to .NET Programming",
"Introduction to Linux",
"Becoming an Entrepreneur"
}
};
}