Dapr + .NET 实战(五)Actor

什么是Actor模式

Actors 为最低级别的“计算单元”

以上解释来自官方文档,看起来“晦涩难懂”。大白话就是说Actors模式是一段需要单线程执行的代码块。

实际开发中我们经常会有一些逻辑不能并发执行,我们常用的做法就是加锁,例如:

lock(obj)
{//dosomething...
}

或者用Redis等中间件,为分布式应用加一些分布式锁。遗憾的是,使用显式锁定机制容易出错。它们很容易导致死锁,并可能对性能产生严重影响。Actors模式为单线程逻辑提供了一种更好的选择。

什么时候用Actors

  • 需要单线程执行,比如需要加lock

  • 逻辑可以被划分为小的执行单元

工作原理

7dc75e23dc124cf4e1bae6a619ea5f5d.png

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

99358fd6aa6aa66ed6c9acf237514860.png

 在客户端调用某个Actor时,安置服务会根据其Id和Actor类型,找到其所在的dapr实例,并执行其方法。

调用Actors方法

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: actorStateStorevalue: "true"

win10自承载模式下已默认设置此项 C:\Users\<username>\.dapr\components\statestore.yaml

项目实例

Actor操作

下面将通过一个审核流程的例子来演示。

还是用前面的FrontEnd项目,引入nuget包Dapr.Actors和Dapr.Actors.AspNetCore

841d231570dd054e44ccd4745b8f7c7b.png

 定义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 paidawait 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 注册 services.AddActors :

services.AddActors(options =>{options.Actors.RegisterActor<OrderStatusActor>();});

为测试这个Actor,需要定义一个接口调用,新增ActorController

c4d271495877395b7b304e133712e8fd.png

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("myid-"+orderId);var proxy = ActorProxy.Create<IOrderStatusActor>(actorId, "OrderStatusActor");var result = await proxy.Paid(orderId);return Ok(result);}}
}

ActorProxy.Create 为创建代理实例。 Create方法采用两个参数:标识特定执行组件和执行组件 ActorId 类型。它还具有一个泛型类型参数,用于指定执行组件类型所实现的执行组件接口。由于服务器和客户端应用程序都需要使用执行组件接口,它们通常存储在单独的共享项目中。

启动FrontEnd

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

下面通过postman测试下,调用成功

46e9f1b2d38dc17a2cabcc5f710c5e99.png

 查看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));}

重新启动FrontEnd

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

postman测试

57b485b89988ed6039f5e985675e0acf.png

 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 采用五个参数:

  1. 计时器的名称。

  2. 触发计时器时要调用的方法的名称。

  3. 要传递给回调方法的状态。

  4. 首次调用回调方法之前要等待的时间。

  5. 回调方法调用之间的时间间隔。可以指定 以 TimeSpan.FromMilliseconds(-1) 禁用定期信号。

在OrderStatusActor构造方法中调用StartTimerAsync

StartTimerAsync("test-timer", "this is a test timer").ConfigureAwait(false).GetAwaiter().GetResult();

重新启动FrontEnd

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

通过调用paid接口实例化一个Actor,即可开启timer

6e7fdc9040aab56f3de3f329012f8e7c.png

 查看控制台,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 个参数:

  1. 提醒的名称。

  2. 注册期间提供的用户状态。

  3. 注册期间提供的调用到期时间。

  4. 注册期间提供的调用周期。

在OrderStatusActor构造方法中调用SetReminderAsync

SetReminderAsync("this is a test reminder").ConfigureAwait(false).GetAwaiter().GetResult();

重新启动FrontEnd

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

通过调用paid接口实例化一个Actor,即可开启reminder

31bbf7cb96665aa30df6a4d09a85239b.png

 查看控制台,reminder触发成功

== APP == warn: FrontEnd.ActorDefine.OrderStatusActor[0]
== APP ==       reminder fired: this is a test reminder
相关文章:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/297511.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打印机一直显示正在打印中_中国和桌面3D打印机正在引领3D打印市场

2020年11月4日&#xff0c;南极熊获悉&#xff0c;国外的市场研究公司CONTEXT最近发布了3D打印市场报告&#xff0c;“中国”和“桌面3D打印机”正引领全球的3D打印市场从COVID-19疫情中复苏。分析报告显示&#xff0c;在2020年第一季度至第二季度期间&#xff0c;中国国内工业…

python中的星号和乘号_Python 函数中参数前面一个和两个星号(**)的区别

Python是一种计算机程序设计语言。是一种面向对象的动态类型语言&#xff0c;最初被设计用于编写自动化 脚本( shell)&#xff0c;随着版本的不断更新和语言新功能的添加&#xff0c;越来越多被用于独立的、大型项目的开发。在 Python 的函数中经常能看到输入的参数前面有一个或…

sql重新注册服务器,sql server无法新建注册服务器怎么办?

SQL Server 不存在或访问被拒绝ConnectionOpen (Connect())这个"SQL Server 不存在或访问被拒绝"通常是最复杂的&#xff0c;错误发生的原因比较多&#xff0c;需要检查的方面也比较多。一般说来&#xff0c;有以下几种可能性&#xff1a;1、SQL Server名称或IP地址拼…

java中的递归

所谓递归&#xff0c;是指程序调用自身&#xff0c;当然&#xff0c;递归不会无休止地调用下去&#xff0c;它必然有一个出口&#xff0c;当满足条件时程序也就结束了&#xff0c;不然的话&#xff0c;那就是死循环了。看下面这个类&#xff0c;有几个递归方法&#xff0c;看了…

雨中的蚊子为啥不会被雨滴砸死?

全世界只有3.14 % 的人关注了爆炸吧知识在细雨中漫步是很浪漫的一件事&#xff0c;但作为蚊子&#xff0c;雨中飞行相当于天上掉汽车&#xff01;你有没有想过&#xff0c;它们是怎么活下来的&#xff1f;直到2015年&#xff0c;菠萝科学奖物理学奖颁给美国佐治亚理工学院胡立德…

.NET 6 迁移到 Minimal API

.NET 6 迁移到 Minimal APIIntro上次写了一篇 Minimal API Todo Sample&#xff0c;有些童鞋觉得 Minimal API 有些鸡肋&#xff0c;有一些功能的支持都不太好&#xff0c;但是其实 Host 之前支持的功能 Minimal API 大部分都是支持的&#xff0c;上次的 Todo Sample 完全没有使…

教你透彻了解红黑树

教你透彻了解红黑树 作者&#xff1a;July、saturnman 2010年12月29日本文参考&#xff1a;Google、算法导论、STL源码剖析、计算机程序设计艺术。本人声明&#xff1a;个人原创&#xff0c;转载请注明出处。推荐阅读&#xff1a;Left-Leaning Red-Black Trees, Dagstuhl Wor…

cass字体_不动产 准备工作 第一步: 管理CASS码

管理CASS码https://www.zhihu.com/video/1063850168960647168管理CASS码 功能概述&#xff1a;通过管理CASS码将不动产基础矢量数据分为房屋、房屋附属、其他设施三类&#xff0c;同时通过管理CASS码可以对建筑物面积计算规则进行自定义和统设、以及设置建筑物注记文本(和数据入…

lol修改服务器域名,LOL历史转区用户解冻大区官网自助系统地址 新版申请解冻账号网址...

原标题&#xff1a;LOL历史转区用户解冻大区官网自助系统地址 新版申请解冻账号网址英雄联盟在9月1日正式上线了历史转区用户解冻服务&#xff0c;因转区导致冻结账号的玩家可以在这边申请解冻了&#xff0c;很多玩家还不清楚申请的地址在哪&#xff0c;下面就来为大家详细的介…

c语言浮点型常量表示平均数_小白基础知识必备|| 整型常量与进制间的转换

一、C语言关键字C语言的关键字共有32个&#xff0c;根据关键字的作用&#xff0c;可分为数据类型关键字、控制语句关键字、存储类型关键字和其它关键字四类。数值类型关键字(12个)void、char、short、int、long、float、double、signed、unsigned、struct、enum、union控制语句…

2.页面布局示例笔记

1.CSS中的三种定位机制 标准文档流 就是流式布局中的定位浮动定位相对定位 2.当元素设置了float或者相对定位的时候&#xff0c;就无法通过设置margin的auto进行居中了 3.设置了浮动的元素任然处于标准文档流当中&#xff0c;任然会占用标准文档流的影响。而不像相对定位…

中国代工厂的困惑:把大牌t恤卖到99块3件,还会有人买吗?

▲ 点击查看“很多人都知道大牌有溢价但是不知道大牌溢价逼近900%打个比方一件1000块的T恤&#xff0c;T恤成本如果是100那么剩下的900&#xff0c;算是买了个大牌logo”这是我们上个月去到的一家中国代工厂工厂内部人员向我们透露的他说&#xff0c;其实无论是纪梵希、爱马仕、…

LUA面向对象编程技巧

详文请见 http://ffown.sinaapp.com/?p11 1. LUA中的对象 我们知道&#xff0c;对象由属性和方法组成。LUA中最基本的结构是table&#xff0c;So 必须用table描述对象的属性。lua中的function可以用来表示方法。那么LUA中的类 可以通过table function模拟出来。至于继承&…

.NET 排序 Array.SortT 实现分析

System.Array.Sort<T> 是.NET内置的排序方法, 灵活且高效, 大家都学过一些排序算法&#xff0c;比如冒泡排序,插入排序,堆排序等&#xff0c;不过你知道这个方法背后使用了什么排序算法吗?先说结果, 实际上 Array.Sort 不止使用了一种排序算法, 为了保证不同的数据量的排…

Android 中文api (81)——InputMethod [输入法]

前言 本章内容是android.view.inputmethod.InputMethod&#xff0c;为输入法相关章节&#xff0c;版本为Android 2.3 r1&#xff0c;翻译来自"六必治"&#xff0c;欢迎大家访问他的博客&#xff1a;http://www.cnblogs.com/zcmky/&#xff0c;再次感谢"六必治&q…

联想电脑如何添加无线网络连接服务器,安装英特尔MYWIFI的操作步骤

适用范围:(1)操作系统&#xff1a;仅支持VISTA /WINDOWS 7&#xff0c;不支持WINDOWS XP/2003/2000&#xff1b;(2)硬件&#xff1a;INTEL MY WIFI支持INTEL 5100以及以上无线网卡&#xff0c;非INTEL无线网卡不支持。知识点分析:英特尔的MY WIFI技术是一项针对笔记本电脑无线网…

假如把女生比作一种水果

1 和睡相不好的人一起睡觉是什么体验&#xff1f;2 箱子里的是我方输出&#xff0c;外面的是对方打野3 女儿问爸爸小时候都玩什么&#xff0c;于是爸爸给她做了这个。。4 推上一网友随手拍到的照片&#xff0c;就好像是三张图片拼起来的一样。5 一位台湾艺用解剖学老师的硬核授…

张萍萍 计科高职13-1 201303014010

通过这次的实践&#xff0c;我第一次感觉学好一门英语是多么的重要&#xff0c;这次历尽千辛万险才把作业完成&#xff0c;通过这次实践我发现我还有许多的地方进行改进&#xff0c;不过通过这次试验我也学到了不少的东西&#xff0c;我学会了如何使用gethub来管理代码和如何管…

python tkinter进度条_在python3.7中更新tkinter进度条

抱歉&#xff0c;花了一段时间&#xff0c;但我能搞定。在 我不知道你遇到了什么与Python3.x不兼容的地方&#xff0c;但我找到了我跟踪的这个更新的视频。在 除了“停止”命令之外&#xff0c;它几乎完美地工作了&#xff0c;我无法开始工作。在from tkinter import * from tk…

ai怎么调界面大小_科研论文作图系列-从PPT到AI (一)

这是“投必得学术”推送的第44篇文章&#xff0c;专注科研技能和资讯分享&#xff01;关注“投必得学术”&#xff0c;可以看到我们所有干货和资讯&#xff01;导语&#xff1a;之前的推送中&#xff0c;小编给大家介绍过几款科研作图软件&#xff0c;包括统计分析软件Origin和…