This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Actors

将代码和数据封装为可重用的 actor 对象,作为一种常见的微服务设计模式

1 - Actor 概述

Actor API 构建块的概述

Actor 模式将 actor 描述为最低级别的"计算单元"。换句话说,你需要将代码编写在一个自包含的单元(称为 actor)中,该单元接收消息并一次处理一条消息,无需任何并发或线程机制。

当代码处理消息时,它可以向其他 actor 发送一条或多条消息,或创建新的 actor。底层运行时负责管理每个 actor 的运行方式、时间和位置,并在 actor 之间路由消息。

大量 actor 可以同时执行,并且 actor 之间相互独立执行。

Dapr 中的 Actor

Dapr 包含一个专门实现虚拟 Actor 模式的运行时。通过 Dapr 的实现,你可以根据 actor 模型编写 Dapr actor,Dapr 利用底层平台提供的可扩展性和可靠性保证。

每个 actor 都定义为 actor 类型的实例,就像对象是类的实例一样。例如,可能有一个实现了计算器功能的 actor 类型,并且可能有多个该类型的 actor 分布在集群中的各个节点上。每个这样的 actor 都通过 actor ID 进行唯一标识。

下面的概述视频和演示展示了 Dapr 中 actor 的工作原理。

Dapr Actor 与 Dapr Workflow 的对比

Dapr actor 基于状态管理和服务调用 API 创建具有身份的有状态、长时间运行的对象。Dapr Workflow 与 Dapr Actor 相关,workflow 构建在 actor 之上,提供更高级别的抽象来编排一组 actor,实现常见的 workflow 模式并代表你管理 actor 的生命周期。

Dapr actor 旨在提供一种在分布式系统中封装状态和行为的方式。actor 可以由客户端应用程序按需激活。当 actor 被激活时,它会被分配一个唯一的身份,这允许它在多次调用之间维护其状态。这使得 actor 非常适合构建有状态、可扩展和容错的分布式应用程序。

另一方面,Dapr Workflow 提供了一种定义和编排涉及分布式系统中多个服务和组件的复杂工作流的方式。Workflow 允许你定义需要按特定顺序执行的步骤或任务序列,并可用于实现业务流程、事件驱动的工作流和其他类似场景。

如上所述,Dapr Workflow 构建在 Dapr Actor 之上,管理其激活和生命周期。

何时使用 Dapr actor

与任何其他技术决策一样,你应该根据要解决的问题来决定是否使用 actor。例如,如果你正在构建聊天应用程序,你可能会使用 Dapr actor 来实现聊天室和用户之间的单独聊天会话,因为每个聊天会话都需要维护自己的状态并且是可扩展和容错的。

一般来说,如果满足以下条件,请考虑使用 actor 模式来为你的问题或场景建模:

  • 你的问题空间涉及大量(数千或更多)小型、独立和隔离的状态与逻辑单元。
  • 你希望使用单线程对象,这些对象不需要来自外部组件的大量交互,包括跨一组 actor 查询状态。
  • 你的 actor 实例不会通过发出 I/O 操作来以不可预测的延迟阻塞调用者。

何时使用 Dapr Workflow

当你需要定义和编排涉及多个服务和组件的复杂工作流时,你将使用 Dapr Workflow。例如,使用前面的聊天应用程序示例,你可能会使用 Dapr Workflow 来定义应用程序的总体工作流,例如如何注册新用户、如何发送和接收消息,以及应用程序如何处理错误和异常。

了解有关 Dapr Workflow 的更多信息以及如何在应用程序中使用工作流。

Actor 类型和 actor ID

Actor 唯一定义为 actor 类型的实例,类似于对象是类的实例。例如,你可能有一个实现了计算器功能的 actor 类型。该类型的许多 actor 可能分布在集群中的各个节点上。

每个 actor 都通过 actor ID 进行唯一标识。actor ID 可以是你选择的_任何_字符串值。如果你不提供 actor ID,Dapr 会为你生成一个随机字符串作为 ID。

功能

命名空间 Actor

Dapr 支持命名空间 actor。可以将 actor 类型部署到不同的命名空间中。你可以在同一命名空间中调用这些 actor 的实例。

了解有关命名空间 actor 及其工作原理的更多信息。

Actor 生命周期

由于 Dapr actor 是虚拟的,因此不需要显式创建或销毁它们。Dapr actor 运行时:

  1. 一旦接收到针对该 actor ID 的初始请求,就会自动激活 actor。
  2. 对未使用的 actor 的内存对象进行垃圾回收。
  3. 维护 actor 存在的知识,以防以后重新激活。

actor 的状态超出对象的生命周期,因为状态存储在为 Dapr 运行时配置的状态提供程序中。

了解有关 actor 生命周期的更多信息。

分布和故障转移

为了提供可扩展性和可靠性,actor 实例分布在集群中,Dapr 在整个集群中分布 actor 实例并自动将它们迁移到健康节点。

了解有关 Dapr actor 放置的更多信息。

Actor 通信

你可以通过 HTTP 调用 actor 方法来调用它们,如下面的常规示例所示。

  1. 服务调用边车上的 actor API。
  2. 使用来自放置服务的缓存分区信息,边车确定哪个 actor 服务实例将托管 actor ID 3。调用被转发到相应的边车。
  3. Pod 2 中的边车实例调用服务实例以调用 actor 并执行 actor 方法。

了解有关调用 actor 方法的更多信息。

并发性

Dapr actor 运行时为访问 actor 方法提供了一个简单的基于轮次的访问模型。基于轮次的访问大大简化了并发系统,因为不需要用于数据访问的同步机制。

状态

事务性状态存储可用于存储 actor 状态。无论你是否打算在 actor 中存储任何状态,都必须在状态存储组件的元数据部分为属性 actorStateStore 指定值 true。Actor 状态以特定方案存储在事务性状态存储中,从而允许进行一致的查询。只能使用单个状态存储组件作为所有 actor 的状态存储。阅读状态 API 参考actor API 参考以了解有关 actor 状态存储的更多信息。

Actor 定时器和提醒

Actor 可以通过注册定时器或提醒来为自己安排定期工作。

定时器和提醒的功能非常相似。主要区别在于,Dapr actor 运行时在停用后不保留有关定时器的任何信息,而使用 Dapr actor 状态提供程序持久化有关提醒的信息。

这种区别允许用户在轻量级但无状态的定时器与资源需求更高但有状态的提醒之间进行权衡。

下面的概述视频和演示展示了 actor 定时器和提醒的工作原理。

后续步骤

Actor 功能和概念 >>

相关链接

2 - Actor 运行时功能

了解 Dapr 中 Actor 的功能和概念

既然您已经从高层次了解了 Actor 构建块,让我们深入探讨 Dapr 中 Actor 包含的功能和概念。

Actor 生命周期

Dapr Actor 是虚拟的,这意味着它们的生命周期与其内存中的表示形式无关。因此,它们不需要被显式创建或销毁。Dapr Actor 运行时在首次收到针对该 Actor ID 的请求时自动激活 Actor。如果 Actor 在一段时间内未被使用,Dapr Actor 运行时会回收内存中的对象。如果稍后需要重新激活,它也会保留有关 Actor 存在的知识。

调用 Actor 方法、定时器和提醒会重置 Actor 空闲时间。例如,提醒触发会保持 Actor 处于活动状态。

  • Actor 提醒无论 Actor 处于活动状态还是非活动状态都会触发。如果为非活动 Actor 触发,它会先激活该 Actor。
  • Actor 定时器触发会重置空闲时间;但是,定时器仅在 Actor 处于活动状态时才会触发。

Dapr 运行时用于检查 Actor 是否可以被垃圾回收的空闲超时和扫描间隔是可配置的。当 Dapr 运行时调用 Actor 服务以获取支持的 Actor 类型时,可以传递此信息。

由于虚拟 Actor 模型,这种虚拟 Actor 生命周期抽象存在一些注意事项,实际上 Dapr Actor 的实现有时会偏离此模型。

首次向其 Actor ID 发送消息时,Actor 会自动激活(导致构造 Actor 对象)。一段时间后,Actor 对象被垃圾回收。将来,再次使用 Actor ID 会导致构造新的 Actor 对象。Actor 的状态比对象的生命周期更长,因为状态存储在为 Dapr 运行时配置的状态提供程序中。

分布和故障转移

为了提供可扩展性和可靠性,Actor 实例分布在整个集群中,Dapr 会根据需要自动将它们从故障节点迁移到健康节点。

Actor 分布在 Actor 服务的实例中,这些实例分布在集群中的节点上。每个服务实例包含给定 Actor 类型的一组 Actor。

Actor 放置服务

Dapr Actor 运行时通过 Actor Placement 服务为您管理分布方案和键范围设置。当创建服务的新实例时:

  1. 边车调用 Actor 服务以检索注册的 Actor 类型和配置设置。
  2. 相应的 Dapr 运行时注册它可以创建的 Actor 类型。
  3. Placement 服务计算给定 Actor 类型在所有实例中的分区。

每个 Actor 类型的此分区数据表会在环境中运行的每个 Dapr 实例中更新和存储,并且可以随着创建和销毁新的 Actor 服务实例而动态变化。

当客户端调用具有特定 ID 的 Actor(例如,actor id 123)时,客户端的 Dapr 实例会对 Actor 类型和 ID 进行哈希处理,并使用该信息调用可以服务于该特定 Actor ID 请求的相应 Dapr 实例。因此,对于任何给定的 Actor ID,总是调用相同的分区(或服务实例)。下图显示了这一点。

这简化了一些选择,但也带来了一些考虑:

  • 默认情况下,Actor 被随机放置到 pod 中,从而实现均匀分布。
  • 由于 Actor 是随机放置的,因此应预期 Actor 操作始终需要网络通信,包括方法调用数据的序列化和反序列化,从而产生延迟和开销。

Actor 通信

您可以通过调用 HTTP 端点与 Dapr 交互以调用 Actor 方法。

POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/<method/state/timers/reminders>

您可以在请求正文中为 Actor 方法提供任何数据,请求的响应将在响应正文中,即来自 Actor 调用的数据。

另一种也许更方便的与 Actor 交互的方式是通过 SDK。Dapr 目前支持 .NETJavaPython 的 Actor SDK。

有关更多详细信息,请参阅 Dapr Actor 功能

并发

Dapr Actor 运行时为访问 Actor 方法提供了一个简单的基于轮次的访问模型。这意味着在任何时候,一个 Actor 对象的代码中只能有一个线程处于活动状态。基于轮次的访问大大简化了并发系统,因为不需要数据访问的同步机制。这也意味着系统在设计时必须特别考虑每个 Actor 实例的单线程访问性质。

单个 Actor 实例一次不能处理多个请求。如果期望 Actor 实例处理并发请求,它可能会导致吞吐量瓶颈。

如果两个 Actor 之间存在循环请求,同时对外部请求之一发出外部请求,Actor 可能会相互死锁。Dapr Actor 运行时会在 Actor 调用时自动超时并向调用者抛出异常,以中断可能的死锁情况。

重入

要允许 Actor “重入"并调用自身的方法,请参阅 Actor 重入

基于轮次的访问

一个轮次包括响应来自其他 Actor 或客户端的请求而对 Actor 方法进行的完整执行,或定时器/提醒回调的完整执行。尽管这些方法和回调是异步的,但 Dapr Actor 运行时不会交错它们。一个轮次必须完全完成后才允许进行新的轮次。换句话说,当前正在执行的 Actor 方法或定时器/提醒回调必须在允许新的方法或回调调用之前完全完成。如果执行已从方法或回调返回,并且方法或回调返回的任务已完成,则认为方法或回调已完成。值得强调的是,即使在不同方法、定时器和回调之间也会遵守基于轮次的并发。

Dapr Actor 运行时通过在轮次开始时获取每个 Actor 的锁并在轮次结束时释放锁来强制执行基于轮次的并发。因此,基于轮次的并发是针对每个 Actor 强制执行的,而不是跨 Actor 强制执行的。Actor 方法和定时器/提醒回调可以同时代表不同的 Actor 执行。

以下示例说明了上述概念。考虑一个实现两个异步方法(例如,Method1 和 Method2)、一个定时器和一个提醒的 Actor 类型。下图显示了代表属于此 Actor 类型的两个 Actor(ActorId1 和 ActorId2)执行这些方法和回调的时间线示例。

后续步骤

定时器和提醒 >>

相关链接

3 - Actor 运行时配置参数

修改默认的 Dapr Actor 运行时配置行为

您可以使用以下配置参数修改默认的 Dapr Actor 运行时行为。

参数描述默认值
entities此主机支持的 Actor 类型。N/A
actorIdleTimeout停用空闲 Actor 前的超时时间。每隔 actorScanInterval 间隔检查一次超时。60 分钟
actorScanInterval扫描需要停用的空闲 Actor 的频率。空闲时间超过 actor_idle_timeout 的 Actor 将被停用。30 秒
drainOngoingCallTimeout正在迁移重平衡 Actor 时的持续时间。这指定了当前活动 Actor 方法完成的超时时间。如果没有当前 Actor 方法调用,则忽略此参数。60 秒
drainRebalancedActors如果为 true,Dapr 将等待 drainOngoingCallTimeout 持续时间,以允许当前 Actor 调用完成,然后再尝试停用 Actor。true
reentrancy (ActorReentrancyConfig)配置 Actor 的重入行为。如果未提供,则禁用重入。禁用,false
entitiesConfig使用配置数组为每个 Actor 类型单独配置。在各个实体配置中指定的任何实体也必须在顶层 entities 字段中指定。N/A

示例

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Register actor runtime with DI
    services.AddActors(options =>
    {
        // Register actor types and configure actor settings
        options.Actors.RegisterActor<MyActor>();

        // Configure default settings
        options.ActorIdleTimeout = TimeSpan.FromMinutes(60);
        options.ActorScanInterval = TimeSpan.FromSeconds(30);
        options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(60);
        options.DrainRebalancedActors = true;
        options.ReentrancyConfig = new() { Enabled = false };

        // Add a configuration for a specific actor type.
        // This actor type must have a matching value in the base level 'entities' field. If it does not, the configuration will be ignored.
        // If there is a matching entity, the values here will be used to overwrite any values specified in the root configuration.
        // In this example, `ReentrantActor` has reentrancy enabled; however, 'MyActor' will not have reentrancy enabled.
        options.Actors.RegisterActor<ReentrantActor>(typeOptions: new()
        {
            ReentrancyConfig = new()
            {
                Enabled = true,
            }
        });
    });

    // Register additional services for use with actors
    services.AddSingleton<BankService>();
}

参阅 .NET SDK 关于注册 Actor 的文档.

import { CommunicationProtocolEnum, DaprClient, DaprServer } from "@dapr/dapr";

// Configure the actor runtime with the DaprClientOptions.
const clientOptions = {
  actor: {
    actorIdleTimeout: "1h",
    actorScanInterval: "30s",
    drainOngoingCallTimeout: "1m",
    drainRebalancedActors: true,
    reentrancy: {
      enabled: true,
      maxStackDepth: 32,
    },
  },
};

// Use the options when creating DaprServer and DaprClient.

// Note, DaprServer creates a DaprClient internally, which needs to be configured with clientOptions.
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort, clientOptions);

const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.HTTP, clientOptions);

参阅使用 JavaScript SDK 编写 Actor 的文档.

from datetime import timedelta
from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig

ActorRuntime.set_actor_config(
    ActorRuntimeConfig(
        actor_idle_timeout=timedelta(hours=1),
        actor_scan_interval=timedelta(seconds=30),
        drain_ongoing_call_timeout=timedelta(minutes=1),
        drain_rebalanced_actors=True,
        reentrancy=ActorReentrancyConfig(enabled=False),
    )
)

参阅使用 Python SDK 运行 Actor 的文档

// import io.dapr.actors.runtime.ActorRuntime;
// import java.time.Duration;

ActorRuntime.getInstance().getConfig().setActorIdleTimeout(Duration.ofMinutes(60));
ActorRuntime.getInstance().getConfig().setActorScanInterval(Duration.ofSeconds(30));
ActorRuntime.getInstance().getConfig().setDrainOngoingCallTimeout(Duration.ofSeconds(60));
ActorRuntime.getInstance().getConfig().setDrainBalancedActors(true);
ActorRuntime.getInstance().getConfig().setActorReentrancyConfig(false, null);

参阅使用 Java SDK 编写 Actor 的文档.

const (
    defaultActorType = "basicType"
    reentrantActorType = "reentrantType"
)

type daprConfig struct {
	Entities                []string                `json:"entities,omitempty"`
	ActorIdleTimeout        string                  `json:"actorIdleTimeout,omitempty"`
	ActorScanInterval       string                  `json:"actorScanInterval,omitempty"`
	DrainOngoingCallTimeout string                  `json:"drainOngoingCallTimeout,omitempty"`
	DrainRebalancedActors   bool                    `json:"drainRebalancedActors,omitempty"`
	Reentrancy              config.ReentrancyConfig `json:"reentrancy,omitempty"`
	EntitiesConfig          []config.EntityConfig   `json:"entitiesConfig,omitempty"`
}

var daprConfigResponse = daprConfig{
	Entities:                []string{defaultActorType, reentrantActorType},
	ActorIdleTimeout:        actorIdleTimeout,
	ActorScanInterval:       actorScanInterval,
	DrainOngoingCallTimeout: drainOngoingCallTimeout,
	DrainRebalancedActors:   drainRebalancedActors,
	Reentrancy:              config.ReentrancyConfig{Enabled: false},
	EntitiesConfig: []config.EntityConfig{
		{
            // Add a configuration for a specific actor type.
            // This actor type must have a matching value in the base level 'entities' field. If it does not, the configuration will be ignored.
            // If there is a matching entity, the values here will be used to overwrite any values specified in the root configuration.
            // In this example, `reentrantActorType` has reentrancy enabled; however, 'defaultActorType' will not have reentrancy enabled.
			Entities: []string{reentrantActorType},
			Reentrancy: config.ReentrancyConfig{
				Enabled:       true,
				MaxStackDepth: &maxStackDepth,
			},
		},
	},
}

func configHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(daprConfigResponse)
}

参阅使用 Go SDK 的 Actor 示例.

相关链接

4 - 命名空间 Actor

了解命名空间 Actor

Dapr 中的命名空间提供隔离能力,从而实现多租户。通过 Actor 命名空间,相同的 Actor 类型可以部署到不同的命名空间中。你可以调用同一命名空间中的这些 Actor 实例。

创建和配置命名空间

你可以在自托管模式或 Kubernetes 上使用命名空间。

在自托管模式下,你可以通过设置 NAMESPACE 环境变量来指定 Dapr 实例的命名空间。

在 Kubernetes 上,你可以在部署 Actor 应用程序时创建和配置命名空间。例如,从以下 kubectl 命令开始:

kubectl create namespace namespace-actorA
kubectl config set-context --current --namespace=namespace-actorA

然后,将你的 Actor 应用程序部署到此命名空间中(本例中为 namespace-actorA)。

为命名空间配置 Actor 状态存储

每个命名空间 Actor 部署必须使用各自独立的状态存储。虽然你可以为每个 Actor 命名空间使用不同的物理数据库,但某些状态存储组件提供了通过表、前缀、集合等方式进行逻辑隔离的方法。这允许你使用相同的物理数据库来支持多个命名空间,只要你在 Dapr 组件定义中提供逻辑隔离即可。

以下是一些示例。

示例 1:通过 etcd 中的前缀

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.etcd
  version: v2
  metadata:
  - name: endpoints
    value: localhost:2379
  - name: keyPrefixPath
    value: namespace-actorA
  - name: actorStateStore
    value: "true"

示例 2:通过 SQLite 中的表名

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.sqlite
  version: v1
  metadata:
  - name: connectionString
    value: "data.db"
  - name: tableName
    value: "namespace-actorA"
  - name: actorStateStore
    value: "true"

示例 3:通过 Redis 中的逻辑数据库编号

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"
  - name: redisDB
    value: "1"
  - name: redisPassword
    secretKeyRef:
      name: redis-secret
      key:  redis-password
  - name: actorStateStore
    value: "true"
  - name: redisDB
    value: "1"
auth:
  secretStore: <SECRET_STORE_NAME>

请查看你的状态存储组件规范以了解其提供的功能。

后续步骤

5 - Actor 定时器和提醒

为 Actor 设置定时器和提醒并执行错误处理

Actor 可以通过注册定时器或提醒来调度自身的周期性工作。

定时器和提醒的功能非常相似。主要区别在于,Dapr actor 运行时在停用后不保留关于定时器的任何信息,而使用 Dapr Scheduler 持久化关于提醒的信息。

这种区别允许用户在轻量级但无状态的定时器与资源消耗更大但有状态的提醒之间进行权衡。

定时器和提醒的调度配置总结如下:


data 是一个可选参数,包含在调用时传递给提醒回调方法的数据。


dueTime 是一个可选参数,设置首次调用回调的时间或时间间隔。如果省略 dueTime,回调将在定时器/提醒注册后立即被调用。

支持的格式:

  • RFC3339 日期格式,例如 2020-10-02T15:00:00Z
  • time.Duration 格式,例如 2h30m
  • ISO 8601 duration 格式,例如 PT2H30M

period 是一个可选参数,设置两次连续回调调用之间的时间间隔。当以 ISO 8601-1 duration 格式指定时,你还可以配置重复次数以限制回调调用的总次数。 如果省略 period,回调将只被调用一次。

支持的格式:

  • time.Duration 格式(使用持续时间值时支持亚秒级精度),例如 2h30m500ms
  • ISO 8601 duration 格式,例如 PT2H30MR5/PT1M30S

ttl 是一个可选参数,设置定时器/提醒到期和删除的时间或时间间隔。如果省略 ttl,则不应用任何限制。

支持的格式:

  • RFC3339 日期格式,例如 2020-10-02T15:00:00Z
  • time.Duration 格式,例如 2h30m
  • ISO 8601 duration 格式。例如:PT2H30M

仅适用于提醒

overwrite 是一个可选布尔参数,指示是否覆盖同名的现有提醒。 如果 overwrite 设置为 true,任何同名的现有提醒将被新配置替换。 如果 overwrite 设置为 false 或省略,并且已存在同名的提醒,操作将失败并返回已存在的错误。 请注意,覆盖现有提醒将重置其状态,包括调用次数和下次触发时间,就像创建新提醒一样。


仅适用于提醒

failurePolicy 是一个可选参数,定义提醒调用失败时的行为。 支持的失败策略包括:

  • drop:丢弃失败的调用,提醒继续按计划进行下一次调用,就好像失败没有发生一样。
  • constant:提醒将以固定的间隔重试失败的调用指定次数。
    • interval:每次重试尝试之间的时间间隔。如果未指定,间隔为 “0s”,意味着立即尝试重试。
    • maxRetries:重试尝试的最大次数。如果未指定,调用将以指定间隔无限期重试,直到成功。

如果未指定失败策略,将应用默认的失败策略:重试 3 次,间隔为 1 秒。


actor 运行时会验证调度配置的正确性,并在输入无效时返回错误。

当你在 period 中指定重复次数以及 ttl 时,定时器/提醒将在任一条件满足时停止,以先发生者为准。

Actor 定时器

你可以为 actor 注册一个基于定时器执行的回调。

Dapr actor 运行时确保回调方法遵守基于轮次的并发保证。这意味着在此回调完成执行之前,不会有其他 actor 方法或定时器/提醒回调正在进行。

Dapr actor 运行时在回调完成时保存对 actor 状态所做的更改。如果在保存状态时发生错误,该 actor 对象将被停用,并将激活一个新实例。

当 actor 作为垃圾回收的一部分被停用时,所有定时器都会停止。之后不会再调用定时器回调。此外,Dapr actor 运行时不保留关于停用前正在运行的定时器的任何信息。由 actor 在将来重新激活时注册其所需的任何定时器。

你可以通过调用对 Dapr 的 HTTP/gRPC 请求来为 actor 创建定时器,如下所示,或通过 Dapr SDK。

POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>

示例

定时器参数在请求体中指定。

以下请求体配置了一个定时器,dueTime 为 9 秒,period 为 3 秒。这意味着它将在 9 秒后首次触发,然后每 3 秒触发一次。

{
  "dueTime":"0h0m9s0ms",
  "period":"0h0m3s0ms"
}

以下请求体配置了一个定时器,period 为 3 秒(ISO 8601 duration 格式)。它还将调用次数限制为 10 次。这意味着它将触发 10 次:首先在注册后立即触发,然后每 3 秒触发一次。

{
  "period":"R10/PT3S",
}

以下请求体配置了一个定时器,period 为 3 秒(ISO 8601 duration 格式),ttl 为 20 秒。这意味着它在注册后立即触发,然后在 20 秒的持续时间内每 3 秒触发一次。

{
  "period":"PT3S",
  "ttl":"20s"
}

以下请求体配置了一个定时器,dueTime 为 10 秒,period 为 3 秒,ttl 为 10 秒。它还将调用次数限制为 4 次。这意味着它将在 10 秒后首次触发,然后在 10 秒的持续时间内每 3 秒触发一次,但总共不超过 4 次。

{
  "dueTime":"10s",
  "period":"R4/PT3S",
  "ttl":"10s"
}

你可以通过调用以下命令来删除 actor 定时器

DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>

有关更多详细信息,请参阅 API 规范

Actor 提醒

提醒是一种在指定时间在 actor 上触发持久回调的机制。它们的功能类似于定时器。但与定时器不同,提醒在所有情况下都会被触发,直到 actor 显式注销它们或调用次数用尽。具体来说,提醒会在 actor 停用和故障转移期间触发,因为 Dapr actor 运行时使用 Dapr Scheduler 服务 持久化关于 actor 提醒的信息。

你可以通过调用对 Dapr 的 HTTP/gRPC 请求为 actor 创建持久提醒,如下所示,或通过 Dapr SDK。

POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>

与定时器不同,提醒支持 overwrite 选项,允许你替换同名的现有提醒,以及 failurePolicy 选项,定义提醒调用失败时的行为。

检索 actor 提醒

你可以通过调用以下命令来检索 actor 提醒

GET http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>

删除 actor 提醒

你可以通过调用以下命令来删除 actor 提醒

DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>

如果触发 actor 提醒且应用未向运行时返回 2** 代码(例如,由于连接问题),actor 提醒将根据其失败策略进行重试,默认情况下是三次,每次尝试之间的退避间隔为 1 秒。 根据任何可选应用的 actor 弹性策略,可能会尝试额外的重试。

有关更多详细信息,请参阅 API 规范

错误处理

当 actor 的方法成功完成时,运行时将继续按照指定的定时器或提醒计划调用该方法。 为了允许 actor 从故障中恢复并在崩溃或重启后重试,你可以通过配置状态存储(例如 Redis 或 Azure Cosmos DB)来持久化 actor 的状态。

如果方法的调用失败,定时器不会被删除。定时器仅在以下情况下被删除:

  • sidecar 终止
  • 执行次数用尽
  • 你显式删除它

使用 CLI 管理提醒

Actor 提醒持久化在 Scheduler 中。 你可以使用 dapr scheduler CLI 命令管理它们。

列出 actor 提醒

dapr scheduler list --filter actor
NAME                                  BEGIN     COUNT  LAST TRIGGER
actor/MyActorType/actorid1/test1      -3.89s    1      2025-10-03T16:58:55Z
actor/MyActorType/actorid2/test2      -3.89s    1      2025-10-03T16:58:55Z

获取提醒详细信息

dapr scheduler get actor/MyActorType/actorid1/test1 -o yaml

删除提醒

删除单个提醒:

dapr scheduler delete actor/MyActorType/actorid1/test1

删除给定 actor 类型的所有提醒:

dapr scheduler delete-all actor/MyActorType

使用 CLI 管理提醒

Actor 提醒持久化在 Scheduler 中。 你可以使用 dapr scheduler CLI 命令管理它们。

列出 actor 提醒

dapr scheduler list --filter actor
NAME                                  BEGIN     COUNT  LAST TRIGGER
actor/MyActorType/actorid1/test1      -3.89s    1      2025-10-03T16:58:55Z
actor/MyActorType/actorid2/test2      -3.89s    1      2025-10-03T16:58:55Z

获取提醒详细信息

dapr scheduler get actor/MyActorType/actorid1/test1 -o yaml

删除提醒

删除单个提醒:

dapr scheduler delete actor/MyActorType/actorid1/test1

删除给定 actor 类型的所有提醒:

dapr scheduler delete-all actor/MyActorType

删除特定 actor 实例的所有提醒:

dapr scheduler delete-all actor/MyActorType/actorid1

删除特定 actor 类型的所有提醒:

dapr scheduler delete-all actor/MyActorType

删除所有提醒

dapr scheduler delete-all actor

备份和恢复提醒

导出所有提醒:

dapr scheduler export -o reminders-backup.bin

从备份文件恢复:

dapr scheduler import -f reminders-backup.bin

总结

  • 提醒存储在 Dapr Scheduler 中,而不是应用中。
  • 通过 Actors API 创建提醒
  • 使用 dapr scheduler CLI 管理现有提醒(列出、获取、删除、备份/恢复)。

后续步骤

配置 actor 运行时行为 >>

相关链接

6 - 如何:通过脚本与虚拟 actor 交互

调用 actor 方法进行状态管理

学习如何通过调用 HTTP/gRPC 端点来使用虚拟 actor。

调用 actor 方法

您可以通过调用 HTTP/gRPC 端点与 Dapr 交互来调用 actor 方法。

POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method>

在请求正文中提供 actor 方法的数据。请求的响应(来自 actor 方法调用的数据)位于响应正文中。

有关更多详细信息,请参阅 Actor API 规范

使用 actor 保存状态

您可以通过 HTTP/gRPC 端点与 Dapr 交互,利用 Dapr actor 状态管理功能可靠地保存状态。

要使用 actor,您的状态存储必须支持多项目事务。这意味着您的状态存储组件必须实现 TransactionalStore 接口。

查看支持事务/actor 的组件列表。所有 actor 只能使用单个状态存储组件作为状态存储。

后续步骤

Actor 重入 >>

相关链接

7 - 如何:在 Dapr 中启用和使用 Actor 重入

了解有关 Actor 重入的更多信息

虚拟 Actor 模式 的一个核心原则是 Actor 执行的单线程特性。在没有重入的情况下,Dapr 运行时会对所有 Actor 请求进行加锁。第二个请求无法在第一个请求完成之前开始。这意味着 Actor 无法调用自身,或者让另一个 Actor 调用它,即使它是同一调用链的一部分。

重入通过允许来自同一链或上下文的请求重新进入已锁定的 Actor 来解决这个问题。这在以下场景中非常有用:

  • Actor 想要调用自身的方法
  • Actor 在工作流中用于执行工作,然后回调到协调 Actor。

重入所允许的调用链示例如下:

Actor A -> Actor A
ActorA -> Actor B -> Actor A

通过重入,你可以执行更复杂的 Actor 调用,而不会牺牲虚拟 Actor 的单线程行为。

显示协调工作流 Actor 调用工作 Actor 或 Actor 调用自身方法的重入图表

maxStackDepth 参数设置一个值,用于控制可以对同一 Actor 进行多少次重入调用。默认情况下,此值设置为 32,这在大多数情况下绰绰有余。

配置 Actor 运行时以启用重入

可重入的 Actor 必须提供适当的配置。这是通过 Actor 的端点 GET /dapr/config 完成的,类似于其他 Actor 配置元素。

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddSingleton<BankService>();
		services.AddActors(options =>
		{
		options.Actors.RegisterActor<DemoActor>();
		options.ReentrancyConfig = new Dapr.Actors.ActorReentrancyConfig()
			{
			Enabled = true,
			MaxStackDepth = 32,
			};
		});
	}
}
import { CommunicationProtocolEnum, DaprClient, DaprServer } from "@dapr/dapr";

// 使用 DaprClientOptions 配置 actor 运行时。
const clientOptions = {
  actor: {
    reentrancy: {
      enabled: true,
      maxStackDepth: 32,
    },
  },
};
from fastapi import FastAPI
from dapr.ext.fastapi import DaprActor
from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig
from dapr.actor.runtime.runtime import ActorRuntime
from demo_actor import DemoActor

reentrancyConfig = ActorReentrancyConfig(enabled=True)
config = ActorRuntimeConfig(reentrancy=reentrancyConfig)
ActorRuntime.set_actor_config(config)
app = FastAPI(title=f'{DemoActor.__name__}Service')
actor = DaprActor(app)

@app.on_event("startup")
async def startup_event():
	# 注册 DemoActor
	await actor.register_actor(DemoActor)

@app.get("/MakeExampleReentrantCall")
def do_something_reentrant():
	# 在此处调用另一个 actor,重入将自动处理
	return

下面是用 Golang 编写的 Actor 片段,它通过 HTTP API 提供重入配置。Go SDK 尚未包含重入功能。

type daprConfig struct {
	Entities                []string                `json:"entities,omitempty"`
	ActorIdleTimeout        string                  `json:"actorIdleTimeout,omitempty"`
	ActorScanInterval       string                  `json:"actorScanInterval,omitempty"`
	DrainOngoingCallTimeout string                  `json:"drainOngoingCallTimeout,omitempty"`
	DrainRebalancedActors   bool                    `json:"drainRebalancedActors,omitempty"`
	Reentrancy              config.ReentrancyConfig `json:"reentrancy,omitempty"`
}

var daprConfigResponse = daprConfig{
	[]string{defaultActorType},
	actorIdleTimeout,
	actorScanInterval,
	drainOngoingCallTimeout,
	drainRebalancedActors,
	config.ReentrancyConfig{Enabled: true, MaxStackDepth: &maxStackDepth},
}

func configHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(daprConfigResponse)
}

处理重入请求

重入请求的关键是 Dapr-Reentrancy-Id 请求头。此请求头的值用于将请求与其调用链进行匹配,并允许它们绕过 Actor 的锁。

此请求头由 Dapr 运行时为任何指定了重入配置的 Actor 请求生成。一旦生成,它就会被用来锁定 Actor,并且必须传递给所有后续请求。下面是 Actor 处理重入请求的示例:

func reentrantCallHandler(w http.ResponseWriter, r *http.Request) {
    /*
     * 省略。
     */

	req, _ := http.NewRequest("PUT", url, bytes.NewReader(nextBody))

	reentrancyID := r.Header.Get("Dapr-Reentrancy-Id")
	req.Header.Add("Dapr-Reentrancy-Id", reentrancyID)

	client := http.Client{}
	resp, err := client.Do(req)

    /*
     * 省略。
     */
}

演示

观看此视频,了解如何使用 Actor 重入。

后续步骤

Dapr SDK 中的 Actors

相关链接