Dapr .NetCore Actor 电脑版发表于:2021/12/20 15:28 ![](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)