Dapr + .NET Core实战(五)Actor
什么是Actor模式
Actors 为最低级别的“计算单元”
以上解释来自官方文档,看起来“晦涩难懂”。大白话就是说Actors模式是一段需要单线程执行的代码块。
实际开发中我们经常会有一些逻辑不能并发执行,我们常用的做法就是加锁,例如:
lock(obj)
{
//dosomething...
}
或者用Redis等中间件,为分布式应用加一些分布式锁。遗憾的是,使用显式锁定机制容易出错。 它们很容易导致死锁,并可能对性能产生严重影响。Actors模式为单线程逻辑提供了一种更好的选择。
什么时候用Actors
- 需要单线程执行,比如需要加lock
- 逻辑可以被划分为小的执行单元
工作原理

Dapr启动app时,Sidecar调用Actors获取配置信息,之后Sidecar将Actors的信息发送到安置服务(Placement Service),安置服务会将不同的Actor类型根据其Id和Actor类型分区,并将Actor信息广播到所有dapr实例。

在客户端调用某个Actor时,安置服务会根据其Id和Actor类型,找到其所在的dapr实例,并执行其方法。
调用Actor方法
POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method>
<actorType>:执行组件类型。<actorId>:要调用的特定参与者的 ID。<method>:要调用的方法
计时器Timers和提醒器Reminders
Actor可以设置timer和reminder设置执行Actor的时间,有点像我们常用的定时任务。但是timer和reminder也存在不同。
- timer只作用于激活状态的Actor。一个Actor长期不被调用,其自己的空闲计时器会逐渐累积,到一定时间后会被Dapr销毁,timer没法作用于已销毁的Actor。
- reminder则可以作用于所有状态的Actor。主要方式是重置空闲计时器,使其处于活跃状态
操作timer
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>
到期时间(due time)表示注册后 timer 将首次触发的时间。 period 表示timer在此之后触发的频率。 到期时间为0表示立即执行。 负 due times 和负 periods 都是无效。
下面的请求体配置了一个 timer, dueTime 9秒, period 3秒。 这意味着它将在9秒后首次触发,然后每3秒触发一次。
{
"dueTime":"0h0m9s0ms",
"period":"0h0m3s0ms"
}
下面的请求体配置了一个 timer, dueTime 0秒, period 3秒。 这意味着它将在注册之后立即触发,然后每3秒触发一次。
{
"dueTime":"0h0m0s0ms",
"period":"0h0m3s0ms"
}
操作reminder
POST/PUT/GET/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
到期时间(due time)表示注册后 reminders将首次触发的时间。 period 表示在此之后 reminders 将触发的频率。 到期时间为0表示立即执行。 负 due times 和负 periods 都是无效。 若要注册仅触发一次的 reminders ,请将 period 设置为空字符串。
下面的请求体配置了一个 reminders, dueTime 9秒, period 3秒。 这意味着它将在9秒后首次触发,然后每3秒触发一次。
{
"dueTime":"0h0m9s0ms",
"period":"0h0m3s0ms"
}
下面的请求体配置了一个 reminders, dueTime 0秒, period 3秒。 这意味着它将在注册之后立即触发,然后每3秒触发一次。
{
"dueTime":"0h0m0s0ms",
"period":"0h0m3s0ms"
}
下面的请求体配置了一个 reminders, dueTime 15秒, period 空字符串。 这意味着它将在15秒后首次触发,之后就不再被触发。
{
"dueTime":"0h0m15s0ms",
"period":""
}
数据持久化
使用 Dapr 状态管理构建块保存执行组件状态。 由于执行组件可以一轮执行多个状态操作,因此状态存储组件必须支持多项事务。 撰写本文时,以下状态存储支持多项事务:
- Azure Cosmos DB
- MongoDB
- MySQL
- PostgreSQL
- Redis
- RethinkDB
- SQL Server
若要配置要与执行组件一起使用的状态存储组件,需要将以下元数据附加到状态存储配置:
- name: actorStateStore
value: "true"
win10自承载模式下已默认设置此项 C:\Users\<username>\.dapr\components\statestore.yaml
项目实例
Actor操作
下面将通过一个审核流程的例子来演示。
还是用前面的FrontEnd项目,引入nuget包Dapr.Actors和Dapr.Actors.AspNetCore。

定义IOrderStatusActor接口,需要继承自IActor
using Dapr.Actors; using System.Threading.Tasks; namespace FrontEnd.ActorDefine
{
public interface IOrderStatusActor : IActor
{
Task<string> Paid(string orderId);
Task<string> GetStatus(string orderId);
}
}
定义OrderStatusActor实现IOrderStatusActor,并继承自Actor
using Dapr.Actors.Runtime; using System.Threading.Tasks; namespace FrontEnd.ActorDefine
{
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);
} }
}
需要注意的是,执行组件方法的返回类型必须为 Task 或 Task<T> 。 此外,执行组件方法最多只能有一个参数。 返回类型和参数都必须可 System.Text.Json 序列化。
Actor的api是必需的,因为 Dapr 挎斗调用应用程序来承载和与执行组件实例进行交互,所以在Startup的Configure中配置
app.UseEndpoints(endpoints =>
{
endpoints.MapActorsHandlers();
// .......
});
Startup类也是用于注册特定执行组件类型的位置。 在ConfigureServices ScoreActor 使用注册 services.AddActors :
services.AddActors(options =>
{
options.Actors.RegisterActor<OrderStatusActor>();
});
为测试这个Actor,需要定义一个接口调用,新增ActorController
using Dapr.Actors;
using Dapr.Actors.Client; using FrontEnd.ActorDefine; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; namespace FrontEnd.Controllers
{
[Route("[controller]")]
[ApiController]
public class ActorController : ControllerBase
{
[HttpGet("paid/{orderId}")]
public async Task<ActionResult> PaidAsync(string orderId)
{
var actorId = new ActorId(orderId);
var proxy = ActorProxy.Create<IOrderStatusActor>(actorId, "OrderStatusActor");
var result = await proxy.Paid(orderId);
return Ok(result);
}
}
}
ActorProxy.Create 为创建代理实例。 Create方法采用两个参数:标识特定执行组件和执行组件 ActorId 类型。 它还具有一个泛型类型参数,用于指定执行组件类型所实现的执行组件接口。 由于服务器和客户端应用程序都需要使用执行组件接口,它们通常存储在单独的共享项目中。
下面通过postman测试下,调用成功

查看redis中的数据
127.0.0.1:6379> keys *
1) "test_topic"
2) "frontend||guid"
3) "frontend||name"
5) "newOrder"
6) "frontend||OrderStatusActor||myid-123||123"
7) "myapp2||key2"
8) "myapp2||key1"
9) "deathStarStatus"
10) "myapp||name"
127.0.0.1:6379> hgetall frontend||OrderStatusActor||myid-123||123
1) "data"
2) "\"init\""
3) "version"
4) "1"
可以发现actor数据的命名规则是appName||ActorName||ActorId||key
同样可以使用注入的方式创建proxy,ActorController中注入IActorProxyFactory
private readonly IActorProxyFactory _actorProxyFactory;
public ActorController(IActorProxyFactory actorProxyFactory)
{
_actorProxyFactory = actorProxyFactory;
}
新增获取数据接口
[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));
}
postman测试

Timer操作
使用Actor基类的 RegisterTimerAsync 方法计划计时器。在OrderStatusActor类中新增方法
public Task StartTimerAsync(string name, string text)
{
return RegisterTimerAsync(
name,
nameof(TimerCallbackAsync),
Encoding.UTF8.GetBytes(text),
TimeSpan.Zero,
TimeSpan.FromSeconds(3));
} public Task TimerCallbackAsync(byte[] state)
{
var text = Encoding.UTF8.GetString(state); _logger.LogInformation($"Timer fired: {text}"); return Task.CompletedTask;
}
StartTimerAsync方法调用 RegisterTimerAsync 来计划计时器。 RegisterTimerAsync 采用五个参数:
- 计时器的名称。
- 触发计时器时要调用的方法的名称。
- 要传递给回调方法的状态。
- 首次调用回调方法之前要等待的时间。
- 回调方法调用之间的时间间隔。 可以指定 以
TimeSpan.FromMilliseconds(-1)禁用定期信号。
在OrderStatusActor构造方法中调用StartTimerAsync
StartTimerAsync("test-timer", "this is a test timer").ConfigureAwait(false).GetAwaiter().GetResult();
通过调用paid接口实例化一个Actor,即可开启timer

查看控制台,timer触发成功
== APP == info: FrontEnd.ActorDefine.OrderStatusActor[0]
== APP == Timer fired: this is a test timer
TimerCallbackAsync方法以二进制形式接收用户状态。 在示例中,回调在将状态写入日志之前将状态 string 解码回 。
可以通过调用 来停止计时器 UnregisterTimerAsync :
public Task StopTimerAsync(string name)
{
return UnregisterTimerAsync(name);
}
Reminder操作
使用Actor基类的 RegisterReminderAsync 方法计划计时器。在OrderStatusActor类中新增方法
public Task SetReminderAsync(string text)
{
return RegisterReminderAsync(
"test-reminder",
Encoding.UTF8.GetBytes(text),
TimeSpan.Zero,
TimeSpan.FromSeconds(1));
} public Task ReceiveReminderAsync(
string reminderName, byte[] state,
TimeSpan dueTime, TimeSpan period)
{
if (reminderName == "test-reminder")
{
var text = Encoding.UTF8.GetString(state); Logger.LogWarning($"reminder fired: {text}");
} return Task.CompletedTask;
}
RegisterReminderAsync方法类似于 RegisterTimerAsync ,但不必显式指定回调方法。 如上面的示例所示,实现 IRemindable.ReceiveReminderAsync 以处理触发的提醒。
public class OrderStatusActor : Actor, IOrderStatusActor, IRemindable
ReceiveReminderAsync触发提醒时调用 方法。 它采用 4 个参数:
- 提醒的名称。
- 注册期间提供的用户状态。
- 注册期间提供的调用到期时间。
- 注册期间提供的调用周期。
在OrderStatusActor构造方法中调用SetReminderAsync
SetReminderAsync("this is a test reminder").ConfigureAwait(false).GetAwaiter().GetResult();
通过调用paid接口实例化一个Actor,即可开启reminder

查看控制台,reminder触发成功
== APP == warn: FrontEnd.ActorDefine.OrderStatusActor[0]
== APP == reminder fired: this is a test reminder
Dapr + .NET Core实战(五)Actor的更多相关文章
- Dapr + .NET Core实战(七)Secrets
什么是Secrets 应用程序通常会通过使用专用的存储来存储敏感信息,如连接字符串.密钥等. 通常这需要建立一个密钥存储,如Azure Key Vault.Hashicorp等,并在那里存储应用程序级 ...
- Dapr + .NET Core实战(十四)虚拟机集群部署 mDNS + Consul
前面我们说了在单机模式下和K8S集群下的Dapr实战,这次我们来看看如何在不使用K8S的情况下,在一个传统的虚拟机集群里来部署Dapr. 1.环境准备 我们准备两台centos7虚拟机 Dapr1:1 ...
- Dapr + .NET Core实战(九)本地调试
前几节开发Dapr应用程序时,我们使用 dapr cli 来启动dapr服务,就像这样: dapr run --dapr-http-port 3501 --app-port 5001 --app-id ...
- Dapr + .NET Core实战(十-终篇)K8S运行Dapr
工作原理 为了实现在k8s上安装Dapr,Dapr需要部署dapr-sidecar-injector.dapr-operator.dapr-placement和dapr-sentry服务. dapr- ...
- Dapr + .NET Core实战(十一)单机Dapr集群
如何单机部署Dapr集群 第十篇讲过了K8S集群下如何使用Dapr运行程序,但是很多人一直在问如何单机下进行Dapr的负载,这节课我们来聊聊如何单机进行Dapr的负载. 首先要说的是单机下,通过 da ...
- Dapr + .NET Core实战(十一)单机Dapr集群负载均衡
如何单机部署Dapr集群 第十篇讲过了K8S集群下如何使用Dapr运行程序,但是很多人一直在问如何单机下进行Dapr的负载,这节课我们来聊聊如何单机进行Dapr的负载. 首先要说的是单机下,通过 da ...
- Dapr + .NET Core实战(十二)服务调用之GRPC
什么是GRPC gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架. gRPC 的主要优点是: 高性能轻量级 RPC 框架. 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的 ...
- Dapr + .NET Core实战(十三)跨语言开发
因为基于Dapr的服务架构是不限语言的,我们来看看Dapr的跨语言开发.我们使用golang,python,.NET来实现跨语言的服务调用,拓扑如下 我们继续使用.NET 5的fontend和back ...
- Dapr + .NET Core实战(四)发布和订阅
什么是发布-订阅 发布订阅是一种众所周知并被广泛使用的消息传送模式,常用在微服务架构的服务间通信,高并发削峰等情况.但是不同的消息中间件之间存在细微的差异,项目使用不同的产品需要实现不同的实现类,虽然 ...
随机推荐
- CF上部分树形DP练习题
本次 5 道题均来自Codeforce 关于树形DP的算法讲解:Here 791D. Bear and Tree Jumps 如果小熊每次能跳跃的距离为1,那么问题变为求树上任意两点之间距离之和. 对 ...
- python的基础---常用的正则表达式
"""# 一.re 模块 1.作用:根据规则去匹配字符串 2.表达式:匹配字符串的规则 3.常用方法 findall():[掌握]匹配所有的字符串,把匹配结果作为一个列表 ...
- HTTP协议之:HTTP/1.1和HTTP/2
目录 简介 HTTP/1.1 HTTP/2 传输模式对比 流优先级 缓冲区溢出处理 预测资源请求 压缩 总结 简介 HTTP的全称是Hypertext Transfer Protocol,是在1989 ...
- SpringBoot监听redis订阅监听和发布订阅
前言 我们可以在redis中发布一条订阅到通道中,所有监听了这个通道的都可以收到这个发布的内容! redis订阅监听配置类 代码如下: RedisListenerConfig.java package ...
- HDFS简介及基本概念
(一)HDFS简介及其基本概念 HDFS(Hadoop Distributed File System)是hadoop生态系统的一个重要组成部分,是hadoop中的的存储组件,在整个Hadoop中 ...
- 关于Ubuntu18.04 linux系统下使用Tim QQ 微信
先配上张图 步骤: 1.1 :需要安装环境deepin-wine 1.1:(你把他理解为jdk就好,没有jdk无法运行java程序,同理没有deepin-wine环境无法运行腾讯产品) 1.2 :去哪 ...
- 项目中经常用到的sass语法汇总
1.定义变量 使用:$(符号定义变量) 注意:使用时要带有'$'符号,定义变量的方式与PHP相同 $变量:数值; $color_r : red; div{ color:$color_r; } 2.if ...
- Element form表单方法resetFields无效
之前遇到resetFields无效时都是自己手动用this.ruleForm = Object.assign({}, this.ruleForm, this.$options.data().ruleF ...
- springboot静态资源路径制定
spring.resources.static-location参数指定了Spring Boot-web项目中静态文件存放地址, 该参数默认设置为: classpath:/static, classp ...
- MySQL列举常见的关系型数据库和非关系型都有那些?
关系型数据库: Oracle.DB2.Microsoft SQL Server.Microsoft Access.MySQL 非关系型数据库: NoSql.Cloudant.MongoDb.redis ...