實體函式定義了讀取和更新小段狀態的操作,稱為 持久實體。 與編排器函式類似,實體函式使用一種特殊的觸發類型,稱為 實體觸發器。 與編排器函式不同,實體函式明確管理實體狀態,而非透過控制流程來表示狀態。
實體可藉由將工作分散到多個實體來協助您擴展應用程式,每個實體都只保有適量的狀態。
實體定義了讀取和更新小狀態片段的操作,稱為 持久實體。 與編排器不同,實體是明確管理狀態,而非透過控制流程來表示狀態。 實體可藉由將工作分散到多個實體來協助您擴展應用程式,每個實體都只保有適量的狀態。
支持持久實體
實體功能及相關功能可在 Durable Functions 2.00 及之後版本中使用。
| 程式設計語言 |
支持持久實體 |
| .NET 隔離 |
✅ |
| .NET 內含式模型 |
✅ |
| JAVA |
✅ |
| Python |
✅ |
| JavaScript |
✅ |
| PowerShell |
❌ |
| Durable Task SDK |
支持持久實體 |
| .NET 隔離 |
✅ |
| .NET 內含式模型 |
✅ |
| JAVA |
✅ |
| Python |
✅ |
| JavaScript |
✅ |
| Go(Beta) |
✅ |
| PowerShell |
❌ |
耐用實體的一般概念
實體元件就像小型服務,透過訊息傳遞進行溝通。 每個實體都有獨特的身份,必要時還有內部狀態。 實體會在提示時執行操作。 操作可能會更新狀態、呼叫外部服務,或等待回應。 實體會透過執行階段經由可靠佇列傳送的訊息,與其他實體、協調流程及用戶端通訊。
為避免衝突,單一實體會按順序一個接一個地執行操作。
備註
當你呼叫一個實體時,它會將資料載荷處理完成,並會安排一個新的執行,當有新輸入到達時啟動。 因此,你的實體執行日誌可能會在每次呼叫後顯示額外的一次執行。 這是預料中的。
實體識別碼
使用 實體 ID 來存取實體。 實體 ID 是一對字串,用來唯一識別實體實例。 它包含:
-
實體名稱,用以識別實體類型。 例如:
Counter 。 這個名稱與實作該實體的實體函式名稱相符。 這不區分大小寫。
-
實體鍵,能在所有同名實體中唯一識別該實體。 例如,一個全域唯一識別碼 (GUID)。
例如, Counter 實體函數可能用於線上遊戲中的計分。 每個遊戲實例都有獨特的實體 ID,例如 @Counter@Game1 和 @Counter@Game2。 要鎖定一個實體,請指定其實體 ID。
實體作業
要對實體呼叫操作,請指定:
- 目標實體的實體識別碼。
-
操作名稱,是指定要執行的操作的字串。 例如,實體
Counter 可以支援 add、 get或 reset 操作。
-
操作輸入,是操作的可選參數。 例如,該
add 操作以整數為輸入。
-
排程時間,是指定操作交付時間的可選參數。 例如,將手術安排在幾天後進行。
操作可以回傳結果值或錯誤結果,例如 JavaScript 錯誤或 .NET 例外。 呼叫協調程序會接收結果或錯誤。
操作可以回傳結果值或錯誤結果,例如 .NET 例外或 Python 例外。 呼叫者會收到結果或錯誤訊息。
實體作業也可以建立、讀取、更新和刪除實體的狀態。 執行時會始終在儲存中保存實體狀態。
定義實體
請使用兩種 API 之一來定義 .NET 中的實體:
基於函數的語法:在函式導向語法中,你可以將每個實體寫成函式,並在應用程式中執行派遣操作。 這種語法對於狀態簡單、操作少或動態操作集的實體(如應用程式框架)非常有效。 但維護起來很繁瑣,因為它在編譯時無法捕捉型別錯誤。
類基礎語法:在類別基礎語法中,.NET類別與方法建模實體與操作。 這種語法讓程式碼更易閱讀,也能以型別安全的方式呼叫操作。 它是在函式語法上加一層薄層,所以你可以在同一個應用程式裡混合兩種變體。
你使用的 API 取決於你的 C# 函式執行在哪裡。 建議使用 隔離的工作程序 ,但你也可以在宿主程序中執行。
孤立工作處理序函式型範例
下列範例示範在隔離式背景工作處理序中的函式型Counter實體。 它支持add、reset、、 getdelete和 。
[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
return dispatcher.DispatchAsync(operation =>
{
if (operation.State.GetState(typeof(int)) is null)
{
operation.State.SetState(0);
}
switch (operation.Name.ToLowerInvariant())
{
case "add":
int state = operation.State.GetState<int>();
state += operation.GetInput<int>();
operation.State.SetState(state);
return new(state);
case "reset":
operation.State.SetState(0);
break;
case "get":
return new(operation.State.GetState<int>());
case "delete":
operation.State.SetState(null);
break;
}
return default;
});
}
隔離工作者處理序類別式範例
以下範例展示了使用類別與方法實作該 Counter 實體的過程。
public class Counter : TaskEntity<int>
{
readonly ILogger logger;
public Counter(ILogger<Counter> logger)
{
this.logger = logger;
}
public void Add(int amount) => this.State += amount;
public void Reset() => this.State = 0;
public int Get() => this.State;
[Function(nameof(Counter))]
public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
return dispatcher.DispatchAsync(this);
}
}
行程內基於函數的範例
此範例展示了一個簡單 Counter 實體實作為持久函數。 它定義了三個運算——add、、resetget和——,這些運算使用整數態。
[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
switch (ctx.OperationName.ToLowerInvariant())
{
case "add":
ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
break;
case "reset":
ctx.SetState(0);
break;
case "get":
ctx.Return(ctx.GetState<int>());
break;
}
}
欲了解更多資訊,請參閱 函式式語法。
處理序內以類別為基礎的範例
這個範例展示了同一 Counter 實體,透過類別和方法實作。
[JsonObject(MemberSerialization.OptIn)]
public class Counter
{
[JsonProperty("value")]
public int CurrentValue { get; set; }
public void Add(int amount) => this.CurrentValue += amount;
public void Reset() => this.CurrentValue = 0;
public int Get() => this.CurrentValue;
[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<Counter>();
}
這個實體會將狀態儲存在 Counter 持有當前計數器值的物件中。 Durable Functions 透過使用 Json.NET 函式庫來序列化和反序列化這個物件。
欲了解更多資訊,請參閱 定義實體類別。
JavaScript 自 1.3.0 npm 套件的 durable-functions 版本起支援持久實體。 此範例展示了以 JavaScript 中作為持久函式實作的 Counter 實體。
計數器/function.json
{
"bindings": [
{
"name": "context",
"type": "entityTrigger",
"direction": "in"
}
],
"disabled": false
}
計數器/index.js
const df = require("durable-functions");
module.exports = df.entity(function(context) {
const currentValue = context.df.getState(() => 0);
switch (context.df.operationName) {
case "add": {
const amount = context.df.getInput();
context.df.setState(currentValue + amount);
break;
}
case "reset":
context.df.setState(0);
break;
case "get":
context.df.return(currentValue);
break;
}
});
下列程式碼顯示實作為以 Python 撰寫之持久函式的Counter實體。
import azure.functions as func
import azure.durable_functions as df
# Entity function called counter
@myApp.entity_trigger(context_name="context")
def Counter(context):
current_value = context.get_state(lambda: 0)
operation = context.operation_name
if operation == "add":
amount = context.get_input()
current_value += amount
elif operation == "reset":
current_value = 0
elif operation == "get":
context.set_result(current_value)
context.set_state(current_value)
從 1.9.0 版本開始,Durable Functions for Java 支援使用類別式語法來定義實體。 你可以擴充 AbstractTaskEntity<TState> 基底類別來定義你的實體。
以下範例展示了一個Counter實體,在 Java 中實作為持久函數。
實體類別:
import com.microsoft.durabletask.AbstractTaskEntity;
import com.microsoft.durabletask.TaskEntityOperation;
public class CounterEntity extends AbstractTaskEntity<Integer> {
public void add(int value) {
this.state += value;
}
public void subtract(int value) {
this.state -= value;
}
public int get() {
return this.state;
}
public void reset() {
this.state = 0;
}
@Override
protected Integer initializeState(TaskEntityOperation operation) {
return 0;
}
@Override
protected Class<Integer> getStateType() {
return Integer.class;
}
}
實體功能:
@FunctionName("Counter")
public String counterEntity(
@DurableEntityTrigger(name = "req") String req) {
return EntityRunner.loadAndRun(req, CounterEntity::new);
}
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
.NET 的 Durable Task SDK 支援使用類別式語法來定義實體。 實作 TaskEntity<TState> 基底類別來定義你的實體。
以下範例展示了使用 Durable Task SDK 實作的 Counter 實體:
using Microsoft.DurableTask.Entities;
public class Counter : TaskEntity<int>
{
public void Add(int amount) => this.State += amount;
public void Reset() => this.State = 0;
public int Get() => this.State;
}
要向員工註冊該實體:
builder.Services.AddDurableTaskWorker()
.AddTasks(registry =>
{
registry.AddEntity<Counter>();
})
.UseDurableTaskScheduler(connectionString);
要從協調器發出指令或呼叫特定的實體:
public class EntityOrchestration : TaskOrchestrator<string, int>
{
public override async Task<int> RunAsync(TaskOrchestrationContext context, string entityKey)
{
var entityId = new EntityInstanceId(nameof(Counter), entityKey);
// Signal the entity (fire-and-forget)
await context.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
// Call the entity and wait for response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, nameof(Counter.Get));
return currentValue;
}
}
JavaScript/TypeScript 的 Durable Task SDK 支援使用類別式語法來定義實體。 擴充 TaskEntity<TState> 基底類別來定義你的實體。
以下範例展示了使用 Durable Task SDK 實作的 Counter 實體:
import { TaskEntity } from "@microsoft/durabletask-js";
class CounterEntity extends TaskEntity {
add(amount) {
this.state.value += amount;
return this.state.value;
}
get() {
return this.state.value;
}
reset() {
this.state.value = 0;
}
initializeState() {
return { value: 0 };
}
}
要向員工註冊該實體:
worker.addNamedEntity("Counter", () => new CounterEntity());
Python 的 Durable Task SDK 支援使用函式式語法來定義實體。
以下範例展示了使用 Durable Task SDK 實作的 Counter 實體:
from durabletask import task
def counter_entity(ctx: task.EntityContext):
"""Counter entity that maintains an integer state."""
current_value = ctx.get_state(lambda: 0)
operation = ctx.operation_name
if operation == "add":
amount = ctx.get_input()
current_value += amount
elif operation == "reset":
current_value = 0
elif operation == "get":
ctx.set_result(current_value)
ctx.set_state(current_value)
要向員工註冊該實體:
with DurableTaskSchedulerWorker(
host_address=endpoint,
secure_channel=secure_channel,
taskhub=taskhub,
token_credential=credential
) as worker:
worker.add_entity(counter_entity)
worker.start()
PowerShell 對持久實體的支援尚未提供。 請持續關注未來更新。
Java 的 Durable Task SDK 支援使用類別式語法來定義實體。 你可以擴充 AbstractTaskEntity<TState> 基底類別來定義你的實體。
以下範例展示了使用 Durable Task SDK 實作的 Counter 實體:
import com.microsoft.durabletask.AbstractTaskEntity;
import com.microsoft.durabletask.TaskEntityOperation;
public class CounterEntity extends AbstractTaskEntity<Integer> {
public void add(int amount) {
this.state += amount;
}
public void reset() {
this.state = 0;
}
public int get() {
return this.state;
}
@Override
protected Integer initializeState(TaskEntityOperation operation) {
return 0;
}
@Override
protected Class<Integer> getStateType() {
return Integer.class;
}
}
要向員工註冊該實體:
DurableTaskGrpcWorker worker = new DurableTaskGrpcWorkerBuilder()
.addEntity("Counter", CounterEntity::new)
.build();
worker.start();
要從協調器發出指令或呼叫特定的實體:
return ctx -> {
EntityInstanceId counterId = new EntityInstanceId("Counter", "myCounter");
// Signal the entity (fire-and-forget)
ctx.signalEntity(counterId, "add", 5);
// Call the entity and wait for response
int currentValue = ctx.callEntity(counterId, "get", Integer.class).await();
ctx.complete(currentValue);
};
Go SDK 支援以函式為基礎的實體。 此計數器讀取現有狀態、處理操作,並保存更新:
package main
import (
"fmt"
"github.com/microsoft/durabletask-go/task"
)
func counter(ctx *task.EntityContext) (any, error) {
var value int
if ctx.HasState() {
if err := ctx.GetState(&value); err != nil {
return nil, err
}
}
switch ctx.Operation {
case "get":
return value, nil
case "add":
var amount int
if err := ctx.GetInput(&amount); err != nil {
return nil, err
}
value += amount
case "reset":
value = 0
case "delete":
ctx.DeleteState()
return nil, nil
default:
return nil, fmt.Errorf("unknown counter operation %q", ctx.Operation)
}
if err := ctx.SetState(value); err != nil {
return nil, err
}
return value, nil
}
將以下函式儲存在同一套件中的另一個檔案中。 在建立 worker 之前,請先呼叫 registerCounter worker 的登錄資料。 啟動 UseCounter 時,使用唯一的實體金鑰作為其字串輸入。
package main
import (
"fmt"
"github.com/microsoft/durabletask-go/api"
"github.com/microsoft/durabletask-go/task"
)
func useCounter(ctx *task.OrchestrationContext) (any, error) {
var key string
if err := ctx.GetInput(&key); err != nil {
return nil, err
}
if key == "" {
return nil, fmt.Errorf("entity key must not be empty")
}
id := api.NewEntityID("Counter", key)
if err := ctx.SignalEntity(id, "add", task.WithSignalEntityInput(1)); err != nil {
return nil, err
}
var value int
if err := ctx.CallEntity(id, "get").Await(&value); err != nil {
return nil, err
}
return value, nil
}
func registerCounter(registry *task.TaskRegistry) error {
if err := registry.AddEntityN("Counter", counter); err != nil {
return err
}
return registry.AddOrchestratorN("UseCounter", useCounter)
}
訊號不會回傳操作結果。 呼叫會等待結果。 單一實體實例的操作以序列方式執行。 關於註冊模式、工廠及鎖定傳輸,請參見 Go SDK 實體範例。
Warning
Go 和 Python 使用與 .NET 不同的多實體鎖序。 不要同時從 Go/Python 和 .NET 編排中取得重疊的鎖集。
存取實體
透過單向或雙向通訊來存取實體:
-
呼叫實體使用雙向 (來回) 通訊。 先向該實體發送操作訊息,然後等待回應訊息後繼續。 回應訊息會提供結果值或錯誤(例如JavaScript錯誤或.NET例外)。
- 對實體發出訊號時,會使用單向通訊 (發送後不理)。 發送行動訊息,但不要等待回應。 執行時保證送達,但發送端無法觀察結果值或錯誤。
從用戶端函數、編排器函數或實體函數存取實體。 並非所有情境都支援兩種溝通類型:
- 用戶端函式支援對實體發出訊號,以及讀取實體狀態。
- 協調器功能支援訊號傳遞及呼叫實體。
- 實體功能支援信令實體。
透過單向或雙向通訊來存取實體:
-
呼叫實體使用雙向 (來回) 通訊。 先向該實體發送操作訊息,然後等待回應訊息後繼續。 回應訊息會提供結果值或錯誤。
- 對實體發出訊號時,會使用單向通訊 (發送後不理)。 發送行動訊息,但不要等待回應。 執行時保證送達,但發送端無法觀察結果值或錯誤。
從用戶端或協調器存取實體。 並非所有情境都支援兩種溝通類型:
- 用戶端支援對實體發出訊號,以及讀取實體狀態。
- 編排器支援訊號與呼叫元件。
以下範例說明如何存取實體。
範例:客戶端向實體發出訊號
若要從一般的 Azure 函式(也稱為用戶端函式)存取實體,請使用 entity client binding。 以下範例展示了一個由佇列觸發的函式,透過此綁定來向實體發出訊號。
備註
為了簡單起見,下列範例會顯示用來存取實體的鬆散型別語法。 一般來說,透過 介面存取實體 ,因為介面提供更多型別檢查。
進行中:
進行中:
[FunctionName("AddFromQueue")]
public static Task Run(
[QueueTrigger("durable-function-trigger")] string input,
[DurableClient] IDurableEntityClient client)
{
// Entity operation input comes from the queue message content.
var entityId = new EntityId(nameof(Counter), "myCounter");
int amount = int.Parse(input);
return client.SignalEntityAsync(entityId, "Add", amount);
}
孤立工作程序:
[Function("AddFromQueue")]
public static Task Run(
[QueueTrigger("durable-function-trigger")] string input,
[DurableClient] DurableTaskClient client)
{
// Entity operation input comes from the queue message content.
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
int amount = int.Parse(input);
return client.Entities.SignalEntityAsync(entityId, "Add", amount);
}
const df = require("durable-functions");
module.exports = async function (context) {
const client = df.getClient(context);
const entityId = new df.EntityId("Counter", "myCounter");
await client.signalEntity(entityId, "add", 1);
};
import azure.functions as func
import azure.durable_functions as df
# An HTTP-Triggered Function with a Durable Functions Client to set a value on a durable entity
@myApp.route(route="entitysetvalue")
@myApp.durable_client_input(client_name="client")
async def http_set(req: func.HttpRequest, client):
entityId = df.EntityId("Counter", "myCounter")
await client.signal_entity(entityId, "add", 1)
return func.HttpResponse("Done", status_code=200)
@FunctionName("SignalCounter")
public HttpResponseMessage signalCounter(
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Void> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
String entityKey = request.getQueryParameters().getOrDefault("key", "myCounter");
String operation = request.getQueryParameters().getOrDefault("op", "add");
String valueStr = request.getQueryParameters().getOrDefault("value", "1");
EntityInstanceId entityId = new EntityInstanceId("Counter", entityKey);
if ("reset".equals(operation)) {
durableContext.signalEntity(entityId, operation);
} else {
int value = Integer.parseInt(valueStr);
durableContext.signalEntity(entityId, operation, value);
}
return request.createResponseBuilder(HttpStatus.ACCEPTED)
.body("Signal sent: " + operation + " on entity '" + entityKey + "'")
.build();
}
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
訊號一詞表示 API 語法呼叫是單向且非同步的。 客戶端函式無法知道實體何時處理該操作。 客戶端函式無法觀察結果值或例外。
要從用戶端存取實體,請使用 DurableTaskClient 來傳送信號或讀取實體狀態。
// Signal an entity
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
await client.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
// Signal an entity
const counterId = new EntityInstanceId("Counter", "myCounter");
await client.signalEntity(counterId, "add", 1);
# Signal an entity
entity_id = EntityId("Counter", "myCounter")
client.signal_entity(entity_id, "add", 1)
PowerShell 對持久實體的支援尚未提供。 請持續關注未來更新。
// Signal an entity
EntityInstanceId counterId = new EntityInstanceId("Counter", "myCounter");
client.getEntities().signalEntity(counterId, "add", 1);
訊號一詞表示 API 語法呼叫是單向且非同步的。 客戶端無法知道實體何時處理該操作。
範例:客戶端讀取實體狀態
從用戶端函式查詢實體狀態:
進行中:
[FunctionName("QueryCounter")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
[DurableClient] IDurableEntityClient client)
{
var entityId = new EntityId(nameof(Counter), "myCounter");
EntityStateResponse<JObject> stateResponse = await client.ReadEntityStateAsync<JObject>(entityId);
return req.CreateResponse(HttpStatusCode.OK, stateResponse.EntityState);
}
孤立工作程序:
[Function("QueryCounter")]
public static async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function)] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
if (entity is null)
{
return req.CreateResponse(HttpStatusCode.NotFound);
}
HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(entity);
return response;
}
const df = require("durable-functions");
module.exports = async function (context) {
const client = df.getClient(context);
const entityId = new df.EntityId("Counter", "myCounter");
const stateResponse = await client.readEntityState(entityId);
return stateResponse.entityState;
};
# An HTTP-Triggered Function with a Durable Functions Client to retrieve the state of a durable entity
@myApp.route(route="entityreadvalue")
@myApp.durable_client_input(client_name="client")
async def http_read(req: func.HttpRequest, client):
entityId = df.EntityId("Counter", "myCounter")
entity_state_result = await client.read_entity_state(entityId)
entity_state = "No state found"
if entity_state_result.entity_exists:
entity_state = str(entity_state_result.entity_state)
return func.HttpResponse(entity_state)
@FunctionName("GetCounter")
public HttpResponseMessage getCounter(
@HttpTrigger(name = "req", methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Void> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
String entityKey = request.getQueryParameters().getOrDefault("key", "myCounter");
EntityInstanceId entityId = new EntityInstanceId("Counter", entityKey);
EntityMetadata metadata = durableContext.getEntityMetadata(entityId, true);
if (metadata == null) {
return request.createResponseBuilder(HttpStatus.NOT_FOUND)
.body("Entity '" + entityKey + "' not found")
.build();
}
Integer state = metadata.readStateAs(Integer.class);
return request.createResponseBuilder(HttpStatus.OK)
.header("Content-Type", "application/json")
.body("{\"key\": \"" + entityKey + "\", \"value\": " + state + "}")
.build();
}
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
用戶端可以查詢實體的狀態:
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
if (entity != null)
{
Console.WriteLine($"Current value: {entity.State}");
}
const counterId = new EntityInstanceId("Counter", "myCounter");
const metadata = await client.getEntity(counterId);
if (metadata.exists) {
console.log(`Current value: ${metadata.state?.value}`);
}
entity_id = EntityId("Counter", "myCounter")
state = client.get_entity_state(entity_id)
if state is not None:
print(f"Current value: {state}")
PowerShell 對持久實體的支援尚未提供。 請持續關注未來更新。
EntityInstanceId counterId = new EntityInstanceId("Counter", "myCounter");
EntityMetadata entityMetadata = client.getEntities().getEntityMetadata(counterId, true);
if (entityMetadata != null) {
Integer state = entityMetadata.readStateAs(Integer.class);
System.out.printf("Current value: %d%n", state);
}
實體狀態查詢會前往持久追蹤儲存庫,回傳該實體最近持續存在的狀態。 這個狀態永遠是「已承諾」狀態,因此絕不會是執行操作中假設的暫時中間狀態。 但相較於實體的記憶體內狀態,此狀態可能已過時。 只有協調流程能夠讀取實體的記憶體內狀態,如下一節所述。
範例:協調流程對實體發出訊號並呼叫實體
編排器功能可透過 API 在 編排觸發綁定中存取實體。 以下範例程式碼展示了一個編排器函式呼叫並向一個 Counter 實體發送信號。
進行中:
[FunctionName("CounterOrchestration")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var entityId = new EntityId(nameof(Counter), "myCounter");
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.CallEntityAsync<int>(entityId, "Get");
if (currentValue < 10)
{
// One-way signal to the entity which updates the value - does not await a response
context.SignalEntity(entityId, "Add", 1);
}
}
孤立工作程序:
[Function("CounterOrchestration")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, "Get");
if (currentValue < 10)
{
// One-way signal to the entity which updates the value - does not await a response
await context.Entities.SignalEntityAsync(entityId, "Add", 1);
}
}
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
const entityId = new df.EntityId("Counter", "myCounter");
// Two-way call to the entity which returns a value - awaits the response
const currentValue = yield context.df.callEntity(entityId, "get");
return currentValue;
});
備註
JavaScript 不支援從編排器發送實體訊號。 請改用 callEntity。
@myApp.orchestration_trigger(context_name="context")
def orchestrator(context: df.DurableOrchestrationContext):
entityId = df.EntityId("Counter", "myCounter")
context.signal_entity(entityId, "add", 3)
state = yield context.call_entity(entityId, "get")
return state
@FunctionName("CounterOrchestration")
public String counterOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String entityKey = ctx.getInput(String.class);
EntityInstanceId entityId = new EntityInstanceId("Counter", entityKey);
// Signal entity operations (fire-and-forget)
ctx.signalEntity(entityId, "add", 10);
ctx.signalEntity(entityId, "add", 5);
ctx.signalEntity(entityId, "subtract", 3);
// Call entity and wait for result
int value = ctx.callEntity(entityId, "get", Integer.class).await();
return "Counter '" + entityKey + "' final value: " + value;
}
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
只有協調流程可以呼叫實體並取得回應,該回應可能是傳回值或例外狀況。 使用 客戶端綁定 的用戶端功能僅可對實體發出通知。
編排器可透過上下文的 Entities API 存取實體:
public class CounterOrchestration : TaskOrchestrator<string, int>
{
public override async Task<int> RunAsync(TaskOrchestrationContext context, string entityKey)
{
var entityId = new EntityInstanceId(nameof(Counter), entityKey);
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, nameof(Counter.Get));
if (currentValue < 10)
{
// One-way signal to the entity - does not await a response
await context.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
}
return currentValue;
}
}
const counterOrchestration = async function* (ctx, entityKey) {
const counterId = new EntityInstanceId("Counter", entityKey);
// Two-way call to the entity which returns a value - awaits the response
const currentValue = yield* ctx.entities.callEntity(counterId, "get");
if (currentValue < 10) {
// One-way signal to the entity - does not await a response
ctx.entities.signalEntity(counterId, "add", 1);
}
return currentValue;
};
def counter_orchestration(ctx: task.OrchestrationContext, entity_key: str):
entity_id = EntityId("Counter", entity_key)
# Two-way call to the entity which returns a value
current_value = yield ctx.call_entity(entity_id, "get")
if current_value < 10:
# One-way signal to the entity
ctx.signal_entity(entity_id, "add", 1)
return current_value
PowerShell 對持久實體的支援尚未提供。 請持續關注未來更新。
return ctx -> {
EntityInstanceId counterId = new EntityInstanceId("Counter", entityKey);
// Two-way call to the entity which returns a value - awaits the response
int currentValue = ctx.callEntity(counterId, "get", Integer.class).await();
if (currentValue < 10) {
// One-way signal to the entity - does not await a response
ctx.signalEntity(counterId, "add", 1);
}
ctx.complete(currentValue);
};
只有編排器能呼叫實體並取得回應,回應可能是回傳值或例外。 用戶端只能對實體發出信號。
備註
從編排器呼叫實體元件類似於呼叫活動。 主要差異在於實體是具有位址(實體 ID)且支援指定操作名稱的持久物件。 活動是無狀態的,沒有操作的概念。
範例:一個實體對另一個實體發出訊號
實體函式可以在執行作業時傳送訊號給其他實體,甚或傳送給本身。
例如,修改前一個 Counter 實體範例,當計數器達到 100 時,向監控實體發送「里程碑達標」訊號。
進行中:
case "add":
var currentValue = ctx.GetState<int>();
var amount = ctx.GetInput<int>();
if (currentValue < 100 && currentValue + amount >= 100)
{
ctx.SignalEntity(new EntityId("MonitorEntity", ""), "milestone-reached", ctx.EntityKey);
}
ctx.SetState(currentValue + amount);
break;
孤立工作程序:
case "add":
var currentValue = operation.State.GetState<int>();
var amount = operation.GetInput<int>();
if (currentValue < 100 && currentValue + amount >= 100)
{
operation.Context.SignalEntity(new EntityInstanceId("MonitorEntity", ""), "milestone-reached", operation.Context.EntityInstanceId);
}
operation.State.SetState(currentValue + amount);
break;
case "add":
const amount = context.df.getInput();
if (currentValue < 100 && currentValue + amount >= 100) {
const entityId = new df.EntityId("MonitorEntity", "");
context.df.signalEntity(entityId, "milestone-reached", context.df.instanceId);
}
context.df.setState(currentValue + amount);
break;
備註
Python 不支援實體對實體的訊號。 改用編排器來向實體發出信號。
以下範例展示了實體對實體的訊號傳遞。 當發生大額存款時,實體 Account 會通知實體 Audit 記錄該交易。
public class AccountEntity extends AbstractTaskEntity<Integer> {
private static final int LARGE_TRANSACTION_THRESHOLD = 500;
public void deposit(int amount) {
this.state += amount;
String key = this.context.getId().getKey();
// Entity signals another entity: notify the audit entity of large deposits
if (amount >= LARGE_TRANSACTION_THRESHOLD) {
EntityInstanceId auditEntityId = new EntityInstanceId("Audit", "ledger");
String message = String.format("Large deposit of %d into account '%s'", amount, key);
this.context.signalEntity(auditEntityId, "record", message);
}
}
// ... other operations ...
@Override
protected Integer initializeState(TaskEntityOperation operation) {
return 0;
}
@Override
protected Class<Integer> getStateType() {
return Integer.class;
}
}
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
實體協調
有時你需要協調多個實體之間的作業。 例如,在銀行應用程式中,實體可以代表個別銀行帳戶。 當你從一個帳戶轉帳到另一個帳戶時,你需要確保來源帳戶有足夠的資金。 你也需要同時更新兩個帳號,作為一個一致的操作。
範例:資金轉移
下列程式碼範例會使用協調器函式,在兩個帳戶實體之間轉移資金。 若要協調實體更新,請使用鎖定在流程協調中建立臨界區段。
備註
為了簡化起見,此範例重用 Counter 了先前定義的實體。 在實際應用中,定義一個更詳細 BankAccount 的實體會比較好。
使用該 LockAsync 方法來建立關鍵區段。
// This is a method called by an orchestrator function
public static async Task<bool> TransferFundsAsync(
string sourceId,
string destinationId,
int transferAmount,
IDurableOrchestrationContext context)
{
var sourceEntity = new EntityId(nameof(Counter), sourceId);
var destinationEntity = new EntityId(nameof(Counter), destinationId);
// Create a critical section to avoid race conditions.
// No operations can be performed on either the source or
// destination accounts until the locks are released.
using (await context.LockAsync(sourceEntity, destinationEntity))
{
ICounter sourceProxy =
context.CreateEntityProxy<ICounter>(sourceEntity);
ICounter destinationProxy =
context.CreateEntityProxy<ICounter>(destinationEntity);
int sourceBalance = await sourceProxy.Get();
if (sourceBalance >= transferAmount)
{
await sourceProxy.Add(-transferAmount);
await destinationProxy.Add(transferAmount);
// the transfer succeeded
return true;
}
else
{
// the transfer failed due to insufficient funds
return false;
}
}
}
在.NET中,LockAsync 返回 IDisposable。 將它處置後,重要區段便會結束。 用它搭配 using 方塊來代表關鍵區段。
在上述範例中,協調器函式會將資金從來源實體中轉移到目的地實體。 此 LockAsync 方法會鎖定來源與目的帳戶實體。 此鎖定確保在協調邏輯退出敘述結束的關鍵區段 using 前,其他用戶端無法查詢或修改任一帳號的狀態。 此行為可防止來源帳戶透支。
使用該 context.df.lock 方法來建立關鍵區段。
const df = require("durable-functions");
// This is an orchestrator function
module.exports = df.orchestrator(function* (context) {
const { sourceId, destinationId, transferAmount } = context.df.getInput();
const sourceEntity = new df.EntityId("Counter", sourceId);
const destinationEntity = new df.EntityId("Counter", destinationId);
// Create a critical section to avoid race conditions.
// No operations can be performed on either the source or
// destination accounts until the lock is released.
const lock = yield context.df.lock(sourceEntity, destinationEntity);
try {
const sourceBalance = yield context.df.callEntity(sourceEntity, "get");
if (sourceBalance >= transferAmount) {
yield context.df.callEntity(sourceEntity, "add", -transferAmount);
yield context.df.callEntity(destinationEntity, "add", transferAmount);
// the transfer succeeded
return true;
} else {
// the transfer failed due to insufficient funds
return false;
}
} finally {
lock.release();
}
});
在 JavaScript 中,context.df.lock 會回傳一個 DurableLock。 對其呼叫 release 以結束關鍵區段,或讓執行階段在協調流程結束時自動釋放鎖定。
在上述範例中,協調器函式會將資金從來源實體中轉移到目的地實體。 此 lock 方法會鎖定來源與目的帳戶實體。 此鎖定確保在協調邏輯解除鎖定前,沒有其他用戶端能查詢或修改任一帳號的狀態。 此行為可防止來源帳戶透支。
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
備註
當協調流程結束時,不論是正常結束還是發生錯誤,任何進行中的重要區段都會隱含結束,系統也會釋放所有鎖定。
重要區段行為
此 LockAsync 方法在配器中創造關鍵區段。 這些關鍵區段會防止其他協調程序對一組指定的實體進行重疊變更。 在內部運作中,LockAsync API 會先向實體發送「鎖定」操作,然後在收到每個實體的「鎖定已取得」回應後返回。 鎖定和解除鎖定是所有實體皆支援的內建作業。
其他客戶端在該實體被鎖定時無法執行操作。 此行為確保每次只有一個協調處理個體能夠鎖定實體。 如果呼叫端嘗試在實體遭協調流程鎖定時,對該實體調用作業,該作業就會放入待處理作業佇列。 在持有鎖定的協調流程釋放鎖定之前,不會處理任何待處理作業。
備註
這種行為與大多數程式語言中使用的同步原語略有不同,例如 lock C# 中的語句。 例如,在 C# 中,所有執行緒都必須使用該 lock 陳述式以確保正確的同步。 不過,實體不需要所有呼叫端都明確鎖定實體。 如果任何呼叫端鎖定實體,該實體上的所有其他作業都會遭到封鎖,並排入該鎖定之後。
實體的鎖具有持久性,因此即使執行中的程序重新啟動,鎖仍會保留下來。 系統會將鎖作為實體持久狀態的一部分來持續存在。
不同於交易,重大區段不會在發生錯誤時自動回復變更。 請改為撰寫錯誤處理,例如復原或重試,例如藉由攔截錯誤或例外狀況。 這是刻意設計的選擇。 自動回復編排過程的所有效果一般是困難的,甚至是不可能的,因為編排可能會執行一些操作並呼叫外部服務,這些操作和呼叫都可能無法撤銷。 此外,復原嘗試可能會失敗,而且需要進一步的錯誤處理。
此 context.df.lock 方法在配器中創造關鍵區段。 這些關鍵區段會防止其他協調程序對一組指定的實體進行重疊變更。 在內部,lock API 會向實體發送「鎖定」作業,並在收到每個實體的「鎖定已取得」回覆後傳回DurableLock。 鎖定和解除鎖定是所有實體皆支援的內建作業。
備註
context.df.lock API 需要 3.4.0 npm 套件的 durable-functions 或更新版本,以及 3.13.0 擴充功能的 Microsoft.Azure.WebJobs.Extensions.DurableTask 或更新版本。 在 v3.13.0 尚未在擴充套件中取得之前,請 手動安裝擴充套件。
請查看擴充套件版本何時發布。
若要結束臨界區段,請對傳回的DurableLock呼叫release ,或讓執行階段在協調流程結束時自動解除鎖定。 在臨界工作完成後立即解除鎖定,讓其他流程協調能更快取得該鎖定。 使用context.df.isLocked來檢查鎖定目前是否處於佔用狀態。
const lock = yield context.df.lock(sourceEntity, destinationEntity);
try {
yield context.df.callEntity(sourceEntity, "add", -amount);
yield context.df.callEntity(destinationEntity, "add", amount);
} finally {
lock.release();
}
其他客戶端在該實體被鎖定時無法執行操作。 此行為確保每次只有一個協調處理個體能夠鎖定實體。 如果呼叫端嘗試在實體遭協調流程鎖定時,對該實體調用作業,該作業就會放入待處理作業佇列。 在持有鎖定的協調流程釋放鎖定之前,不會處理任何待處理作業。
備註
這種行為與大多數程式語言中使用的同步原語略有不同。 實體並不要求所有呼叫者都必須明確取得鎖定。 如果任何呼叫端鎖定實體,該實體上的所有其他作業都會遭到封鎖,並排入該鎖定之後。
實體的鎖具有持久性,因此即使執行中的程序重新啟動,鎖仍會保留下來。 系統會將鎖作為實體持久狀態的一部分來持續存在。
不同於交易,重大區段不會在發生錯誤時自動回復變更。 相反地,請撰寫錯誤處理邏輯,例如回滾或重試,也可以透過捕獲錯誤來執行。 這是刻意設計的選擇。 自動回復編排過程的所有效果一般是困難的,甚至是不可能的,因為編排可能會執行一些操作並呼叫外部服務,這些操作和呼叫都可能無法撤銷。 此外,復原嘗試可能會失敗,而且需要進一步的錯誤處理。
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
關鍵區段規則
與大多數程式語言中的低階鎖定原語不同,臨界區 塊保證不會死結。 為防止死結,系統會強制執行以下限制:
- 您不能巢狀化臨界區段。
- 您不能在臨界區段中建立子協調流程。
- 你只能呼叫你在臨界區段中鎖定的實體。
- 你不能在關鍵區段用多個平行呼叫來呼叫同一個實體。
- 你只能在臨界區段中,向鎖集合之外的實體發送訊號。
如果你違反這些規則,執行時會丟出錯誤,例如 LockingRulesViolationException .NET 中。 錯誤訊息說明了違反了哪條規則。
如果你違反這些規則,執行時會丟出錯誤。 錯誤訊息說明了違反了哪條規則。
Durable Functions 不支援 Go。 要看 Go 範例,請在本文頂部選擇Durable Task SDKs。
持久實體與虛擬演員的比較
持久實體會從 角色模型中汲取許多想法。 如果你熟悉演員,可能會在這篇文章中認出幾個概念。 長期實體類似虛擬演員,在 Orleans 專案中也稱為顆粒。 例如:
- 您可以使用實體識別碼來定址持久實體。
- 持久實體作業會序列化執行,以防止競爭條件。
- 呼叫或向一個實體發送信號會隱含性地創建該實體。
- 當實體未執行作業時,執行階段會將其從記憶體卸載。
重要差異包括:
- 耐用實體優先考量耐用性而非延遲,因此可能無法符合嚴格延遲需求的應用。
- 持久實體不會讓訊息逾時。 Orleans 會在可設定的期間後讓訊息逾時 (預設為 30 秒)。
- 實體會可靠且有序地傳遞訊息。 Orleans 支援資料流訊息的可靠、依序傳遞,但不保證所有 Grain 之間訊息都具備此特性。
- 協調流程是唯一可搭配實體使用要求-回應模式的地方。 在實體內部,使用單向訊息傳遞(信號),就像原本的 Actor 模型一樣。
- 持久實體不會發生死結。 Orleans 可能會死結,而且死結會持續到訊息逾時為止。
- 持久實體可搭配持久協調流程運作,並支援分散式鎖定機制。
下一步