面向.NET開發人員的Dapr- actors 構建塊


原文地址:https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/actors 

The actor model originated in 1973. It was proposed by Carl Hewitt as a conceptual model of concurrent computation, a form of computing in which several computations are executed at the same time. Highly parallel computers weren't yet available at that time, but the more recent advancements of multi-core CPUs and distributed systems have made the actor model popular.

Actor 模型 起源於Carl Hewitt  在 1973 年提出的作為並發計算的概念模型,這種形式的計算會同時執行多個計算。 當時並沒有高度並行的計算機,但多核 Cpu 和分布式系統的最新進步使得Actor 模型 變得流行。

In the actor model, the actor is an independent unit of compute and state. Actors are completely isolated from each other and they will never share memory. Actors communicate with each other using messages. When an actor receives a message, it can change its internal state, and send messages to other (possibly new) actors.

在Actor 模型中,Actor 是一個計算和狀態獨立的單元。 Actors 完全彼此隔離,它們永遠不會共享內存。 Actors 使用消息相互通信。 當一個Actor 收到消息時,它可以更改其內部狀態,並將消息發送到其他 (可能是新的) Actors。

The reason why the actor model makes writing concurrent systems easier is that it provides a turn-based (or single-threaded) access model. Multiple actors can run at the same time, but each actor will process received messages one at a time. This means that you can be sure that at most one thread is active inside an actor at any time. That makes writing correct concurrent and parallel systems much easier.

Actor模型使得編寫並發系統變得更簡單的,它提供了基於 turn-based 的 (或單線程) 訪問模型。 多個Actors可以同時運行,但每個Actor 一次只處理一個接收的消息。 這意味着,在任何時候,都可以確保在Actors 中最多有一個線程處於活動狀態。 這使得編寫正確的並發系統和並行系統變得更加容易。

What it solves

它解決了什么問題

Actor model implementations are usually tied to a specific language or platform. With the Dapr actors building block however, you can leverage the actor model from any language or platform.

Actor 模型的實現通常綁定到特定語言或平台。 使用 Dapr Actor 構建塊可以從任何語言或平台 來使用 Actor 模型。

Dapr's implementation is based on the virtual actor pattern introduced by Project "Orleans". With the virtual actor pattern, you don't need to explicitly create actors. Actors are activated implicitly and placed on a node in the cluster the first time a message is sent to the actor. When not executing operations, actors are silently unloaded from memory. If a node fails, Dapr automatically moves activated actors to healthy nodes. Besides sending messages between actors, the Dapr actor model also support scheduling future work using timers and reminders.

Dapr 的實現基於 項目 "奧爾良" 中引入的虛擬Actor模式。 對於虛擬Actor模式,不需要顯式的創建Actor。 第一次將消息發送到Actor時,Actor將被隱式激活並放置在群集中的節點上。 當不執行操作時,Actor 會以靜默方式從內存中卸載。 如果某個節點出現故障,Dapr 會自動將激活的Actor 移到正常的節點。 除了在Actor之間發送消息以外,Dapr Actor模型還支持使用計時器和提醒調度將來的工作。

While the actor model can provide great benefits, it's important to carefully consider the actor design. For example, having many clients call the same actor will result in poor performance because the actor operations execute serially. Here are some criteria to check if a scenario is a good fit for Dapr actors:

雖然Actor模型 提供了很大的優勢,但必須仔細考慮Actor的設計。 例如,如果多個客戶端調用相同的Actor,則會導致性能不佳,因為Actor  操作會按順序執行。 下面的檢查清單是是否適用於 Dapr Actor的一些標准:

  • Your problem space involves concurrency. Without actors, you'd have to introduce explicit locking mechanisms in your code.
  • 問題空間涉及並發性。 如果沒有Actor,則需要在代碼中引入顯式鎖定機制。
  • Your problem space can be partitioned into small, independent, and isolated units of state and logic.
  • 可以將問題空間分區為小、獨立和隔離的狀態和邏輯單元。
  • You don't need low-latency reads of the actor state. Low-latency reads cannot be guaranteed because actor operations execute serially.
  • 不需要低延遲的讀取Actor 狀態。  因為Actor 操作是按順序執行,不能保證低延遲讀取。
  • You don't need to query state across a set of actors. Querying across actors is inefficient because each actor's state needs to be read individually and can introduce unpredictable latencies.
  • 不需要在一組Actor 之間查詢狀態。 跨Actor 的查詢效率低下,因為每個Actor 的狀態都需要單獨讀取,並且可能會導致不可預測的延遲。

One design pattern that fits these criteria quite well is the orchestration-based saga or process manager design pattern. A saga manages a sequence of steps that must be taken to reach some outcome. The saga (or process manager) maintains the current state of the sequence and triggers the next step. If a step fails, the saga can execute compensating actions. Actors make it easy to deal with concurrency in the saga and to keep track of the current state. The eShopOnDapr reference application uses the saga pattern and Dapr actors to implement the Ordering process.

滿足這些條件的一種設計模式非常好,就是 基於業務流程的 saga流程管理器 設計模式。 Saga 管理必須執行的一系列步驟才能達到某些結果。 Saga (或進程管理器) 維護序列的當前狀態,並觸發下一步。 如果一個步驟失敗,saga 可以執行補償操作。 利用Actor,可以輕松處理 saga 中的並發,並跟蹤當前狀態。 EShopOnDapr 參考應用程序使用 saga 模式和 Dapr Actor來實現排序過程。

How it works

工作原理

The Dapr sidecar provides the HTTP/gRPC API to invoke actors. This is the base URL of the HTTP API:

Dapr 挎斗提供了用於調用Actor 的 HTTP/gRPC API。 這是 HTTP API 的基 URL:

http://localhost:<daprPort>/v1.0/actors/<actorType>/<actorId>/

  • <daprPort>: the HTTP port that Dapr listens on.
  • <daprPort>: Dapr 偵聽的 HTTP 端口。
  • <actorType>: the actor type.
  • <actorType>:執行組件類型。
  • <actorId>: the ID of the specific actor to call.
  • <actorId>:要調用的特定Actor的 ID。

The sidecar manages how, when and where each actor runs, and also routes messages between actors. When an actor hasn't been used for a period of time, the runtime deactivates the actor and removes it from memory. Any state managed by the actor is persisted and will be available when the actor re-activates. Dapr uses an idle timer to determine when an actor can be deactivated. When an operation is called on the actor (either by a method call or a reminder firing), the idle timer is reset and the actor instance will remain activated.

挎斗管理每個Actor 的運行時間和位置,以及在Actor之間路由消息的方式。 如果一段時間未使用某個Actor,則運行時將停用該執行組件,並將其從內存中刪除。 Actor 所管理的任何狀態都將被保留,並在Actor 重新激活時可用。 Dapr 使用空閑計時器來確定何時可以停用Actor。 當在Actor 上調用操作時 (通過方法調用或提醒觸發) ,會重置空閑計時器,並保持激活執行組件實例。

The sidecar API is only one part of the equation. The service itself also needs to implement an API specification, because the actual code that you write for the actor will run inside the service itself. Figure 11-1 shows the various API calls between the service and its sidecar:

挎斗 API 只是公式的一部分。 服務本身還需要實現 API 規范,因為你為Actor 編寫的實際代碼將在服務本身內運行。 圖11-1 顯示了服務和它的挎斗之間的各種 API 調用:

執行組件服務和 Dapr 挎斗之間的 API 調用關系圖。

Figure 11-1. API calls between actor service and Dapr sidecar.

圖 11-1。 actor服務和 Dapr 挎斗之間的 API 調用。

To provide scalability and reliability, actors are partitioned across all the instances of the actor service. The Dapr placement service is responsible for keeping track of the partitioning information. When a new instance of an actor service is started, the sidecar registers the supported actor types with the placement service. The placement service calculates the updated partitioning information for the given actor type and broadcasts it to all instances. Figure 11-2 shows what happens when a service is scaled out to a second replica:

為了提供可伸縮性和可靠性,將在Actor服務的所有實例中對actor進行分區。 Dapr placement  服務負責跟蹤分區信息。 啟動Actor 服務的新實例時,挎斗會將支持的Actor 類型注冊到placement 服務。 placement 服務計算給定Actor 類型的更新分區信息,並將其廣播給所有實例。 圖11-2 顯示了將服務擴展到第二個副本時發生的情況:

執行組件放置服務的關系圖。

igure 11-2. Actor placement service.

  1. On startup, the sidecar makes a call to the actor service to get the registered actor types as well as actor configuration settings.
  2. The sidecar sends the list of registered actor types to the placement service.
  3. The placement service broadcasts the updated partitioning information to all actor service instances. Each instance will keep a cached copy of the partitioning information and use it to invoke actors.
  1. 啟動時,挎斗調用actor服務以獲取注冊的Actor類型和Actor的配置設置。
  2. 挎斗將注冊的Actor類型的列表發送到placement 服務。
  3. placement 服務會將更新的分區信息廣播到所有Actor服務實例。 每個實例都將保留分區信息的緩存副本,並使用它來調用Actor。

Important

Because actors are randomly distributed across service instances, it should be expected that an actor operation always requires a call to a different node in the network.

由於actor是在各服務實例間隨機分發的,因此Actor 始終需要調用網絡中的其他節點。

The next figure shows an ordering service instance running in Pod 1 call the method of an instance with ID . Because the actor with ID is placed in a different instance, this results in a call to a different node in the cluster:shipOrderActor33

下圖顯示了在 Pod 1 中運行的ordering 服務實例調用 ship OrderActor ID 為的實例的方法 3 。 由於 ID 的actor 3 放在不同的實例中,因此將導致調用群集中的不同節點:

調用執行組件方法的關系圖。

Figure 11-3. Calling an actor method.

圖 11-3。 調用執Actor方法。

  1. The service calls the actor API on the sidecar. The JSON payload in the request body contains the data to send to the actor.
  2. The sidecar uses the locally cached partitioning information from the placement service to determine which actor service instance (partition) is responsible for hosting the actor with ID . In this example, it's the service instance in pod 2. The call is forwarded to the appropriate sidecar.3
  3. The sidecar instance in pod 2 calls the service instance to invoke the actor. The service instance activates the actor (if it hasn't already) and executes the actor method.
  1. 服務在挎斗上調用Actor API。 請求正文中的 JSON 有效負載包含要發送到Actor 的數據。
  2. 挎斗使用placement 服務中的本地緩存的分區信息來確定哪個執行組件服務實例 (分區) 負責托管 ID 為的Actor 。 在此示例中,它是 pod 2 中的服務實例。 調用將轉發到相應的挎斗 3。
  3. Pod 2 中的挎斗實例調用服務實例以調用Actor。 如果Actor尚未 並執行Actor方法,則該服務實例將激活該執行組件。
Turn-based access model

Turn-based 的訪問模型

The turn-based access model ensures that at any time there's at most one thread active inside an actor instance. To understand why this is useful, consider the following example of a method that increments a counter value:

turn-based 的訪問模型可確保在一個Actor實例內最多只有一個線程處於活動狀態。 若要了解此操作的原因,請考慮以下用於遞增計數器值的方法示例:

public int Increment()
{
     var currentValue = GetValue();
     var newValue = currentValue + 1;

    SaveValue(newValue);

    return newValue;
}

Let's assume that the current value returned by the method is . When two threads call the method at the same time, there's a risk of both of them calling the method before one of them calls . This results in both threads starting with the same initial value (). The threads then increment the value to and return it to the caller. The resulting value after the two calls is now instead of which it should be. This is a simple example to illustrate the kind of issues that can slip into your code when working with multiple threads, and is easy to solve. In real world applications however, concurrent and parallel scenarios can become very complex.GetValue1IncrementGetValueSaveValue1223

假設方法返回的當前值 GetValue1 。 當兩個線程同時調用方法時,它們會在調用方法 Increment GetValue 之前調用方法 SaveValue 。 這會導致兩個線程以相同初始值開始 (1) 。 然后,線程遞增值並將 2 其返回給調用方。 現在,兩次調用后的結果值是, 2 而不是它的值 3 。 這是一個簡單的示例,說明了在使用多個線程時可能會滑入代碼的問題種類,並且很容易解決。 但在實際應用程序中,並發和並行方案可能會變得非常復雜。

In traditional programming models, you can solve this problem by introducing locking mechanisms. For example:

在傳統編程模型中,可以通過引入鎖定機制來解決此問題。 例如:

public int Increment()
{
     int newValue;

    lock (_lockObject)
     {
         var currentValue = GetValue();
         newValue = currentValue + 1;

        SaveValue(newValue);
     }

    return newValue;
}

Unfortunately, using explicit locking mechanisms is error-prone. They can easily lead to deadlocks and can have serious impact on performance.

遺憾的是,使用顯式鎖定機制容易出錯。 它們很容易導致死鎖,並可能對性能產生嚴重影響。

Thanks to the turn-based access model, you don't need to worry about multiple threads with actors, making it much easier to write concurrent systems. The following actor example closely mirrors the code from the previous sample, but doesn't require any locking mechanisms to be correct:

由於使用的是Turn-based 的訪問模型,因此,您無需擔心使用actors 的多線程,使得編寫並發系統變得更加容易。 下面的執行組件示例將對上一個示例中的代碼進行密切鏡像,但不需要任何鎖定機制是正確的:

public async Task<int> IncrementAsync()
{
     var counterValue = await StateManager.TryGetStateAsync<int>("counter");

    var currentValue = counterValue.HasValue ? counterValue.Value : 0;
     var newValue = currentValue + 1;

    await StateManager.SetStateAsync("counter", newValue);

    return newValue;
}

Timers and reminders
計時器和提醒

Actors can use timers and reminders to schedule calls to themselves. Both concepts support the configuration of a due time. The difference lies in the lifetime of the callback registrations:

Actors 可以使用計時器和提醒來調度自身的調用。 這兩個概念都支持配置截止時間。 不同之處在於回調注冊的生存期:

  • Timers will only stay active as long as the the actor is activated. Timers will not reset the idle-timer, so they cannot keep an actor active on their own.
  • 只要激活Actor,計時器就會保持活動狀態。 計時器 不會 重置空閑計時器,因此它們不能使Actor 處於活動狀態。
  • Reminders outlive actor activations. If an actor is deactivated, a reminder will re-activate the actor. Reminders will reset the idle-timer.
  • 提醒長於Actor激活。 如果停用了某個Actor,則會重新激活該執行組件。 提醒 重置空閑計時器。

Timers are registered by making a call to the actor API. In the following example, a timer is registered with a due time of 0 and a period of 10 seconds.

計時器是通過調用Actor API 來注冊的。 在下面的示例中,在時間為0的情況下注冊計時器,時間為10秒。

Bash

curl -X POST http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name> \
  -H "Content-Type: application/json" \
  -d '{
        "dueTime": "0h0m0s0ms",
        "period": "0h0m10s0ms"
      }'

Because the due time is 0, the timer will fire immediately. After a timer callback has finished, the timer will wait 10 seconds before firing again.

由於截止時間為0,因此將立即觸發計時器。 計時器回調完成后,計時器將等待10秒,然后再次觸發。

Reminders are registered in a similar way. The following example shows a reminder registration with a due time of 5 minutes, and an empty period:

提醒注冊方式類似。 下面的示例演示了一個提醒注冊,該注冊的截止時間為5分鍾,空時間為空:

Bash

curl -X POST http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name> \
  -H "Content-Type: application/json" \
  -d '{
        "dueTime": "0h5m0s0ms",
        "period": ""
      }'

This reminder will fire in 5 minutes. Because the given period is empty, this will be a one-time reminder.

此提醒將在5分鍾后激發。 由於給定時間段為空,這將為一次性提醒。

Note

備注

Timers and reminders both respect the turn-based access model. When a timer or reminder fires, the callback will not be executed until any other method invocation or timer/reminder callback has finished.

計時器和提醒均遵循turn-based 的訪問模型。 當計時器或提醒觸發時,直到任何其他方法調用或計時器/提醒回調完成后才會執行回調。

State persistence
狀態持久性

Actor state is persisted using the Dapr state management building block. Because actors can execute multiple state operations in a single turn, the state store component must support multi-item transactions. At the time of writing, the following state stores support multi-item transactions:

使用 Dapr 狀態管理構建塊保存Actor 狀態。 由於執行組件可以一輪執行多個狀態操作,因此狀態存儲組件必須支持多項事務。 撰寫本文時,以下狀態存儲支持多項事務:

  • Azure Cosmos DB
  • MongoDB
  • MySQL
  • PostgreSQL
  • Redis
  • RethinkDB
  • SQL Server

To configure a state store component for use with actors, you need to append the following metadata to the state store configuration:

若要配置要與Actors 一起使用的狀態存儲組件,需要將以下元數據附加到狀態存儲配置:

YAML

- name: actorStateStore
  value: "true"

Here's a complete example for a Redis state store:

下面是 Redis 狀態存儲的完整示例:

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"

Use the Dapr .NET SDK

使用 Dapr .NET SDK

You can create an actor model implementation using only HTTP/gRPC calls. However, it's much more convenient to use the language specific Dapr SDKs. At the time of writing, the .NET, Java and Python SDKs all provide extensive support for working with actors.

你只能使用 HTTP/gRPC 調用來創建Actor 模型實現。 但是,更方便的方法是使用特定於語言的 Dapr Sdk。 撰寫本文時,.NET、Java 和 Python Sdk 都為使用Actor 提供了廣泛的支持。

To get started with the .NET Dapr actors SDK, you add a package reference to Dapr.Actors to your service project. The first step of creating an actual actor is to define an interface that derives from . Clients use the interface to invoke operations on the actor. Here's a simple example of an actor interface for keeping scores:IActor

若要開始使用 .NET Dapr actors SDK,你可以將包引用Dapr.Actors 添加到 你的服務項目。 創建實際Actor 的第一步是定義並繼承的接口 IActor 。 客戶端使用接口調用Actor 上的操作。 下面是一個簡單的Actor 接口示例,用於保持分數:

C#

public interface IScoreActor : IActor
{
    Task<int> IncrementScoreAsync();

    Task<int> GetScoreAsync();
}

Important

重要

The return type of an actor method must be or . Also, actor methods can have at most one argument. Both the return type and the arguments must be serializable.TaskTask<T>System.Text.Json

Actor 方法的返回類型必須為 TaskTask<T> 。 此外,Actor 方法最多只能有一個參數。 返回類型和參數都必須支持 System.Text.Json 序列化。

Next, implement the actor by deriving a class from . The class must also implement the interface:ScoreActorActorScoreActorIScoreActor

接下來,通過從派生類來實現參與者 ScoreActor ActorScoreActor類還必須實現 IScoreActor 接口:

C#

public class ScoreActor : Actor, IScoreActor
{
    public ScoreActor(ActorHost host) : base(host)
    {
    }

    // TODO Implement interface methods.
}

The constructor in the snippet above takes a argument of type . The class represents the host for an actor type within the actor runtime. You need to pass this argument to the constructor of the base class. Actors also support dependency injection. Any additional arguments that you add to the actor constructor are resolved using the .NET dependency injection container.hostActorHostActorHostActor

上面代碼段中的構造函數采用 host 類型的參數 ActorHostActorHost類表示Actor 運行時中的actor 類型的宿主。 需要將此參數傳遞給基類的構造函數 Actor 。 actor 還支持依賴項注入。 使用 .NET 依賴關系注入容器來解析添加到actor 構造函數的任何其他參數。

Let's now implement the method of the interface:IncrementScoreAsync

現在,讓我們實現 IncrementScoreAsync 接口的方法:

C#

public Task<int> IncrementScoreAsync()
{
    return StateManager.AddOrUpdateStateAsync(
        "score",
        1,
        (key, currentScore) => currentScore + 1
    );
}

In the snippet above, a single call to provides the full implementation for the method. The method takes three arguments:StateManager.AddOrUpdateStateAsyncIncrementScoreAsyncAddOrUpdateStateAsync

在上面的代碼片段中,對方法的一次調用 StateManager.AddOrUpdateStateAsync 提供了完整的 IncrementScoreAsync 方法實現。 AddOrUpdateStateAsync方法采用三個參數:

  1. The key of the state to update.
  2. The value to write if no score is stored in the state store yet.
  3. A to call if there already is a score stored in the state store. It takes the state key and current score, and returns the updated score to write back to the state store.Func
  1. 要更新的狀態的鍵。
  2. 如果尚未將評分存儲在狀態存儲中,則為要寫入的值。
  3. Func 狀態存儲中已有分數存儲時要調用的。 它將使用狀態鍵和當前評分,並返回更新后的分數以寫回到狀態存儲區。

The implementation reads the current score from the state store and returns it to the client:GetScoreAsync

GetScoreAsync實現讀取狀態存儲中的當前評分,並將其返回給客戶端:

C#

public async Task<int> GetScoreAsync()
{
    var scoreValue = await StateManager.TryGetStateAsync<int>("score");
    if (scoreValue.HasValue)
    {
        return scoreValue.Value;
    }

    return 0;
}

To host actors in an ASP.NET Core service, you must add a reference to the Dapr.Actors.AspNetCore package and make some changes to the class. In the following example, the method adds the actor endpoints by calling :StartupConfigureendpoints.MapActorsHandlers

若要在 ASP.NET Core 服務中承載Actor,你必須添加對包的引用 Dapr.Actors.AspNetCore 並對Startup 類進行一些更改 。 在下面的示例中, Configure 方法通過調用來添加執行組件終結點 endpoints.MapActorsHandlers

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    // Actors building block does not support HTTPS redirection.
    //app.UseHttpsRedirection();

    app.UseEndpoints(endpoints =>
    {
        // Add actor endpoints.
        endpoints.MapActorsHandlers();

        endpoints.MapControllers();
    });
}

The actors endpoints are necessary because the Dapr sidecar calls the application to host and interact with actor instances.

actors 終結點是必需的,因為 Dapr 挎斗調用應用程序來承載和與執行組件實例進行交互。

Important

重要

Make sure your class does not contain an call to redirect clients to the HTTPS endpoint. This will not work with actors. By design, a Dapr sidecar sends requests over unencrypted HTTP by default. The HTTPS middleware will block these requests when enabled.Startupapp.UseHttpsRedirection

請確保你的 Startup 類不包含 app.UseHttpsRedirection 將客戶端重定向到 HTTPS 終結點的調用。 這不適用於Actor。 按照設計,默認情況下,Dapr 挎斗通過未加密的 HTTP 發送請求。 啟用后,HTTPS 中間件會阻止這些請求。

The class is also the place to register the specific actor types. In the example below, registers the using :StartupConfigureServicesScoreActorservices.AddActors

Startup類也是用於注冊特定執行組件類型的位置。 在下面的示例中, ConfigureServices ScoreActor 使用注冊 services.AddActors

C#

public void ConfigureServices(IServiceCollection services)
{
    // ...

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

At this point, the ASP.NET Core service is ready to host the and accept incoming requests. Client applications use actor proxies to invoke operations on actors. The following example shows how a console client application invokes the operation on a instance:ScoreActorIncrementScoreAsyncScoreActor

此時,ASP.NET Core 服務已准備好承載 ScoreActor 和接受傳入的請求。 客戶端應用程序使用Actor代理來調用Actor上的操作。 下面的示例演示了控制台客戶端應用程序如何 IncrementScoreAsync 對實例調用操作 ScoreActor

C#

static async Task MainAsync(string[] args)
{
    var actorId = new ActorId("scoreActor1");

    var proxy = ActorProxy.Create<IScoreActor>(actorId, "ScoreActor");

    var score = await proxy.IncrementScoreAsync();

    Console.WriteLine($"Current score: {score}");
}

The above example uses the Dapr.Actors package to call the actor service. To invoke an operation on an actor, you need to be able to address it. You'll need two parts for this:

上面的示例使用 Dapr.Actors 包來調用Actor 服務。 若要在Actor 上調用操作,需要能夠解決該操作。 此操作需要兩個部分:

  1. The actor type uniquely identifies the actor implementation across the whole application. By default, the actor type is the name of the implementation class (without namespace). You can customize the actor type by adding an to the implementation class and setting its property.ActorAttributeTypeName
  2. The uniquely identifies an instance of an actor type. You can also use this class to generate a random actor id by calling .ActorIdActorId.CreateRandom
  1. actor type 在整個應用程序中唯一標識執行組件實現。 默認情況下,actor 類型是 (沒有命名空間) 的實現類的名稱。 可以通過將添加 ActorAttribute 到實現類並設置其屬性,自定義參與者類型 TypeName
  2. ActorId唯一標識actor 類型的實例。 還可以通過調用來使用此類生成隨機執行組件 id ActorId.CreateRandom

The example uses to create a proxy instance for the . The method takes two arguments: the identifying the specific actor and the actor type. It also has a generic type parameter to specify the actor interface that the actor type implements. As both the server and client applications need to use the actor interfaces, they're typically stored in a separate shared project.ActorProxy.CreateScoreActorCreateActorId

該示例使用 ActorProxy.CreateScoreActor 創建代理實例 。 Create方法采用兩個參數:標識特定Actor和actor ActorId 類型。 它還具有一個泛型類型參數,用於指定actor類型所實現的actor接口。 由於服務器和客戶端應用程序都需要使用actor 接口,它們通常存儲在單獨的共享項目中。

The final step in the example calls the method on the actor and outputs the result. Remember that the Dapr placement service distributes the actor instances across the Dapr sidecars. Therefore, expect an actor call to be a network call to another node.IncrementScoreAsync

該示例中的最后一個步驟調用 Actor上的方法IncrementScoreAsync 並輸出結果。 請記住,Dapr placement 服務跨 Dapr 分支分發actor 實例。 因此,需要將actor 調用作為對另一個節點的網絡調用。

Call actors from ASP.NET Core clients
從 ASP.NET Core 客戶端調用參與者

The console client example in the previous section uses the static method directly to get an actor proxy instance. If the client application is an ASP.NET Core application, you should use the interface to create actor proxies. The main benefit is that it allows you to manage configuration centrally in the method. The method takes a delegate that allows you to specify actor runtime options, such as the HTTP endpoint of the Dapr sidecar. The following example specifies custom to use for actor state persistence and message deserialization:ActorProxy.CreateIActorProxyFactoryConfigureServicesAddActorsJsonSerializerOptions

上一部分中的控制台客戶端示例直接使用靜態 ActorProxy.Create 方法獲取Actor 代理實例。 如果客戶端應用程序是 ASP.NET Core 應用程序,則應使用 IActorProxyFactory 接口創建Actor 代理。 主要優點是它允許您集中管理方法中的配置 ConfigureServices . AddActors方法采用一個委托,該委托允許指定actor 運行時選項,如 Dapr 挎斗的 HTTP 終結點。 下面的示例指定了用於actor  JsonSerializerOptions 狀態持久性和消息反序列化的自定義:

C#

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddActors(options =>
    {
        var jsonSerializerOptions = new JsonSerializerOptions()
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            PropertyNameCaseInsensitive = true
        };

        options.JsonSerializerOptions = jsonSerializerOptions;
        options.Actors.RegisterActor<ScoreActor>();
    });
}

The call to registers the for .NET dependency injection. This allows ASP.NET Core to inject an instance into your controller classes. The following example calls an actor method from an ASP.NET Core controller class:AddActorsIActorProxyFactoryIActorProxyFactory

AddActors 注冊 IActorProxyFactory .net 依賴項注入而調用的。 這允許 ASP.NET Core 將實例注入 IActorProxyFactory 控制器類。 下面的示例從 ASP.NET Core 控制器類調用執行組件方法:

C#

[ApiController]
[Route("[controller]")]
public class ScoreController : ControllerBase
{
    private readonly IActorProxyFactory _actorProxyFactory;

    public ScoreController(IActorProxyFactory actorProxyFactory)
    {
        _actorProxyFactory = actorProxyFactory;
    }

    [HttpPut("{scoreId}")]
    public Task<int> IncrementAsync(string scoreId)
    {
        var scoreActor = _actorProxyFactory.CreateActorProxy<IScoreActor>(
            new ActorId(scoreId),
            "ScoreActor");

        return scoreActor.IncrementScoreAsync();
    }
}

Actors can also call other actors directly. The base class exposes an class through the property. To create an actor proxy from within an actor, use the property of the base class. The following example shows an that invokes operations on two other actors:ActorIActorProxyFactoryProxyFactoryProxyFactoryActorOrderActor

Actors還可以直接調用其他Actors。 Actor基類 IActorProxyFactory 通過屬性公開類 ProxyFactory 。 若要從Actor 中創建執行組件代理,請使用 ProxyFactory 基類的屬性 Actor 。 下面的示例演示一個 OrderActor ,它對兩個其他Actor 調用操作:

C#Copy

public class OrderActor : Actor, IOrderActor
{
    public OrderActor(ActorHost host) : base(host)
    {
    }

    public async Task ProcessOrderAsync(Order order)
    {
        var stockActor = ProxyFactory.CreateActorProxy<IStockActor>(
            new ActorId(order.OrderNumber),
            "StockActor");

        await stockActor.ReserveStockAsync(order.OrderLines);

        var paymentActor = ProxyFactory.CreateActorProxy<IPaymentActor>(
            new ActorId(order.OrderNumber),
            "PaymentActor");

        await paymentActor.ProcessPaymentAsync(order.PaymentDetails);
    }
}

Note

備注

By default, Dapr actors aren't reentrant. This means that a Dapr actor cannot be called more than once in the same chain. For example, the call chain is not allowed. At the time of writing, there's a preview feature available to support reentrancy. However, there is no SDK support yet. For more details, see the official documentation.Actor A -> Actor B -> Actor A

默認情況下,Dapr actors 不可重入。 這意味着不能在同一鏈中多次調用 Dapr actor。 例如, Actor A -> Actor B -> Actor A 不允許使用的調用鏈。 撰寫本文時,有一個預覽功能可用於支持重入。 但是,尚無 SDK 支持。 有關更多詳細信息,請參閱 官方文檔

Call non-.NET actors

調用非.NET actors

So far, the examples used strongly-typed actor proxies based on .NET interfaces to illustrate actor invocations. This works great when both the actor host and client are .NET applications. However, if the actor host is not a .NET application, you don't have an actor interface to create a strongly-typed proxy. In these cases, you can use a weakly-typed proxy.

到目前為止,這些示例使用基於 .NET 接口的強類型Actor 代理來說明actor 調用。 當actor 主機和客戶端都是 .NET 應用程序時,這非常有效。 但是,如果actor 主機不是 .NET 應用程序,則沒有創建強類型代理的actor 接口。 在這些情況下,可以使用弱類型代理。

You create weakly-typed proxies in a similar way to strongly-typed proxies. Instead of relying on a .NET interface, you need to pass in the actor method name as a string.

創建弱類型代理的方式與強類型代理類似。 需要將actor 方法名稱作為字符串傳遞,而不是依賴於 .NET 接口。

C#

[HttpPut("{scoreId}")]
public Task<int> IncrementAsync(string scoreId)
{
    var scoreActor = _actorProxyFactory.CreateActorProxy(
        new ActorId(scoreId),
        "ScoreActor");

    return scoreActor("IncrementScoreAsync");
}
Timers and reminders
計時器和提醒

Use the method of the base class to schedule actor timers. In the following example, a exposes a method. Clients can call the method to start a timer that repeatedly writes a given text to the log output.RegisterTimerAsyncActorTimerActorStartTimerAsync

使用 RegisterTimerAsync 基類的 Actor 方法計划actor 計時器。 在下面的示例中, TimerActor 公開 StartTimerAsync 方法。 客戶端可以調用 方法來啟動一個計時器,該計時器將給定的文本重復寫入日志輸出。

C#

public class TimerActor : Actor, ITimerActor
{
    public TimerActor(ActorHost host) : base(host)
    {
    }

    public Task StartTimerAsync(string name, string text)
    {
        return RegisterTimerAsync(
            name,
            nameof(TimerCallback),
            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;
    }
}

The method calls to schedule the timer. takes five arguments:StartTimerAsyncRegisterTimerAsyncRegisterTimerAsync

StartTimerAsync方法調用 RegisterTimerAsync 來調度計時器。 RegisterTimerAsync 采用五個參數:

  1. The name of the timer.
  2. The name of the method to call when the timer fires.
  3. The state to pass to the callback method.
  4. The amount of time to wait before the callback method is first invoked.
  5. The time interval between callback method invocations. You can specify to disable periodic signaling.TimeSpan.FromMilliseconds(-1)
  1. 計時器的名稱。
  2. 觸發計時器時要調用的方法的名稱。
  3. 要傳遞給回調方法的狀態。
  4. 首次調用回調方法之前要等待的時間。
  5. 回調方法調用之間的時間間隔。 可以指定 以 TimeSpan.FromMilliseconds(-1) 禁用定期信號。

The method receives the user state in binary form. In the example, the callback decodes the state back to a before writing it to the log.TimerCallbackAsyncstring

TimerCallbackAsync方法以二進制形式接收用戶狀態。 在示例中,回調在將狀態寫入日志之前將狀態 string 解碼 。

Timers can be stopped by calling :UnregisterTimerAsync

可以通過調用 UnregisterTimerAsync 來停止計時器 :

C#

public class TimerActor : Actor, ITimerActor
{
    // ...

    public Task StopTimerAsync(string name)
    {
        return UnregisterTimerAsync(name);
    }
}

Remember that timers do not reset the actor idle timer. When no other calls are made on the actor, it may be deactivated and the timer will be stopped automatically. To schedule work that does reset the idle timer, use reminders which we'll look at next.

請記住,計時器不會重置Actor空閑計時器。 當actor 上未進行其他調用時,可能會停用該Actor,並且計時器將自動停止。 若要計划重置空閑計時器的工作,請使用我們接下來將查看的提醒。

To use reminders in an actor, your actor class must implement the interface:IRemindable

若要在Actor 中使用提醒,Actor 類必須實現 IRemindable 接口:

C#

public interface IRemindable
{
    Task ReceiveReminderAsync(
        string reminderName, byte[] state,
        TimeSpan dueTime, TimeSpan period);
}

The method is called when a reminder is fired. It takes 4 arguments:ReceiveReminderAsync

觸發提醒時調用ReceiveReminderAsync 方法。 它采用 4 個參數:

  1. The name of the reminder.
  2. The user state provided during registration.
  3. The invocation due time provided during registration.
  4. The invocation period provided during registration.
  1. 提醒的名稱。
  2. 注冊期間提供的用戶狀態。
  3. 注冊期間提供的調用到期時間。
  4. 注冊期間提供的調用周期。

To register a reminder, use the method of the actor base class. The following example sets a reminder to fire a single time with a due time of three minutes.RegisterReminderAsync

若要注冊提醒,請使用 Actor基類的 方法RegisterReminderAsync 。 以下示例設置一個提醒,以在到期時間為 3 分鍾時發送一次。

C#

public class ReminderActor : Actor, IReminderActor, IRemindable
{
    public ReminderActor(ActorHost host) : base(host)
    {
    }

    public Task SetReminderAsync(string text)
    {
        return RegisterReminderAsync(
            "DoNotForget",
            Encoding.UTF8.GetBytes(text),
            TimeSpan.FromSeconds(3),
            TimeSpan.FromMilliseconds(-1));
    }

    public Task ReceiveReminderAsync(
        string reminderName, byte[] state,
        TimeSpan dueTime, TimeSpan period)
    {
        if (reminderName == "DoNotForget")
        {
            var text = Encoding.UTF8.GetString(state);

            Logger.LogInformation($"Don't forget: {text}");
        }

        return Task.CompletedTask;
    }
}

The method is similar to but you don't have to specify a callback method explicitly. As the above example shows, you implement to handle fired reminders.RegisterReminderAsyncRegisterTimerAsyncIRemindable.ReceiveReminderAsync

RegisterReminderAsync方法類似於 RegisterTimerAsync ,但不必顯式指定回調方法。 如上面的示例所示,實現 IRemindable.ReceiveReminderAsync 以處理觸發的提醒。

Reminders both reset the idle timer and are persistent. Even if your actor is deactivated, it will be reactivated at the moment a reminder fires. To stop a reminder from firing, call .UnregisterReminderAsync

提醒同時重置空閑計時器和持久性。 即使Actor 已停用,也會在觸發提醒時重新激活。 若要停止觸發提醒,請調用 UnregisterReminderAsync

Sample application: Dapr Traffic Control

示例應用程序:Dapr 交通控制

The default version of Dapr Traffic Control does not use the actor model. However, it does contain an alternative actor-based implementation of the TrafficControl service that you can enable. To make use of actors in the TrafficControl service, open up the file and uncomment the statement at the top of the file:src/TrafficControlService/Controllers/TrafficController.csUSE_ACTORMODEL

Dapr 交通控制的默認版本不使用Actor 模型。 但是,它確實包含可以啟用的 TrafficControl 服務的基於Actor 的替代實現。 若要使用 TrafficControl 服務中的Actor ,請打開 文件並取消注釋文件 src/TrafficControlService/Controllers/TrafficController.cs 頂部的 USE_ACTORMODEL 語句:

C#

#define USE_ACTORMODEL

When the actor model is enabled, the application uses actors to represent vehicles. The operations that can be invoked on the vehicle actors are defined in an interface:IVehicleActor

啟用Actor模型后,應用程序將使用Actor 來表示車輛。 可在車輛Actor 上調用的操作在接口中 IVehicleActor 定義:

C#

public interface IVehicleActor : IActor
{
    Task RegisterEntryAsync(VehicleRegistered msg);
    Task RegisterExitAsync(VehicleRegistered msg);
}

The (simulated) entry cameras call the method when a new vehicle is first detected in the lane. The only responsibility of this method is storing the entry timestamp in the actor state:RegisterEntryAsync

首次在 (檢測到) 車輛時,模擬入口相機會 調用 RegisterEntryAsync 方法。 此方法的唯一職責是在actor 狀態中存儲條目時間戳:

C#

var vehicleState = new VehicleState
{
    LicenseNumber = msg.LicenseNumber,
    EntryTimestamp = msg.Timestamp
};
await StateManager.SetStateAsync("VehicleState", vehicleState);

When the vehicle reaches the end of the speed camera zone, the exit camera calls the method. The method first gets the current states and updates it to include the exit timestamp:RegisterExitAsyncRegisterExitAsync

當車輛到達速度相機區域末尾時,退出攝像頭調用 RegisterExitAsync 方法。 RegisterExitAsync方法首先獲取當前狀態並更新它以包括退出時間戳:

C#

var vehicleState = await StateManager.GetStateAsync<VehicleState>("VehicleState");
vehicleState.ExitTimestamp = msg.Timestamp;

Note

備注

The code above currently assumes that a instance has already been saved by the method. The code could be improved by first checking to make sure the state exists. Thanks to the turn-based access model, no explicit locks are required in the code.VehicleStateRegisterEntryAsync

上面的代碼當前假定 實例 VehicleState 已由 方法 RegisterEntryAsync 保存。 可以通過首先檢查 以確保狀態存在來改進代碼。 得益於turn-based 的訪問模型,代碼中不需要顯式鎖。

After the state is updated, the method checks if the vehicle was driving too fast. If it was, the actor publishes a message to the pub/sub topic:RegisterExitAsynccollectfine

狀態更新后, RegisterExitAsync 方法將檢查車輛是否駕駛速度過快。 如果是,則Actor 將消息發布到 pub/sub 主題collectfine

C#

int violation = _speedingViolationCalculator.DetermineSpeedingViolationInKmh(
    vehicleState.EntryTimestamp, vehicleState.ExitTimestamp);

if (violation > 0)
{
    var speedingViolation = new SpeedingViolation
    {
        VehicleId = msg.LicenseNumber,
        RoadId = _roadId,
        ViolationInKmh = violation,
        Timestamp = msg.Timestamp
    };

    await _daprClient.PublishEventAsync("pubsub", "collectfine", speedingViolation);
}

The code above uses two external dependencies. The encapsulates the business logic for determining whether or not a vehicle has driven too fast. The allows the actor to publish messages using the Dapr pub/sub building block._speedingViolationCalculator_daprClient

上面的代碼使用兩個外部依賴項。 封裝用於確定車輛是否駕駛速度過快 _speedingViolationCalculator 的業務邏輯。 允許 actor 使用 Dapr pub/sub 構建基塊發布消息。

Both dependencies are registered in the class and injected into the actor using constructor dependency injection:Startup

這兩個依賴項在 類 Startup 中注冊,並且使用構造函數依賴項注入注入到Actor中:

C#

private readonly DaprClient _daprClient;
private readonly ISpeedingViolationCalculator _speedingViolationCalculator;
private readonly string _roadId;

public VehicleActor(
    ActorHost host, DaprClient daprClient,
    ISpeedingViolationCalculator speedingViolationCalculator)
    : base(host)
{
    _daprClient = daprClient;
    _speedingViolationCalculator = speedingViolationCalculator;
    _roadId = _speedingViolationCalculator.GetRoadId();
}

The actor based implementation no longer uses the Dapr state management building block directly. Instead, the state is automatically persisted after each operation is executed.

基於Actor 的實現不再直接使用 Dapr 狀態管理構建基塊。 而是在執行每個操作后自動保留狀態。

Summary

總結

The Dapr actors building block makes it easier to write correct concurrent systems. Actors are small units of state and logic. They use a turn-based access model which saves you from having to use locking mechanisms to write thread-safe code. Actors are created implicitly and are silently unloaded from memory when no operations are performed. Any state stored in the actor is automatically persisted and loaded when the actor is reactivated. Actor model implementations are typically created for a specific language or platform. With the Dapr actors building block however, you can leverage the actor model from any language or platform.

Dapr actors 構建基塊可以更輕松地編寫正確的並發系統。 actors 是狀態和邏輯的小單元。 它們使用基於輪次的訪問模型,無需使用鎖定機制編寫線程安全代碼。 actors 是隱式創建的,在未執行任何操作時以無提示方式從內存中卸載。 重新激活actors 時,自動持久保存並加載actors 中存儲的任何狀態。 actors 模型實現通常是為特定語言或平台創建的。 但是,借助 Dapr 執行組件構建基塊,可以從任何語言或平台利用執行actors 模型。

Actors support timers and reminders to schedule future work. Timers do not reset the idle timer and will allow the actor to be deactivated when no other operations are performed. Reminders do reset the idle timer and are also persisted automatically. Both timers and reminders respect the turn-based access model, making sure that no other operations can execute while the timer/reminder events are handled.

Actor 支持計時器和提醒來調度將來的工作。 計時器不會重置空閑計時器,並且允許Actor 在未執行其他操作時停用。 提醒會重置空閑計時器,並且也會自動保留。 計時器和提醒都遵守基於輪次的訪問模型,確保在處理計時器/提醒事件時無法執行任何其他操作。

Actor state is persisted using the Dapr state management building block. Any state store that supports multi-item transactions can be used to store actor state.

使用 Dapr 狀態管理構建基塊 持久保存執行組件狀態。 支持多項事務的任何狀態存儲都可用於存儲執行組件狀態。

References


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM