工作流架构

Dapr 工作流引擎架构

Dapr 工作流允许开发者使用多种编程语言的普通代码来定义工作流。工作流引擎运行在 Dapr 边车内部,协调作为应用程序一部分部署的工作流代码。Dapr 工作流构建在 Dapr Actor 之上,为工作流执行提供持久性和可扩展性。

本文介绍:

  • Dapr 工作流引擎的架构
  • 工作流引擎如何与应用程序代码交互
  • 工作流引擎如何融入整体 Dapr 架构
  • 不同的工作流后端如何与工作流引擎配合使用

有关如何在应用程序中编写 Dapr 工作流的更多信息,请参阅如何:编写工作流

Dapr 工作流引擎内部由 Dapr 的 actor 运行时驱动。下图展示了 Kubernetes 模式下的 Dapr 工作流架构:

Diagram showing how the workflow architecture works in Kubernetes mode

要使用 Dapr 工作流构建块,您需要使用 Dapr Workflow SDK 在应用程序中编写工作流代码,SDK 内部使用 gRPC 流连接到边车。这会注册工作流以及任何工作流活动,或工作流可以调度的任务。

引擎直接嵌入到边车中,并使用 durabletask-go 框架库实现。该框架允许您交换不同的存储提供程序,包括为 Dapr 创建的存储提供程序,它在幕后利用内部 actor。由于 Dapr 工作流使用 actor,您可以将工作流状态存储在状态存储中。

边车交互

当工作流应用程序启动时,它使用工作流编写 SDK 向 Dapr 边车发送 gRPC 请求,并获取工作流工作项的流,遵循服务器流式 RPC 模式。这些工作项可以是任何内容,从"启动新的 X 工作流"(其中 X 是工作流的类型)到"代表工作流 X 调度具有输入 Z 的活动 Y"。

工作流应用程序执行适当的工作流代码,然后向边车发送 gRPC 请求,其中包含执行结果。

Dapr 工作流引擎协议

所有交互都通过单个 gRPC 通道进行,并由应用程序发起,这意味着应用程序不需要打开任何入站端口。 这些交互的细节由特定语言的 Dapr 工作流编写 SDK 内部处理。

工作流与应用程序 actor 交互的区别

如果您熟悉 Dapr actor,您可能会注意到,与应用程序定义的 actor 相比,工作流的边车交互方式有一些差异。

Actor工作流
应用程序创建的 actor 可以使用 HTTP 或 gRPC 与边车交互。工作流仅使用 gRPC。由于工作流 gRPC 协议的复杂性,实现工作流时_必须_使用 SDK。
Actor 操作从边车推送到应用程序代码。这要求应用程序监听特定的_应用端口_。对于工作流,操作由应用程序使用流式协议从边车_拉取_。应用程序不需要监听任何端口即可运行工作流。
Actor 向边车显式注册自己。工作流不向边车注册自己。嵌入式引擎不跟踪工作流类型。此职责委托给工作流应用程序及其 SDK。

工作流分布式跟踪

工作流引擎使用的 durabletask-go 核心使用 Open Telemetry SDK 编写分布式跟踪。 这些跟踪由 Dapr 边车自动捕获,并导出到配置的 Open Telemetry 提供程序,例如 Zipkin。

引擎管理的每个工作流实例表示为一个或多个 span。 有一个表示完整工作流执行的父 span,以及各种任务的子 span,包括活动任务执行和持久化计时器的 span。

工作流活动代码当前_不能_访问跟踪上下文。

工作流 actor

当工作流客户端连接到边车时,会注册两种类型的 actor 以支持工作流引擎:

  • dapr.internal.{namespace}.{appID}.workflow
  • dapr.internal.{namespace}.{appID}.activity

{namespace} 值是 Dapr 命名空间,如果未配置命名空间,则默认为 default{appID} 值是应用程序的 ID。 例如,如果您有一个名为"wfapp"的工作流应用程序,则工作流 actor 的类型为 dapr.internal.default.wfapp.workflow,活动 actor 的类型为 dapr.internal.default.wfapp.activity

下图演示了工作流 actor 在 Kubernetes 场景中如何运行:

Diagram demonstrating internally registered actors across a cluster

与用户定义的 actor 一样,工作流 actor 通过 actor 放置服务提供的哈希查找表分布在集群中。 它们还维护自己的状态并使用提醒。 但是,与应用程序代码中的 actor 不同,这些工作流 actor 嵌入到 Dapr 边车中。 应用程序代码完全不知道这些 actor 的存在。

任何隐式支持 actor 的状态存储都隐式支持 Dapr 工作流。

工作流 actor部分所述,工作流通过附加到历史日志来增量保存其状态。 工作流的历史日志分布在多个状态存储键中,以便每个"检查点"仅需要附加最新的条目。

每个检查点的大小由工作流在进入空闲状态之前调度的并发操作数确定。 顺序工作流因此会对状态存储进行较小的批量更新,而扇出/扇入工作流将需要更大的批量。 批量大小也受工作流调用活动子工作流时输入和输出大小的影响。

Diagram of workflow actor state store interactions

不同的状态存储实现可能会隐式地限制您可以编写的工作流类型。 例如,Azure Cosmos DB 状态存储将项目大小限制为 2 MB 的 UTF-8 编码 JSON(来源)。 活动或子工作流的输入或输出负载作为单个记录存储在状态存储中,因此 2 MB 的项目限制意味着工作流和活动的输入和输出不能超过 2 MB 的 JSON 序列化数据。

同样,如果状态存储对批量事务的大小施加限制,这可能会限制工作流可以调度的并行操作数量。

可以从状态存储中清除工作流状态,包括其所有历史记录。 每个 Dapr SDK 都公开了用于清除特定工作流实例的所有元数据的 API。

状态存储记录数

每次工作流运行在状态存储中保存为历史记录的记录数由其复杂性或"形状"决定。换句话说,即活动、计时器、子工作流等的数量。 下表显示了不同工作流任务保存的记录数的一般指南。 根据重试或并发性,此数字可能更大或更小。

任务类型保存的记录数
启动工作流5 条记录
调用活动3 条记录
计时器3 条记录
触发事件3 条记录
启动子工作流8 条记录

查询工作流历史

dapr workflow --app-id myapp list
dapr workflow --app-id myapp history <instance-id>

支持的状态存储

工作流引擎支持以下状态存储:

  • PostgreSQL
  • MySQL
  • SQL Server
  • SQLite
  • Oracle Database
  • CockroachDB
  • MongoDB
  • Redis

工作流可扩展性

由于 Dapr 工作流内部使用 actor 实现,Dapr 工作流具有与 actor 相同的可扩展性特征。 放置服务:

  • 不区分工作流 actor 和您在应用程序中定义的 actor
  • 将使用与 actor 相同的算法对工作流进行负载平衡

工作流的预期可扩展性由以下因素决定:

  • 用于托管工作流应用程序的计算机数量
  • 运行工作流的计算机上可用的 CPU 和内存资源
  • 为 actor 配置的状态存储的可扩展性
  • actor 放置服务和提醒子系统的可扩展性

目标应用程序中工作流代码的实现细节也在单个工作流实例的可扩展性中发挥作用。 每个工作流实例一次在单个节点上执行,但工作流可以调度在其他节点上运行的活动和子工作流。

工作流还可以调度这些活动和子工作流并行运行,允许单个工作流可能在整个集群的所有可用节点上分布计算任务。

您可以使用 Dapr 配置配置工作流和活动的最大并发性,如下一节所述。

工作流不控制负载如何在集群中分布的细节。 例如,如果工作流调度 10 个活动任务并行运行,所有 10 个任务可能运行在多达 10 个不同的计算节点上,也可能运行在单个计算节点上。 实际扩展行为由 actor 放置服务决定,该服务管理表示工作流每个任务的 actor 的分布。

Diagram of workflow and activity actors scaled out across multiple Dapr instances

工作流延迟

为了提供持久性和弹性保证,Dapr 工作流频繁写入状态存储并依赖提醒来驱动执行。 因此,Dapr 工作流可能不适合延迟敏感的工作负载。 高延迟的预期来源包括:

  • 持久化工作流状态时状态存储的延迟。
  • 使用大型历史记录重新水合工作流时状态存储的延迟。
  • 集群中太多活动提醒引起的延迟。
  • 集群中高 CPU 使用率引起的延迟。

有关工作流 actor 的设计如何影响执行延迟的更多详细信息,请参阅提醒使用和执行保证部分

提高调度吞吐量

默认情况下,当客户端调度工作流时,工作流引擎会等待工作流完全启动后再向客户端返回响应。 在返回之前等待工作流启动会降低工作流的调度吞吐量。 当调度具有开始时间的工作流时,工作流引擎不会等待工作流启动就向客户端返回响应。 要提高调度吞吐量,请考虑在调度工作流时添加"现在"的开始时间。 以下显示了在 Go SDK 中调度开始时间为"现在"的工作流的示例:

client.ScheduleNewWorkflow(ctx, "MyCoolWorkflow", workflow.WithStartTime(time.Now()))

使用 Dapr Shared 与工作流时的工作流集群部署

当使用 Dapr Shared时,可能会有多个 daprd 边车在单个负载均衡器或服务后面运行。 因此,接收工作的辅助实例可能不是接收工作结果的实例。 Dapr 创建第三种 actor 类型来处理此场景:dapr.internal.{namespace}.{appID}.executor,用于将辅助结果路由回正确的工作流 actor,以确保正确操作。

后续步骤

编写工作流 >>

相关链接