Dapr中的Actor模型
@ 归零 | 星期四,四月 22 日,2021 年 | 1 分钟阅读 | 更新于 星期四,四月 22 日,2021 年

原文链接:https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/

介紹

Actor模型将Actor描述为最低层次的 “计算单元”。换句话说,你把代码写在一个独立的单元(称为Actor)中,这个单元每次接收消息并处理它们,没有任何并发或线程。

当你的代码处理一条消息时,它可以发送一条或多条消息给其他Actor,或者创建新的Actor。底层运行时管理每个Actor的运行方式、时间和地点,并在角色之间路由消息。

大量的actor可以同时执行,而且actor之间可以独立执行。

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

何时使用Actors

与其他任何技术决策一样,你应该根据你要解决的问题来决定是否使用actor。

actor设计模式可以很好地适应一些分布式系统问题和场景,但你首先应该考虑的是模式的约束条件。一般来说,在以下情况下,可以考虑用actor模式来模拟你的问题或场景。

  • 你的问题空间涉及大量(数千或更多)小的,独立的,隔离的状态和逻辑单元。
  • 你想使用单线程对象,这些对象不需要从外部组件中进行大量的交互,包括在一组actors中查询状态。
  • 你的actor实例不会因为发出I/O操作而以不可预知的延迟来阻塞调用者。

Dapr中的Actor

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

![image](/images/actor_background_game_example.png | width=100)

Actor的生命周期

Dapr的Actor是虚拟的,这意味着它们的生命周期与它们在内存中的表现无关。因此,它们不需要被显式创建或销毁。Dapr Actors 运行时在第一次收到对该actor ID的请求时,会自动激活一个actor。如果一个actor在一段时间内没有被使用,Dapr Actors运行时就会对内存中的对象进行垃圾回收。如果以后需要重新激活它,它也会保持该actor存在的知识。

对actor方法的调用和提醒会重置idle time,例如,触发提醒会使actor保持活跃。无论actor是活跃还是不活跃,actor提醒都会被触发,如果触发不活跃的actor,它将首先激活actor。actor定时器不重置idle time,所以触发定时器不会使actor保持活跃状态。定时器只有在actor处于活动状态时才会触发。

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

由于虚拟actor模型的存在,这种虚拟actor生命周期抽象带有一些警告,事实上Dapr Actors的实现有时会偏离该模型。

在第一次向actor ID发送消息时该actor就会自动激活(导致一个actor对象被构造)。经过一段时间后,该actor对象会被垃圾回收。在未来,再次使用actor ID,会导致一个新的actor对象被构造。一个actor的状态会超过对象的生命周期,因为状态被存储在Dapr运行时配置的状态提供者中。

分发和故障转移

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

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

### Actor placement 服务

Dapr actor运行时为你管理分配方案和密钥范围设置。这是由actor Placement服务完成的。当创建一个新的服务实例时,相应的Dapr运行时会注册它可以创建的actor类型,并且Placement服务会计算给定类型的所有实例的分区。这些分区信息表会在运行环境中的每个Dapr实例中更新和存储,并且可以随着新的actor服务实例的创建和销毁而动态变化。如下图所示。 image

当client调用具有特定 id 的 actor(例如,actor id 123)时,client的 Dapr 实例会对 actor 类型和 id 进行哈希,并使用该信息调用到可以为该特定 actor id 的请求提供服务的相应 Dapr 实例。因此,对于任何给定的actor id,总是调用同一个分区(或服务实例)。如下图所示。

image

这简化了一些选择,但同时也要考虑一些问题。

  • 默认情况下,Actors被随机放置到pods中,已实现均匀分布。

  • 因为actor是随机放置的,所以应该期望actor操作总是需要网络通信,包括方法调用的数据序列化和反序列化,会产生延迟和开销。

注意:Dapr actor Placement 服务仅用于演员放置,因此,如果您的服务没有使用 Dapr 演员,则不需要该服务。Placement服务可以在所有托管环境中运行,包括自托管和Kubernetes。

Actor通信

你可以通过HTTP/gRPC,与Dapr交互调用actor方法。

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

你可以在request body中为actor方法提供任何数据,而请求的响应将在响应体中,即actor调用的数据。

更多细节请参考Dapr Actor特性

并发性

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

单个actor实例一次不能处理一个以上的请求。如果期望一个actor实例处理并发的请求,它可能会造成吞吐量瓶颈。

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

image

Turn-based 访问

A turn包括完整执行一个actor方法,以响应其他actor或客户端的请求,或者完整执行一个计时器/提醒回调。尽管这些方法和回调是异步的,但Dapr Actors运行时不会将它们交错在一起。在允许新的回合之前,一个回合必须完全完成。换句话说,在允许对方法或回调的新调用之前,当前正在执行的actor方法或计时器/提醒回调必须完全完成。如果方法或回调已从该方法或回调返回执行并且该方法或回调返回的任务已完成,则该方法或回调被视为已完成。值得强调的是,即使在不同的方法,计时器和回调中,也要尊重基于回合的并发。

Dapr actors运行时通过在开始时获取每个actor锁,并在结束时释放锁来强制执行Turn-based并发。因此,Turn-based 并发性是在每个Actor的基础上执行的,而不是跨Actor。Actor方法和定时器/提醒器回调可以代表不同的Actor同时执行。

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

image

© 2014 - 2022 Lionel's Blog

Powered by Hugo with theme Dream.