首页 资讯 社群 我的社区 搜索

dapr微服务.netcore sdk入门

LM123
2019-12-19 17:50:32

Actors入门

先决条件

概述

本文档描述如何在客户端应用程序上创建Actor(MyActor)并调用其方法.

MyActor --- MyActor.Interfaces
         |
         +- MyActorService
         |
         +- MyActorClient
  • 接口项目(MyActorMyActor.Interfaces)。此项目包含参与者的接口定义。Actor接口可以在任何名称的项目中定义。接口定义了actor实现和调用actor的客户机共享的actor契约。因为客户机项目可能依赖于它,所以通常在与actor实现分离的程序集中定义它是有意义的.

  • actor服务项目(MyActorMyActor service)。这个项目实现了ASP.Net核心web服务,该服务将承载参与者。它包含actor MyActor.cs的实现。actor实现是从基类型actor派生并实现MyActor.interfaces项目中定义的接口的类。actor类还必须实现一个构造函数,该构造函数接受ActorService实例和ActorId,并将它们传递给基本actor类.

  • actor客户端项目(MyActorMyActor client)此项目包含actor客户端的实现,该客户端调用actor接口中定义的MyActor方法.

第1步 - 创建Actor接口

Actor接口定义Actor实现和调用Actor的客户机共享的Actor契约.

Actor接口定义如下:

  • Actor接口必须继承 Dapr.Actors.IActor 接口
  • Actor方法的返回类型必须是Task或Task<object>
  • Actor方法最多可以有一个参数

创建项目和添加依赖

# Create Actor Interfaces
dotnet new classlib -o MyActor.Interfaces

cd MyActor.Interfaces

# Add Dapr.Actors nuget package
dotnet add package Dapr.Actors

升级项目到 .NET Core 3.0

更新csproj文件中的netcore到 .NET Core 3.0

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Dapr.Actors" Version="0.1.0-preview01" />
  </ItemGroup>

</Project>

实现 IMyActor 接口

定义 IMyActor 接口和 MyData 数据对象.

using Dapr.Actors;
using System.Threading.Tasks;

namespace MyActor.Interfaces
{
    public interface IMyActor : IActor
    {       
        Task<string> SetDataAsync(MyData data);
        Task<MyData> GetDataAsync();
        Task RegisterReminder();
        Task UnregisterReminder();
        Task RegisterTimer();
        Task UnregisterTimer();
    }

    public class MyData
    {
        public string PropertyA { get; set; }
        public string PropertyB { get; set; }

        public override string ToString()
        {
            var propAValue = this.PropertyA == null ? "null" : this.PropertyA;
            var propBValue = this.PropertyB == null ? "null" : this.PropertyB;
            return $"PropertyA: {propAValue}, PropertyB: {propBValue}";
        }
    }
}

第2步 - 创建Actor服务

Dapr使用ASP.NET web服务托管Actor服务. 本节将实现 IMyActor 接口以及注册Actor到Dapr运行时.

创建项目及添加依赖

# Create ASP.Net Web service to host Dapr actor
dotnet new webapi -o MyActorService

cd MyActorService

# Add Dapr.Actors nuget package
dotnet add package Dapr.Actors

# Add Dapr.Actors.AspNetCore nuget package
dotnet add package Dapr.Actors.AspNetCore

# Add Actor Interface reference
dotnet add reference ../MyActor.Interfaces/MyActor.Interfaces.csproj

添加Actor实现

实现IMyActor接口并从Dapr.Actors.Actor类派生。下面的示例还演示了如何使用Actor提醒。对于Actor来说,使用提醒,它必须来源于IRemindable。如果不打算使用提醒功能,可以跳过实现下面代码中显示的IRemindable和提醒特定方法.

using Dapr.Actors;
using Dapr.Actors.Runtime;
using MyActor.Interfaces;
using System;
using System.Threading.Tasks;

namespace MyActorService
{
    internal class MyActor : Actor, IMyActor, IRemindable
    {
        /// <summary>
        /// Initializes a new instance of MyActor
        /// </summary>
        /// <param name="actorService">The Dapr.Actors.Runtime.ActorService that will host this actor instance.</param>
        /// <param name="actorId">The Dapr.Actors.ActorId for this actor instance.</param>
        public MyActor(ActorService actorService, ActorId actorId)
            : base(actorService, actorId)
        {
        }

        /// <summary>
        /// This method is called whenever an actor is activated.
        /// An actor is activated the first time any of its methods are invoked.
        /// </summary>
        protected override Task OnActivateAsync()
        {
            // Provides opportunity to perform some optional setup.
            Console.WriteLine($"Activating actor id: {this.Id}");
            return Task.CompletedTask;
        }

        /// <summary>
        /// This method is called whenever an actor is deactivated after a period of inactivity.
        /// </summary>
        protected override Task OnDeactivateAsync()
        {
            // Provides Opporunity to perform optional cleanup.
            Console.WriteLine($"Deactivating actor id: {this.Id}");
            return Task.CompletedTask;
        }

        /// <summary>
        /// Set MyData into actor's private state store
        /// </summary>
        /// <param name="data">the user-defined MyData which will be stored into state store as "my_data" state</param>
        public async Task<string> SetDataAsync(MyData data)
        {
            // Data is saved to configured state store implicitly after each method execution by Actor's runtime.
            // Data can also be saved explicitly by calling this.StateManager.SaveStateAsync();
            // State to be saved must be DataContract serialziable.
            await this.StateManager.SetStateAsync<MyData>(
                "my_data",  // state name
                data);      // data saved for the named state "my_data"

            return "Success";
        }

        /// <summary>
        /// Get MyData from actor's private state store
        /// </summary>
        /// <return>the user-defined MyData which is stored into state store as "my_data" state</return>
        public Task<MyData> GetDataAsync()
        {
            // Gets state from the state store.
            return this.StateManager.GetStateAsync<MyData>("my_data");
        }

        /// <summary>
        /// Register MyReminder reminder with the actor
        /// </summary>
        public async Task RegisterReminder()
        {
            await this.RegisterReminderAsync(
                "MyReminder",              // The name of the reminder
                null,                      // User state passed to IRemindable.ReceiveReminderAsync()
                TimeSpan.FromSeconds(5),   // Time to delay before invoking the reminder for the first time
                TimeSpan.FromSeconds(5));  // Time interval between reminder invocations after the first invocation
        }

        /// <summary>
        /// Unregister MyReminder reminder with the actor
        /// </summary>
        public Task UnregisterReminder()
        {
            Console.WriteLine("Unregistering MyReminder...");
            return this.UnregisterReminderAsync("MyReminder");
        }

        // <summary>
        // Implement IRemindeable.ReceiveReminderAsync() which is call back invoked when an actor reminder is triggered.
        // </summary>
        public Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
        {
            Console.WriteLine("ReceiveReminderAsync is called!");
            return Task.CompletedTask;
        }

        /// <summary>
        /// Register MyTimer timer with the actor
        /// </summary>
        public Task RegisterTimer()
        {
            return this.RegisterTimerAsync(
                "MyTimer",                  // The name of the timer
                this.OnTimerCallBack,       // Timer callback
                null,                       // User state passed to OnTimerCallback()
                TimeSpan.FromSeconds(5),    // Time to delay before the async callback is first invoked
                TimeSpan.FromSeconds(5));   // Time interval between invocations of the async callback
        }

        /// <summary>
        /// Unregister MyTimer timer with the actor
        /// </summary>
        public Task UnregisterTimer()
        {
            Console.WriteLine("Unregistering MyTimer...");
            return this.UnregisterTimerAsync("MyTimer");
        }

        /// <summary>
        /// Timer callback once timer is expired
        /// </summary>
        private Task OnTimerCallBack(object data)
        {
            Console.WriteLine("OnTimerCallBack is called!");
            return Task.CompletedTask;
        }
    }
}

使用显式actor类型名

默认情况下,客户端看到的actor的“类型”是从actor实现类的名称派生的。如果需要,可以通过将actor attribute属性附加到actor实现类来指定显式类型名.

[Actor(TypeName = "MyCustomActorTypeName")]
internal class MyActor : Actor, IMyActor
{
    // ...
}

注册 Actor 到 Dapr 运行时

将 MyActor 注册到 actor runtime并设置本地主机端口(https://localhost:3000) , Dapr runtime可以通过该端口调用actor.

private const int AppChannelHttpPort = 3000;

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseActors(actorRuntime =>
                {
                    // Register MyActor actor type
                    actorRuntime.RegisterActor<MyActor>();
                }
                )
                .UseUrls($"http://localhost:{AppChannelHttpPort}/");

更新Startup.cs

public class Startup
    {
        ...
        
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRouting();
        }
        
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
        }
    }

第3步 - 添加客户端

创建一个简单的控制台应用程序来调用actor服务。Dapr SDK提供Actor代理客户端来调用Actor接口中定义的Actor方法.

创建项目并添加依赖

# Create Actor's Client
dotnet new console -o MyActorClient

cd MyActorClient

# Add Dapr.Actors nuget package
dotnet add package Dapr.Actors

# Add Actor Interface reference
dotnet add reference ../MyActor.Interfaces/MyActor.Interfaces.csproj

使用Actor远程服务调用Actor方法

我们建议使用本地代理到actor实例,因为ActorProxy.Create<IMyActor>(actorID,actorType)返回强类型actor实例来设置远程过程调用.

namespace MyActorClient
{
    using Dapr.Actors;
    using Dapr.Actors.Client;
    using MyActor.Interfaces;
    using System;
    using System.Threading.Tasks;

    ...
        static async Task InvokeActorMethodWithRemotingAsync()
        {
            var actorType = "MyActor";      // Registered Actor Type in Actor Service
            var actorID = new ActorId("1");

            // Create the local proxy by using the same interface that the service implements
            // By using this proxy, you can call strongly typed methods on the interface using Remoting.
            var proxy = ActorProxy.Create<IMyActor>(actorID, actorType);
            var response = await proxy.SetDataAsync(new MyData()
            {
                PropertyA = "ValueA",
                PropertyB = "ValueB",
            });
            Console.WriteLine(response);

            var savedData = await proxy.GetDataAsync();
            Console.WriteLine(savedData);
        }
    ...
}

非远程方式调用 Actor 方法

如果Actor方法最多接受一个参数,则可以调用Actor方法而无需远程处理(直接通过http或使用ActorProxy中提供的helper方法)。Actor运行时将从客户端反序列化传入的请求体,并将其用作方法参数来调用Actor方法。当进行非远程处理调用时,Actor方法参数和返回类型被序列化,反序列化为JSON.

ActorProxy.Create(actorID, actorType) 返回 ActorProxy 实例并允许使用原始http客户端调用IMyActor中定义的方法.

namespace MyActorClient
{
    using Dapr.Actors;
    using Dapr.Actors.Client;
    using MyActor.Interfaces;
    using System;
    using System.Threading.Tasks;

    ...
        static async Task InvokeActorMethodWithoutRemotingAsync()
        {
            var actorType = "MyActor";
            var actorID = new ActorId("1");

            // Create Actor Proxy instance to invoke the methods defined in the interface
            var proxy = ActorProxy.Create(actorID, actorType);
            // Need to specify the method name and response type explicitly
            var response = await proxy.InvokeAsync<string>("SetMyDataAsync", new MyData()
            {
                PropertyA = "ValueA",
                PropertyB = "ValueB",
            });
            Console.WriteLine(response);

            var savedData = await proxy.InvokeAsync<MyData>("GetMyDataAsync");
            Console.WriteLine(savedData);
        }
    ...
}

运行Actor

为了验证及调试 actor 服务及客户端, 我们首先需要通过Dapr CLI运行actor服务.

  1. Run Dapr Runtime via Dapr cli

$ dapr run --app-id myapp --app-port 3000 dotnet MyActorService.dll

在通过Dapr运行时执行MyActorService之后,确保在端口3000上发现应用程序并成功建立actor连接.

INFO[0000] starting Dapr Runtime -- version  -- commit
 INFO[0000] log level set to: info
 INFO[0000] standalone mode configured
 INFO[0000] dapr id: myapp
 INFO[0000] loaded component statestore (state.redis)
 INFO[0000] application protocol: http. waiting on port 3000
 INFO[0000] application discovered on port 3000
 INFO[0000] application configuration loaded
 2019/08/27 14:42:06 redis: connecting to localhost:6379
 2019/08/27 14:42:06 redis: connected to localhost:6379 (localAddr: [::1]:53155, remAddr: [::1]:6379)
 INFO[0000] actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s
 INFO[0000] actors: starting connection attempt to placement service at localhost:50005
 INFO[0000] http server is running on port 3500
 INFO[0000] gRPC server is running on port 50001
 INFO[0000] dapr initialized. Status: Running. Init Elapsed 19.699438ms
 INFO[0000] actors: established connection to placement service at localhost:50005
 INFO[0000] actors: placement order received: lock
 INFO[0000] actors: placement order received: update
 INFO[0000] actors: placement tables updated
 INFO[0000] actors: placement order received: unlock
 ...

2.运行 MyActorClient

如果MyActorClient成功调用托管在MyActorService中的actor,它将在控制台输出.

如果指定不同的Dapr运行时http端口(默认端口:3500),则需要在运行客户端之前设置Dapr_http_port环境变量.

Success
PropertyA: ValueA, PropertyB: ValueB
用户评论