前言:

 前篇-绑定 文章对Dapr的绑定构建块进行了解,本篇继续对 Actor 构建块进行了解学习。

一、Actor简介:

 Actors 为最低级别的“计算单元”。 换句话说,您将代码写入独立单元 ( 称为actor) ,该单元接收消息并一次处理消息,而不进行任何类型的并行或线程处理。

 当代码处理一条消息时,它可以向其他参与者发送一条或多条消息,或者创建新的 Actors。 底层 运行时 将管理每个 actor 的运行方式,时机和位置,并在 Actors 之间传递消息。

 大量 Actors 可以同时执行,而 Actors 可以相互独立执行。

 Dapr 包含专门实现 virtual actors 模式 的运行时。 通过 Dapr 的实现,您可以根据 Actors 模型编写 Dapr Actor,而 Dapr 利用底层平台提供的可扩展性和可靠性保证。

 应用场景:

  • 您的问题空间涉及大量(数千或更多) 的独立和孤立的小单位和逻辑。
  • 您想要处理单线程对象,这些对象不需要外部组件的大量交互,例如在一组 Actors 之间查询状态。
  • 您的 actor 实例不会通过发出I/O操作来阻塞调用方。

 生命周期:

  Dapr Actors 是虚拟的,意思是他们的生命周期与他们的 in - memory 表现不相关。 因此,它们不需要显式创建或销毁。 Dapr Actors 运行时在第一次接收到该 actor ID 的请求时自动激活 actor。 如果 actor 在一段时间内未被使用,那么 Dapr Actors 运行时将回收内存对象。 如果以后需要重新启动,它还将保持对 actor 的一切原有数据。

  调用 actor 方法和 reminders 将重置空闲时间,例如,reminders 触发将使 actor 保持活动状态。 不论 actor 是否处于活动状态或不活动状态 Actor reminders 都会触发,对不活动 actor ,那么会首先激活 actor。 Actor timers 不会重置空闲时间,因此 timer 触发不会使参与者保持活动状态。 Timer 仅在 actor 活跃时被触发。

  空闲超时和扫描时间间隔 Dapr 运行时用于查看是否可以对 actor 进行垃圾收集。 当 Dapr 运行时调用 actor 服务以获取受支持的 actor 类型时,可以传递此信息。

  Virtual actors 生命周期抽象会将一些警告作为 virtual actors 模型的结果,而事实上, Dapr Actors 实施有时会偏离此模型。

  在第一次将消息发送到其 actor 标识时,将自动激活 actor ( 导致构造 actor 对象) 。 在一段时间后,actor 对象将被垃圾回收。 以后,再次使用 actor ID 访问,将构造新的 actor。 Actor 的状态比对象的生命周期更久,因为状态存储在 Dapr 运行时的配置状态提供程序中(也就是说Actor即使不在活跃状态,仍然可以读取它的状态)。

二、工作原理:

 Dapr Sidecar 提供了用于调用执行组件的 HTTP/gRPC API。 这是 HTTP API 的URL格式: 

http://localhost:<daprPort>/v1.0/actors/<actorType>/<actorId>/
  • <daprPort>: Dapr 侦听的 HTTP 端口。
  • <actorType>:执行组件类型。
  • <actorId>:要调用的特定参与者的 ID。

 a)Actor组件放置服务流程:

  1. 启动时,Sidecar 调用执行组件服务以获取注册的执行组件类型和执行组件的配置设置。
  2. Sidecar 将注册的执行组件类型的列表发送到放置服务。
  3. 放置服务会将更新的分区信息广播到所有执行组件服务实例。 每个实例都将保留分区信息的缓存副本,并使用它来调用执行组件。

 b)调用Actor组件方法流程:

  

  1. 服务在Sidecar 上调用执行组件 API。 请求正文中的 JSON 有效负载包含要发送到执行组件的数据。
  2. Sidecar 使用位置服务中的本地缓存的分区信息来确定哪个执行组件服务实例 (分区) 负责托管 ID 为的执行组件 3 。 在此示例中,它是 pod 2 中的服务实例。 调用将转发到相应的Sidecar 。
  3. Pod 2 中的Sidecar 实例调用服务实例以调用执行组件。 服务实例激活actor(如果它还没有激活)并执行actor 方法。

三、Actor timers(定时器) 和 reminders(提醒)

 可以使用计时器和提醒来计划自身的调用。 这两个概念都支持配置截止时间。 不同之处在于回调注册的生存期:

  • 只要激活执行组件,计时器就会保持活动状态。 计时器 不会 重置空闲计时器,因此它们不能使执行组件处于活动状态。
  • 提醒长于执行组件激活。 如果停用了某个执行组件,则会重新激活该执行组件。 提醒  重置空闲计时器。

 1、Timers定时器:

  Dapr Actor 运行时确保回调方法被顺序调用,而非并发调用。 这意味着,在此回调完成执行之前,不会有其他Actor方法或timer/remider回调被执行。

  Timer的下一个周期在回调完成执行后开始计算。 这意味着 timer 在回调执行时停止,并在回调完成时启动。

  Dapr Actor 运行时在回调完成时保存对actor的状态所作的更改。 如果在保存状态时发生错误,那么将取消激活该actor对象,并且将激活新实例。

  当actor作为垃圾回收(GC)的一部分被停用时,所有 timer 都会停止。 在此之后,将不会再调用 timer 的回调。 此外, Dapr Actors 运行时不会保留有关在失活之前运行的 timer 的任何信息。 也就是说,重新启动 actor 后将会激活的 timer 完全取决于注册时登记的 timer。

  a) 创建定时器:

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

  Timer 的 duetime 和回调函数可以在请求主体中指定。 到期时间(due time)表示注册后 timer 将首次触发的时间。 period 表示timer在此之后触发的频率。 到期时间为0表示立即执行。 负 due times 和负 periods 都是无效。

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

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

  b) 删除定时器:

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

 2、Reminders 提醒:

  Reminders 是一种在指定时间内触发 persistent 回调的机制。 它们的功能类似于 timer。 但与 timer 不同,在所有情况下 reminders 都会触发,直到 actor 显式取消注册 reminders 或删除 actor 。 具体而言, reminders 会在所有 actor 失活和故障时也会触发触发,因为Dapr Actors 运行时会将 reminders 信息持久化到 Dapr Actors 状态提供者中。

  a) 创建Reminders   

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

  Reminders 的 duetime 和回调函数可以在请求主体中指定。 到期时间(due time)表示注册后 reminders将首次触发的时间。 period 表示在此之后 reminders 将触发的频率。 到期时间为0表示立即执行。 负 due times 和负 periods 都是无效。 若要注册仅触发一次的 reminders ,请将 period 设置为空字符串。

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

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

  b) 获取Reminders 

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

  c) 删除Reminders 

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

四、数据持久化:

  使用 Dapr 状态管理构建块保存执行组件状态。由于执行组件可以一轮执行多个状态操作,因此状态存储组件必须支持多项事务

  当前状态管理组件支持事务/Actors支持情况:

Name CRUD 事务 ETag Actors 状态 组件版本 自从
Aerospike Alpha v1 1.0
Apache Cassandra Alpha v1 1.0
Cloudstate Alpha v1 1.0
Couchbase Alpha v1 1.0
Hashicorp Consul Alpha v1 1.0
Hazelcast Alpha v1 1.0
Memcached Alpha v1 1.0
MongoDB GA v1 1.0
MySQL Alpha v1 1.0
PostgreSQL Alpha v1 1.0
Redis GA v1 1.0
RethinkDB Alpha v1 1.0
Zookeeper Alpha v1 1.0

 需要支持Actor状态存储需添加以下内容:

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"  

五、.NET Core 实例:

 1、添加nuget包引用:Dapr.ActorsDapr.Actors.AspNetCore。

  2、定义IOrderStatusActor接口,需要继承自IActor

public interface IOrderStatusActor : IActor
{
Task<string> Paid(string orderId);
Task<string> GetStatus(string orderId);
}

  执行组件方法的返回类型必须为 Task 或 Task<T> 。 此外,执行组件方法最多只能有一个参数。 返回类型和参数都必须可 System.Text.Json 序列化。

 3、定义OrderStatusActor实现IOrderStatusActor,并继承自Actor

public class OrderStatusActor : Actor, IOrderStatusActor
{
public OrderStatusActor(ActorHost host) : base(host)
{
}
public async Task<string> Paid(string orderId)
{
// change order status to paid
await StateManager.AddOrUpdateStateAsync(orderId, "init", (key, currentStatus) => "paid");
return orderId;
}
public async Task<string> GetStatus(string orderId)
{
return await StateManager.GetStateAsync<string>(orderId);
}
}

 4、修改Statup.cs文件

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//注册Actor
services.AddActors(option =>
{
option.Actors.RegisterActor<OrderStatusActor>();
});
} public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
//
endpoints.MapActorsHandlers();
});
}

 5、添加ActorController操作Actor

[Route("api/[controller]")]
[ApiController]
public class ActorController : ControllerBase
{
private readonly IActorProxyFactory _actorProxyFactory; public ActorController(IActorProxyFactory actorProxyFactory)
{
_actorProxyFactory = actorProxyFactory;
} /// <summary>
/// 方式一:ActorProxy.Create方式
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
[HttpGet("paid/{orderId}")]
public async Task<ActionResult> PaidAsync(string orderId)
{
var actorId = new ActorId("myid-" + orderId);
var proxy = ActorProxy.Create<IOrderStatusActor>(actorId, "OrderStatusActor");
var result = await proxy.Paid(orderId);
return Ok(result);
} /// <summary>
/// 方式二:依赖注入方式
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
[HttpGet("get/{orderId}")]
public async Task<ActionResult> GetAsync(string orderId)
{
var proxy = _actorProxyFactory.CreateActorProxy<IOrderStatusActor>(
new ActorId("myid-" + orderId), "OrderStatusActor"); return Ok(await proxy.GetStatus(orderId));
}
}

 6、Timer应用:使用Actor基类的 RegisterTimerAsync 方法注册计时器:在OrderStatusActor类中新增方法

#region Timer操作

/// <summary>
/// 启动Timer定时器
/// </summary>
/// <param name="name">定时器名称</param>
/// <param name="text">定时器参数</param>
/// <returns></returns>
public Task StartTimerAsync(string name, string text)
{
//注册立即执行的间隔3s执行的定时器
return RegisterTimerAsync(
name,
nameof(TimerCallbackAsync),
Encoding.UTF8.GetBytes(text),
TimeSpan.Zero,
TimeSpan.FromSeconds(3));
} /// <summary>
/// 定时器回调
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
public Task TimerCallbackAsync(byte[] state)
{
var text = Encoding.UTF8.GetString(state); Console.WriteLine($"Timer fired: {text}"); return Task.CompletedTask; } /// <summary>
/// 停止定时器
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public Task StopTimerAsync(string name)
{
//停止计时器 UnregisterTimerAsync
return UnregisterTimerAsync(name);
} #endregion

 7、Reminder操作:使用Actor基类的 RegisterReminderAsync 方法计划计时器。在OrderStatusActor类中新增方法

#region Reminder 操作

public Task SetReminderAsync(string text)
{
return RegisterReminderAsync(
"test-reminder",
Encoding.UTF8.GetBytes(text),
TimeSpan.Zero,
TimeSpan.FromSeconds(1));
} /// <summary>
/// Reminder触发处理(实现IRemindable接口处理触发)
/// </summary>
/// <param name="reminderName"></param>
/// <param name="state"></param>
/// <param name="dueTime"></param>
/// <param name="period"></param>
/// <returns></returns>
public Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
{
if (reminderName == "test-reminder")
{
var text = Encoding.UTF8.GetString(state);
Console.WriteLine($"reminder fired: {text}");
}
return Task.CompletedTask;
}
#endregion

 8、启动定时器:

public class OrderStatusActor : Actor, IOrderStatusActor, IRemindable
{
public OrderStatusActor(ActorHost host) : base(host)
{
//注册Timer
StartTimerAsync("test-timer", "this is a test timer").ConfigureAwait(false).GetAwaiter().GetResult(); //设置Reminder
SetReminderAsync("this is a test reminder").ConfigureAwait(false).GetAwaiter().GetResult();
}
//其他处理逻辑
}

总结:

 Dapr 执行组件构建基块可以更轻松地编写正确的并发系统。 执行组件是状态和逻辑的小单元。 它们使用基于轮次的访问模型,无需使用锁定机制编写线程安全代码。 执行组件是隐式创建的,在未执行任何操作时以无提示方式从内存中卸载。 重新激活执行组件时,自动持久保存并加载执行组件中存储的任何状态。 执行组件模型实现通常是为特定语言或平台创建的。 但是,借助 Dapr 执行组件构建基块,可以从任何语言或平台利用执行组件模型。

 Actor组件支持计时器和提醒来计划将来的工作。 计时器不会重置空闲计时器,并且允许执行组件在未执行其他操作时停用。 提醒会重置空闲计时器,并且也会自动保留。 计时器和提醒都遵守基于轮次的访问模型,确保在处理计时器/提醒事件时无法执行任何其他操作。

 使用 Dapr 状态管理构建基块 持久保存执行组件状态。 支持多项事务的任何状态存储都可用于存储执行组件状态。

Dapr-Actor构建块的更多相关文章

  1. 面向.NET开发人员的Dapr- actors 构建块

    原文地址:https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/actors The actor m ...

  2. 企业架构研究总结(35)——TOGAF架构内容框架之构建块(Building Blocks)

    之前忙于搬家移居,无暇顾及博客,今天终于得闲继续我的“政治课”了,希望之后至少能够补完TOGAF方面的内容.从前面文章可以看出,笔者并无太多能力和机会对TOGAF进行理论和实际的联系,仅可对标准的文本 ...

  3. TOGAF架构内容框架之构建块(Building Blocks)

    TOGAF架构内容框架之构建块(Building Blocks) 之前忙于搬家移居,无暇顾及博客,今天终于得闲继续我的“政治课”了,希望之后至少能够补完TOGAF方面的内容.从前面文章可以看出,笔者并 ...

  4. Dapr-绑定构建块

    前言: 前篇-发布订阅文章对Dapr的订阅/发布进行了解,本篇继续对 绑定 构建块进行了解. 一.简介: Dapr 资源绑定使服务能够跨即时应用程序外部的外部资源集成业务操作. 来自外部系统的事件可能 ...

  5. JavaScript是如何工作的:Web Workers的构建块 + 5个使用他们的场景

    摘要: 理解Web Workers. 原文:JavaScript是如何工作的:Web Workers的构建块 + 5个使用他们的场景 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这 ...

  6. JBoss 系列十七:使用JGroups构建块MessageDispatcher 构建群组通信应用

    内容概要 本部分说明JGroups构建块接口MessageDispatcher,具体提供一个简单示例来说明如何使用JGroups构建块MessageDispatcher 构建群组通信应用 示例描述 构 ...

  7. JBoss 系列十八:使用JGroups构建块RpcDispatcher构建群组通信应用

    内容概要 本部分说明JGroups构建块接口RpcDispatcher,具体提供一个简单示例来说明如何使用JGroups构建块RpcDispatcher构建群组通信应用. 示例描述 类似Message ...

  8. JBoss 系列十九:使用JGroups构建块RspFilter对群组通信返回消息进行过滤

    内容概述 本部分说明JGroups构建块接口RspFilter,具体提供一个简单示例来说明如何使用JGroups构建块RspFilter对群组通信返回消息进行过滤. 示例描述 我们知道构建块基于通道之 ...

  9. Dapr Actor 的微服务架构

    Dapr中的Actor模型,和Orleans的Virtual Actor一脉相传, 圣杰写过一篇文章Orleans 知多少 | .NET Core 分布式框架介绍过.简单来讲:Actor模型 = 状态 ...

随机推荐

  1. Windows下node-gyp查找VS安装路径简单解析

    node-gyp的作用我已经不想赘述了,这里给一个我之前文章的链接:cnblogs看这里,知乎看这里.本文主要从源码入手,介绍node-gyp查找VisualStudio的过程 为了方便我们研究nod ...

  2. 《手把手教你》系列技巧篇(二十五)-java+ selenium自动化测试-FluentWait(详细教程)

    1.简介 其实今天介绍也讲解的也是一种等待的方法,有些童鞋或者小伙伴们会问宏哥,这也是一种等待方法,为什么不在上一篇文章中竹筒倒豆子一股脑的全部说完,反而又在这里单独写了一篇.那是因为这个比较重要,所 ...

  3. FastAPI 学习之路(十二)接口几个额外信息和额外数据类型

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...

  4. I/O系统

    I/O系统的组成 外部设备 接口部件 总线 相应的管理软件 I/O软件 将用户编制的程序(或数据)输入主机内 将运算结果输出给用户 实现输入输出系统与主机工作的协调 I/O系统的基本功能 完成计算机内 ...

  5. 全网详细JAVA知识点干货学习路线目录,值得收藏学习!

    1.Java简介及开发环境配置 2.Java中的注释&关键字&常量&变量&标识符 3.Java中的基本数据类型及其类型转换 4.Java中的运算符及表达式 5.Java ...

  6. 浅尝装饰器和AOP

    [写在前面] 参考文章:https://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html[从简单的例子入手进行讲解,由浅入深,很到位] 装饰器部 ...

  7. SLAM名词介绍

    gauge freedom:测量自由度 degrees-of-freedom(DoF) 自由度 wide-baseline matches:宽基线匹配 宽基线匹配:从描绘同一场景的两个或多个图像中建立 ...

  8. Java中的函数式编程(六)流Stream基础

    写在前面 如果说函数式接口和lambda表达式是Java中函数式编程的基石,那么stream就是在基石上的最富丽堂皇的大厦. 只有熟悉了stream,你才能说熟悉了Java 的函数式编程. 本文主要介 ...

  9. 微信小程序的发布流程

    一.背景 在中大型的公司里,人员的分工非常仔细,一般会有不同岗位角色的员工同时参与同一个小程序项目.为此,小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作 以往我们在开发 ...

  10. websocket入门案例(echo)

    websocket是用来干什么的,具体的请自行百度. 本文实现一个简单的websocket的入门小例子,实现客户端发送一句换,服务器端返回.即一个简单的交互. 一.服务器端的实现 1.创建一个类实现S ...