首页
视频
资源
登录
原
Dapr .NetCore Actor
3090
人阅读
2021/12/20 15:28
总访问:
2586487
评论:
0
收藏:
0
手机
分类:
.net后台框架
![](https://img.tnblog.net/arcimg/hb/896fd38e95b346f9a0d98c54b135bb94.jpg) >#Dapr .NetCore Actor [TOC] Actor简介 ------------ tn2>简单来讲就是锁的作用,可以用作单线程调用实例,起到加锁的效果。解决了并发抢票的。 比如:抢车票的应用场景。 工作原理 ------------ ![](https://img.tnblog.net/arcimg/hb/25c3b99c88fb4c6aa695cddcf16bdaab.png) tn2>Dapr启动app时,Sidecar调用Actors获取配置信息,之后Sidecar将Actors的信息发送到 **安置服务(Placement Service)**,安置服务会将不同的Actor类型根据其Id和Actor类型分区,并将Actor信息广播到所有dapr实例。 ![](https://img.tnblog.net/arcimg/hb/4b2d82b8dc0e4bf9ac4fe284e16520ff.png) tn2> 在客户端调用某个Actor时,安置服务会根据其Id和Actor类型,找到其所在的dapr实例,并执行其方法。 tn>调用Actors方法 ```bash POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method> ``` | 参数名 | 描述 | | ------------ | ------------ | | `actorType` | 执行组件类型。 | | `actorId` | 要调用的特定参与者的。 | | `method` | 要调用的方法。 | 抢订单Demo ------------ tn2>我们通过100个线程并发访问来进行抢10个商品多案例。 >###创建ActorDaprManager项目 tn2>首先安装相关依赖 ```xml <PackageReference Include="Dapr.Actors.AspNetCore" Version="1.4.0" /> <PackageReference Include="Dapr.AspNetCore" Version="1.4.0" /> ``` tn2>安装其他依赖项 ```xml <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="ServiceStack.Redis" Version="5.13.2" /> <PackageReference Include="StackExchange.Redis" Version="2.2.88" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> ``` >###创建 Actor ![](https://img.tnblog.net/arcimg/hb/4fe03d0b78164af3b8934d8dd7fdf233.png) ```csharp public interface IDemoActor : IActor { /// <summary> /// 初始化10个库存 /// </summary> /// <returns></returns> public Task Init(); /// <summary> /// 获取库存 /// </summary> /// <returns></returns> public Task<long> GetInventory(); /// <summary> /// 下订单 /// </summary> /// <returns></returns> public Task<long> SetOrder(); } ``` ```csharp public class DemoActor : Actor, IDemoActor { RedisHelper _RedisHelper; public DemoActor(ActorHost host, RedisHelper redisHelper) : base(host) { _RedisHelper = redisHelper; } /// <summary> /// 初始化库存 /// </summary> /// <returns></returns> public async Task Init() { await StateManager.SetStateAsync("init", "init"); await _RedisHelper.GetDatabase().StringSetAsync("InventoryNum", 10); await _RedisHelper.GetDatabase().StringSetAsync("OrderNum", 0); } /// <summary> /// 获取剩余库存 /// </summary> /// <returns></returns> public Task<long> GetInventory() { var result = _RedisHelper.GetDatabase().StringGet("InventoryNum").ConvertTo<long>(); return Task.FromResult(result); } /// <summary> /// 下订单 /// </summary> /// <returns></returns> public Task<long> SetOrder() { var result = GetInventory().Result; if (result > 0) { _RedisHelper.GetDatabase().StringIncrement("OrderNum"); return _RedisHelper.GetDatabase().StringDecrementAsync("InventoryNum"); } return Task.FromResult(long.Parse("-1")); } } ``` tn2>关于StateManager存储默认是由`~/.dapr/components/statestore.yaml`指定的,一般都是Redis做存储,要使用时还需启动`statestore.yaml`中的`actorStateStore`参数才行。 ```yaml 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" ``` tn2>除此之外,还需要创建RedisHelper,其实也可以换成其他的存储。 ```csharp public class RedisHelper { /// <summary> /// Redis操作对象 /// </summary> protected readonly IDatabase redis = null; /// <summary> /// 初始化Redis操作方法基础类 /// </summary> /// <param name="dbNum"></param> public RedisHelper(string connstr = "127.0.0.1:6380", int dbNum = 0) { redis = ConnectionMultiplexer.Connect(connstr).GetDatabase(dbNum); } public IDatabase GetDatabase() { return redis; } } public static class IntTool { /// <summary> /// 将值反系列化成对象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <returns></returns> public static T ConvertObj<T>(this RedisValue value) { return value.IsNullOrEmpty ? default(T) : JsonConvert.DeserializeObject<T>(value); } } ``` tn>注意Redis的Ip与端口 tn2>定义了三个方法 | 方法名 | 描述 | | ------------ | ------------ | | `Init` | 初始化库存10个 | | `GetInventory` | 获取剩余库存 | | `SetOrder` | 下订单 | tn2>注册相关服务 ```csharp public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<RedisHelper, RedisHelper>(); services.AddControllers().AddDapr(); services.AddActors(option => { option.Actors.RegisterActor<DemoActor>(); }); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "HMY", Version = "v1" }); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "HMY v1")); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapActorsHandlers(); endpoints.MapControllers(); }); } ``` tn2>添加`DemoController`控制器 ```csharp [ApiController] public class DemoController:Controller { [HttpPost("Init")] public void Init() { var actorinit = GetDemo_Actor(Guid.NewGuid().ToString()); actorinit.Init().Wait(); } /// <summary> /// 不同的 /// </summary> /// <returns></returns> [HttpPost("DemoDifferentActor")] public string DemoDifferentActor() { var actor = GetDemo_Actor(Guid.NewGuid().ToString()); actor.SetOrder(); return "ok"; } /// <summary> /// 同一个 /// </summary> /// <returns></returns> [HttpPost("DemoSameActor")] public string DemoSameActor() { var actor = actorid; actor.SetOrder(); return "ok"; } public static IDemoActor actorid = GetDemo_Actor("bcd"); public static IDemoActor GetDemo_Actor(string actorid) { var actorId = new ActorId(actorid.ToString()); return ActorProxy.Create<IDemoActor>(actorId, nameof(ActorDaprManager.Actors.DemoActor)); } } ``` tn2>我们定义了三个方法,Init用作初始化,DemoDifferentActor是不同的actorId创建出的不同实例,DemoSameActor是相同的actorId创建的相同的实例。 >### 运行并初始化数据 ```csharp dapr run --app-id myapp --app-port 5000 --dapr-http-port 3500 -- dotnet run ``` ![](https://img.tnblog.net/arcimg/hb/008b2493f99e4a5ab22a35dcee1b7f93.png) tn2>在我们的Swagger中,调用Demo控制器中的Init方法。 ![](https://img.tnblog.net/arcimg/hb/668c3220e64a434eac2d8a48b9d3f152.png) ![](https://img.tnblog.net/arcimg/hb/45ac8c26a0274a9098bba09caf3a9d6b.png) ![](https://img.tnblog.net/arcimg/hb/de94f4fd773d48a595bc331601cc4c7f.png) >### Jmeter配置 tn2>并发调用100个线程同时请求10个商品的情况 ![](https://img.tnblog.net/arcimg/hb/fb0bcdefd18a498eb68c65935d07780d.png) ![](https://img.tnblog.net/arcimg/hb/d5e7f286ef4443a5b1601d5c71f9db2f.png) ![](https://img.tnblog.net/arcimg/hb/4c7a653e4cb34c61b276b85f188e7909.png) >### 调用不同Actor进行测试 ![](https://img.tnblog.net/arcimg/hb/0888129b1cbf4221b2a1273dc96d0d58.png) tn2>首先我们看到每一秒处理4347个请求这个性能还是很高的。接下来我们来看看请求后的数据结果。 ![](https://img.tnblog.net/arcimg/hb/c72a58703be147c2898d50936d650895.png) ![](https://img.tnblog.net/arcimg/hb/cab48be54e584dea8fa747e6baee5df7.png) tn2>我们发现购买时已经超过了我们原有的库存量,发生并发问题。可以通过下图来进行理解。 ![](https://img.tnblog.net/arcimg/hb/c1ab0f8af0664ba3abec75653dc6666e.png) >### 调用相同Actor进行测试 tn2>首先需要初始化应用数据,自己调吧!就Init那方法。 然后进行调用相同的ActorId ![](https://img.tnblog.net/arcimg/hb/7923b3f7ceb9402d850f2006768182af.png) ![](https://img.tnblog.net/arcimg/hb/4df2939b371d435fa663f3339b51f115.png) ![](https://img.tnblog.net/arcimg/hb/ee30fcaf36ba475998b9ec346592c637.png) tn2>我们发现它执行时加上锁了的。执行流程如下图所示。 ![](https://img.tnblog.net/arcimg/hb/8d711628384c430289df901ac4144e3a.png) Timer的使用 ------------ tn2>定时触发Actor中的某个方法。 结合上面案例添加相关接口与代码的实现。 ```csharp public interface IDemoActor : IActor { ... /// <summary> /// 注册Timer /// </summary> /// <returns></returns> public Task RegisterTime(); /// <summary> /// 注销Timer /// </summary> /// <returns></returns> public Task UnRegisterTime(); /// <summary> /// Timer回调 /// </summary> /// <returns></returns> public Task TimerCallBack(); } ``` tn2>`RegisterTime`方法设置第一次间隔5秒执行一次TimerCallBack随后每5秒调用一次,并将timer命名为Test。 `UnregisterTimerAsync`方法注销Test Timer。 `TimerCallBack`仅做一个日志打印。 ```csharp public class DemoActor : Actor, IDemoActor { RedisHelper _RedisHelper; ILogger<DemoActor> _log; public DemoActor( ActorHost host, RedisHelper redisHelper, ILogger<DemoActor> log ) : base(host) { _log = log; _RedisHelper = redisHelper; } /// <summary> /// 初始化库存 /// </summary> /// <returns></returns> public async Task Init() { await StateManager.SetStateAsync("init", "init"); await _RedisHelper.GetDatabase().StringSetAsync("InventoryNum", 10); await _RedisHelper.GetDatabase().StringSetAsync("OrderNum", 0); } /// <summary> /// 获取剩余库存 /// </summary> /// <returns></returns> public Task<long> GetInventory() { var result = _RedisHelper.GetDatabase().StringGet("InventoryNum").ConvertTo<long>(); return Task.FromResult(result); } /// <summary> /// 下订单 /// </summary> /// <returns></returns> public Task<long> SetOrder() { var result = GetInventory().Result; if (result > 0) { _RedisHelper.GetDatabase().StringIncrement("OrderNum"); return _RedisHelper.GetDatabase().StringDecrementAsync("InventoryNum"); } return Task.FromResult(long.Parse("-1")); } public Task RegisterTime() { return this.RegisterTimerAsync("Test",nameof(this.TimerCallBack),null,TimeSpan.FromSeconds(5),TimeSpan.FromSeconds(5)); } public Task UnRegisterTime() { return this.UnregisterTimerAsync("Test"); } public Task TimerCallBack() { _log.LogInformation($"[{DateTime.Now}]--Thread: {Thread.CurrentThread.ManagedThreadId} Log"); return Task.CompletedTask; } } ``` tn2>在控制器中添加对应的方法。 ```csharp /// <summary> /// 启动Timer /// </summary> /// <returns></returns> [HttpPost("RegisterTime")] public string RegisterTime() { actorid.RegisterTime(); return "ok"; } /// <summary> /// 注销Timer /// </summary> /// <returns></returns> [HttpPost("UnRegisterTime")] public string UnRegisterTime() { actorid.UnRegisterTime(); return "ok"; } ``` >### 运行测试 tn2>先启动该Timer。 ![](https://img.tnblog.net/arcimg/hb/ff2abf725bda4a04a6695abc22a35394.png) tn2>查看定时日志输出情况。 ![](https://img.tnblog.net/arcimg/hb/c1012b96a0b74ce384548fc955d150be.png) tn2>最后注销该Timer。 ![](https://img.tnblog.net/arcimg/hb/4c1007424ddc4c4db01296d87e222a54.png) Reminder ------------ tn2>与Timer计算器的功能是一样的,只是当Actor实例释放或者调度到其他主机上时,Reminder将会继续开启调度,并因此重新创建actor。 ```csharp /// <summary> /// 注册Reminder /// </summary> /// <returns></returns> public Task RegisterReminder(); /// <summary> /// 注销Reminder /// </summary> /// <returns></returns> public Task UnRegisterReminder(); ``` tn2>在DemoActor中实现IRemindable接口 ```csharp public class DemoActor : Actor, IDemoActor,IRemindable ``` ```csharp public Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period) { _log.LogInformation($"Reminder [{DateTime.Now}]--Thread: {Thread.CurrentThread.ManagedThreadId} Log"); return Task.CompletedTask; } public Task RegisterReminder() { _log.LogInformation("Reminder 已经注册"); return this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(2)); } public Task UnRegisterReminder() { _log.LogInformation("Reminder 已经注销"); return this.UnregisterReminderAsync("TestReminder"); } ``` tn2>并在控制器中定义相关接口 ```csharp /// <summary> /// 启动Reminder /// </summary> /// <returns></returns> [HttpPost("RegisterReminder")] public string RegisterReminder() { actorid.RegisterReminder(); return "ok"; } /// <summary> /// 注销Reminder /// </summary> /// <returns></returns> [HttpPost("UnRegisterReminder")] public string UnRegisterReminder() { actorid.UnRegisterReminder(); return "ok"; } ``` tn2>测试附上一张图 ![](https://img.tnblog.net/arcimg/hb/ed14fff3794d475cb0e9882a89b8d401.png)
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739
👈{{preArticle.title}}
👉{{nextArticle.title}}
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}
尘叶心繁
这一世以无限游戏为使命!
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
.net后台框架
168篇
linux
17篇
linux中cve
1篇
windows中cve
0篇
资源分享
10篇
Win32
3篇
前端
28篇
传说中的c
4篇
Xamarin
9篇
docker
15篇
容器编排
101篇
grpc
4篇
Go
15篇
yaml模板
1篇
理论
2篇
更多
Sqlserver
4篇
云产品
39篇
git
3篇
Unity
1篇
考证
2篇
RabbitMq
23篇
Harbor
1篇
Ansible
8篇
Jenkins
17篇
Vue
1篇
Ids4
18篇
istio
1篇
架构
2篇
网络
7篇
windbg
4篇
AI
18篇
threejs
2篇
人物
1篇
嵌入式
2篇
python
13篇
HuggingFace
8篇
pytorch
9篇
opencv
6篇
最新文章
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2024
TNBLOG.NET
技术交流:
群号656732739
联系我们:
contact@tnblog.net
欢迎加群
欢迎加群交流技术