实体函数Entity functions

实体函数定义读取和更新较小状态片段(称为“持久实体”)的操作。 Entity functions define operations for reading and updating small pieces of state, known as durable entities. 与业务流程协调程序函数一样,实体函数是具有特殊触发器类型“实体触发器”的函数。 Like orchestrator functions, entity functions are functions with a special trigger type, the entity trigger. 与业务流程协调程序函数不同,实体函数会显式管理实体状态,而不是通过控制流隐式表示状态。Unlike orchestrator functions, entity functions manage the state of an entity explicitly, rather than implicitly representing state via control flow. 实体提供了一种横向扩展应用程序的方式,即,将工作分散到多个实体,而每个实体具有适度大小的状态。Entities provide a means for scaling out applications by distributing the work across many entities, each with a modestly sized state.

备注

实体函数和相关功能仅在 Durable Functions 2.0 及更高版本中可用。Entity functions and related functionality is only available in Durable Functions 2.0 and above.

一般概念General concepts

实体的行为有点类似于通过消息进行通信的微型服务。Entities behave a bit like tiny services that communicate via messages. 每个实体具有唯一的标识和内部状态(如果存在)。Each entity has a unique identity and an internal state (if it exists). 与服务或对象一样,实体会根据提示执行操作。Like services or objects, entities perform operations when prompted to do so. 执行的操作可能会更新实体的内部状态。When an operation executes, it might update the internal state of the entity. 它还可能会调用外部服务并等待响应。It might also call external services and wait for a response. 实体使用通过可靠队列隐式发送的消息来与其他实体、业务流程和客户端通信。Entities communicate with other entities, orchestrations, and clients by using messages that are implicitly sent via reliable queues.

为了防止冲突,系统会保证针对单个实体的所有操作按顺序执行,即,一个接一个地执行。To prevent conflicts, all operations on a single entity are guaranteed to execute serially, that is, one after another.

实体 IDEntity ID

可通过唯一标识符(实体 ID)访问实体。 Entities are accessed via a unique identifier, the entity ID. 实体 ID 只是用于唯一标识实体实例的一对字符串。An entity ID is simply a pair of strings that uniquely identifies an entity instance. 它包括:It consists of an:

  • 实体名称,用于标识实体类型的名称 。Entity name, which is a name that identifies the type of the entity. 例如“Counter”。An example is "Counter." 此名称必须与实现该实体的实体函数的名称相匹配。This name must match the name of the entity function that implements the entity. 此名称不区分大小写。It isn't sensitive to case.
  • 实体键,用于在所有其他同名实体中唯一标识该实体的字符串 。Entity key, which is a string that uniquely identifies the entity among all other entities of the same name. 例如一个 GUID。An example is a GUID.

例如,可以使用 Counter 实体函数来保留在线游戏中的积分。For example, a Counter entity function might be used for keeping score in an online game. 游戏的每个实例都具有唯一的实体 ID,例如 @Counter@Game1@Counter@Game2Each instance of the game has a unique entity ID, such as @Counter@Game1 and @Counter@Game2. 针对特定实体的所有操作需要指定实体 ID 作为参数。All operations that target a particular entity require specifying an entity ID as a parameter.

实体操作Entity operations

若要对实体调用操作,需指定:To invoke an operation on an entity, specify the:

  • 目标实体的实体 ID 。Entity ID of the target entity.
  • 操作名称,用于指定要执行的操作的字符串。 Operation name, which is a string that specifies the operation to perform. 例如,Counter 实体可以支持 addgetreset 操作。For example, the Counter entity could support add, get, or reset operations.
  • 操作输入,操作的可选输入参数。 Operation input, which is an optional input parameter for the operation. 例如,add 操作可以采用整数数量作为输入。For example, the add operation can take an integer amount as the input.
  • 计划时间,这是用于指定操作交付时间的可选参数。Scheduled time, which is an optional parameter for specifying the delivery time of the operation. 例如,可以可靠地计划一项操作在将来的几天运行。For example, an operation can be reliably scheduled to run several days in the future.

操作可以返回结果值或错误结果,例如 JavaScript 错误或 .NET 异常。Operations can return a result value or an error result, such as a JavaScript error or a .NET exception. 调用操作的业务流程可以观察到此结果或错误。This result or error can be observed by orchestrations that called the operation.

实体操作还可以创建、读取、更新和删除实体的状态。An entity operation can also create, read, update, and delete the state of the entity. 实体的状态始终持久保存在存储中。The state of the entity is always durably persisted in storage.

定义实体Define entities

目前,用于定义实体的两个不同的 API 为:Currently, the two distinct APIs for defining entities are:

基于函数的语法,其中,实体以函数的形式表示,操作由应用程序显式调度 。Function-based syntax, where entities are represented as functions and operations are explicitly dispatched by the application. 此语法适合用于具有简单状态、少量操作或一组动态操作的实体,例如在应用程序框架中。This syntax works well for entities with simple state, few operations, or a dynamic set of operations like in application frameworks. 此语法的维护可能很繁琐,因为它不会在编译时捕获类型错误。This syntax can be tedious to maintain because it doesn't catch type errors at compile time.

基于类的语法(仅限 .NET) ,其中,实体和操作分别由类和方法表示。Class-based syntax (.NET only), where entities and operations are represented by classes and methods. 此语法可生成更易于阅读的代码,使操作能够以类型安全的方式调用。This syntax produces more easily readable code and allows operations to be invoked in a type-safe way. 基于类的语法是建立基于函数的语法基础之上的一个精简层,因此,在同一应用程序中,这两种变体可以换用。The class-based syntax is a thin layer on top of the function-based syntax, so both variants can be used interchangeably in the same application.

示例:基于函数的语法 - C#Example: Function-based syntax - C#

以下代码是作为持久函数实现的简单 Counter 实体示例。The following code is an example of a simple Counter entity implemented as a durable function. 此函数定义三个操作:addresetget,每个操作针对整数状态运行。This function defines three operations, add, reset, and get, each of which operates on an integer state.

[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;
    }
}

有关基于函数的语法及其用法的详细信息,请参阅基于函数的语法For more information on the function-based syntax and how to use it, see Function-based syntax.

示例:基于类的语法 - C#Example: Class-based syntax - C#

以下示例是使用类和方法的 Counter 实体的等效实现。The following example is an equivalent implementation of the Counter entity using classes and methods.

[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 类型的对象,该对象包含存储计数器当前值的字段。The state of this entity is an object of type Counter, which contains a field that stores the current value of the counter. 为了将此对象持久保存在存储中,Json.NET 库会将其序列化和反序列化。To persist this object in storage, it's serialized and deserialized by the Json.NET library.

有关基于类的语法及其用法的详细信息,请参阅定义实体类For more information on the class-based syntax and how to use it, see Defining entity classes.

访问实体Access entities

可以使用单向或双向通信访问实体。Entities can be accessed using one-way or two-way communication. 以下术语用于区分这两种形式的通信:The following terminology distinguishes the two forms of communication:

  • 使用双向(往返)通信调用实体 。Calling an entity uses two-way (round-trip) communication. 向实体发送操作消息,然后等待响应消息,再继续。You send an operation message to the entity, and then wait for the response message before you continue. 响应消息可以提供结果值或错误结果,例如 JavaScript 错误或 .NET 异常。The response message can provide a result value or an error result, such as a JavaScript error or a .NET exception. 然后,调用方会观察到此结果或错误。This result or error is then observed by the caller.
  • 使用单向(发后不理)通信向实体发出信号 。Signaling an entity uses one-way (fire and forget) communication. 发送操作消息,但不等待响应。You send an operation message but don't wait for a response. 尽管可以保证消息最终会送达,但发送方并不知道何时送达,也无法观察到任何结果值或错误。While the message is guaranteed to be delivered eventually, the sender doesn't know when and can't observe any result value or errors.

可以从客户端函数、业务流程协调程序函数或实体函数内部访问实体。Entities can be accessed from within client functions, from within orchestrator functions, or from within entity functions. 并非所有形式的通信都受所有上下文的支持:Not all forms of communication are supported by all contexts:

  • 在客户端内部,可以向实体发出信号,并可以读取实体状态。From within clients, you can signal entities and you can read the entity state.
  • 在业务流程内部,可以向实体发出信号,并可以调用实体。From within orchestrations, you can signal entities and you can call entities.
  • 在实体内部,可以向实体发出信号。From within entities, you can signal entities.

以下示例演示了访问实体的各种方式。The following examples illustrate these various ways of accessing entities.

示例:客户端向实体发出信号Example: Client signals an entity

若要从普通的 Azure 函数(也称为客户端函数)访问实体,请使用实体客户端绑定To access entities from an ordinary Azure Function, which is also known as a client function, use the entity client binding. 以下示例演示一个队列触发的函数使用此绑定来发送实体信号。The following example shows a queue-triggered function signaling an entity using this binding.

备注

为简单起见,以下示例演示了用于访问实体的松散类型化语法。For simplicity, the following examples show the loosely typed syntax for accessing entities. 通常,我们建议通过接口访问实体,因为这种方法提供更多的类型检查。In general, we recommend that you access entities through interfaces because it provides more type checking.

[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);
}

术语“信号”是指实体 API 调用是单向、异步的。 The term signal means that the entity API invocation is one-way and asynchronous. 客户端函数无法知道实体何时处理了操作。It's not possible for a client function to know when the entity has processed the operation. 另外,客户端函数无法观察到任何结果值或异常。Also, the client function can't observe any result values or exceptions.

示例:客户端读取实体状态Example: Client reads an entity state

客户端函数还可以查询实体的状态,如以下示例中所示:Client functions can also query the state of an entity, as shown in the following example:

[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);
}

实体状态查询将发送到持久跟踪存储,并返回实体的最近持久状态。Entity state queries are sent to the Durable tracking store and return the entity's most recently persisted state. 此状态始终为“已提交”,即,它永远不会是在执行操作的中途设想的暂时中间状态。This state is always a "committed" state, that is, it's never a temporary intermediate state assumed in the middle of executing an operation. 但是,与实体的内存中状态相比,此状态可能已过时。However, it's possible that this state is stale compared to the entity's in-memory state. 如下一部分所述,只有业务流程可以读取实体的内存中状态。Only orchestrations can read an entity's in-memory state, as described in the following section.

示例:业务流程向实体发出信号和调用实体Example: Orchestration signals and calls an entity

业务流程协调程序函数可以使用业务流程触发器绑定中的 API 访问实体。Orchestrator functions can access entities by using APIs on the orchestration trigger binding. 以下示例代码演示了一个调用 Counter 实体并发送其信号的业务流程协调程序函数。The following example code shows an orchestrator function calling and signaling a Counter entity.

[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);
    }
}

只有业务流程能够调用实体和获取响应,该响应可能是返回值或异常。Only orchestrations are capable of calling entities and getting a response, which could be either a return value or an exception. 使用客户端绑定的客户端函数只能发送实体的信号。Client functions that use the client binding can only signal entities.

备注

从业务流程协调程序函数调用实体类似于从业务流程协调程序函数调用活动函数Calling an entity from an orchestrator function is similar to calling an activity function from an orchestrator function. 主要区别在于,实体函数是具有地址(实体 ID)的持久对象。The main difference is that entity functions are durable objects with an address, which is the entity ID. 实体函数支持指定操作名称。Entity functions support specifying an operation name. 另外,活动函数是无状态的,没有操作概念。Activity functions, on the other hand, are stateless and don't have the concept of operations.

示例:实体向实体发出信号Example: Entity signals an entity

当某个实体函数在执行操作时,可以向其他实体(甚至是自身)发送信号。An entity function can send signals to other entities, or even itself, while it executes an operation. 例如,我们可以修改上述 Counter 实体示例,以便在计数器达到值 100 时,向某个监视器实体发送“已达到里程碑”信号。For example, we can modify the previous Counter entity example so that it sends a "milestone-reached" signal to some monitor entity when the counter reaches the value 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;

实体协调(当前仅限 .NET)Entity coordination (currently .NET only)

有时可能需要跨多个实体协调操作。There might be times when you need to coordinate operations across multiple entities. 例如,在银行应用程序中,可能会使用实体来代表不同的银行帐户。For example, in a banking application, you might have entities that represent individual bank accounts. 将资金从一个帐户转移到另一个帐户时,必须确保源帐户有足够的资金。When you transfer funds from one account to another, you must ensure that the source account has sufficient funds. 还必须确保对源帐户和目标帐户的更新都以事务一致性的方式进行。You also must ensure that updates to both the source and destination accounts are done in a transactionally consistent way.

示例:转移资金 (C#)Example: Transfer funds (C#)

以下示例代码使用业务流程协调程序函数在两个 account 实体之间转移资金。The following example code transfers funds between two account entities by using an orchestrator function. 协调实体更新需要使用 LockAsync 方法在业务流程中创建一个关键的节。 Coordinating entity updates requires using the LockAsync method to create a critical section in the orchestration.

备注

为简单起见,本示例重复使用了前面定义的 Counter 实体。For simplicity, this example reuses the Counter entity defined previously. 在实际应用程序中,最好是定义更详细的 BankAccount 实体。In a real application, it would be better to define a more detailed BankAccount entity.

// 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 在释放时会返回一个以关键节结尾的 IDisposableIn .NET, LockAsync returns IDisposable, which ends the critical section when disposed. 可将此 IDisposable 结果与 using 块一起使用,以获取关键节的语法表示形式。This IDisposable result can be used together with a using block to get a syntactic representation of the critical section.

在上面的示例中,业务流程协调程序函数已将资金从源实体转到目标实体。In the preceding example, an orchestrator function transferred funds from a source entity to a destination entity. LockAsync 方法同时锁定了源和目标帐户实体。The LockAsync method locked both the source and destination account entities. 这种锁定确保在业务流程逻辑退出位于 using 语句末尾的关键节之前,其他任何客户端都不能查询或修改任一帐户的状态。This locking ensured that no other client could query or modify the state of either account until the orchestration logic exited the critical section at the end of the using statement. 此行为可防止从源帐户透支的情况。This behavior prevents the possibility of overdrafting from the source account.

备注

当业务流程终止(正常终止,或终止并出错)时,正在进行的所有关键节都会隐式结束,并释放所有锁。When an orchestration terminates, either normally or with an error, any critical sections in progress are implicitly ended and all locks are released.

关键节的行为Critical section behavior

LockAsync 方法在业务流程中创建一个关键节。The LockAsync method creates a critical section in an orchestration. 这些关键节可防止其他业务流程对一组指定的实体进行重叠的更改。These critical sections prevent other orchestrations from making overlapping changes to a specified set of entities. 在内部,LockAsync API 向实体发送“lock”操作;从每个相同实体收到“lock acquired”响应消息时,该 API 将会返回。Internally, the LockAsync API sends "lock" operations to the entities and returns when it receives a "lock acquired" response message from each of these same entities. 锁定和解锁是所有实体都支持的内置操作。Both lock and unlock are built-in operations supported by all entities.

当实体处于锁定状态时,不允许其他客户端对其执行任何操作。No operations from other clients are allowed on an entity while it's in a locked state. 此行为确保每次只有一个业务流程实例可以锁定某个实体。This behavior ensures that only one orchestration instance can lock an entity at a time. 如果调用方尝试针对业务流程锁定的实体调用某个操作,该操作将被置于挂起操作队列中。If a caller tries to invoke an operation on an entity while it's locked by an orchestration, that operation is placed in a pending operation queue. 只有在持有锁的业务流程释放其锁之后,才会处理挂起的操作。No pending operations are processed until after the holding orchestration releases its lock.

备注

此行为与大多数编程语言中使用的同步基元(例如 C# 中的 lock 语句)略有不同。This behavior is slightly different from synchronization primitives used in most programming languages, such as the lock statement in C#. 例如,在 C# 中,所有线程必须使用 lock 语句来确保在多个线程之间正确同步。For example, in C#, the lock statement must be used by all threads to ensure proper synchronization across multiple threads. 但是,实体不需要所有调用方显式锁定实体。Entities, however, don't require all callers to explicitly lock an entity. 如果任一调用方锁定实体,则针对该实体的所有其他操作将被阻止,并排队在该锁的后面。If any caller locks an entity, all other operations on that entity are blocked and queued behind that lock.

实体中的锁是持久性的,因此,即使回收了执行进程,这些锁也仍会保留。Locks on entities are durable, so they persist even if the executing process is recycled. 锁在内部保留为实体持久状态的一部分。Locks are internally persisted as part of an entity's durable state.

与事务不同,关键节在出错时不会自动回滚更改。Unlike transactions, critical sections don't automatically roll back changes in the case of errors. 而是必须显式编写任何错误处理(例如,回滚或重试)的代码,例如,捕获错误或异常。Instead, any error handling, such as roll-back or retry, must be explicitly coded, for example by catching errors or exceptions. 这是有意设计的。This design choice is intentional. 一般情况下,很难或者根本无法自动回滚业务流程造成的所有影响,因为业务流程可能会运行活动,并调用无法回滚的外部服务。Automatically rolling back all the effects of an orchestration is difficult or impossible in general, because orchestrations might run activities and make calls to external services that can't be rolled back. 此外,尝试回滚的过程本身可能会失败,需要进一步进行错误处理。Also, attempts to roll back might themselves fail and require further error handling.

关键节规则Critical section rules

与大多数编程语言中的低级锁定基元不同,不保证关键节死锁Unlike low-level locking primitives in most programming languages, critical sections are guaranteed not to deadlock. 为防止死锁,我们强制实施以下限制:To prevent deadlocks, we enforce the following restrictions:

  • 关键节不可嵌套。Critical sections can't be nested.
  • 关键节无法创建子业务流程。Critical sections can't create suborchestrations.
  • 关键节只能调用它们锁定的实体。Critical sections can call only entities they have locked.
  • 关键节无法使用多个并行调用来调用同一实体。Critical sections can't call the same entity using multiple parallel calls.
  • 关键节只能发送它们未锁定的实体的信号。Critical sections can signal only entities they haven't locked.

违反其中的任意规则都会导致运行时错误(例如 .NET 中的 LockingRulesViolationException),其中会提供一条消息来解释违反了哪条规则。Any violations of these rules cause a runtime error, such as LockingRulesViolationException in .NET, which includes a message that explains what rule was broken.

与虚拟执行组件的比较Comparison with virtual actors

许多持久实体功能来源于执行组件模型的灵感。Many of the durable entities features are inspired by the actor model. 如果你熟悉执行组件,则你可能会理解本文中所述的许多概念。If you're already familiar with actors, you might recognize many of the concepts described in this article. 持久实体非常类似于虚拟执行组件,或 Orleans 项目中普遍存在的“粒度”。Durable entities are particularly similar to virtual actors, or grains, as popularized by the Orleans project. 例如:For example:

  • 可通过实体 ID 对持久实体寻址。Durable entities are addressable via an entity ID.
  • 持久实体操作按顺序逐个执行,以防止出现争用情况。Durable entity operations execute serially, one at a time, to prevent race conditions.
  • 持久实体是在被调用或发送信号时隐式创建的。Durable entities are created implicitly when they're called or signaled.
  • 不执行操作时,将以静默方式从内存中卸载持久实体。When not executing operations, durable entities are silently unloaded from memory.

有一些重要的差别值得注意:There are some important differences that are worth noting:

  • 持久实体优先考虑持久性而不是延迟,因此可能不适合用于延迟要求较为严格的应用程序。Durable entities prioritize durability over latency, and so might not be appropriate for applications with strict latency requirements.
  • 持久实体不会对消息实施内置超时。Durable entities don't have built-in timeouts for messages. 在 Orleans 中,所有消息会在可配置的时间后超时。In Orleans, all messages time out after a configurable time. 默认为 30 秒。The default is 30 seconds.
  • 在实体之间发送的消息将按顺序可靠传送。Messages sent between entities are delivered reliably and in order. 在 Orleans 中,通过流发送的内容支持可靠或有序传送,但不保证粒度之间发送的所有消息支持这种传送方式。In Orleans, reliable or ordered delivery is supported for content sent through streams, but isn't guaranteed for all messages between grains.
  • 实体中的请求-响应模式限制为业务流程。Request-response patterns in entities are limited to orchestrations. 在实体内部,仅允许单向消息传送(也称为“发出信号”),原始执行组件模型中也是如此,但 Orleans 的粒度中不是如此。From within entities, only one-way messaging (also known as signaling) is permitted, as in the original actor model, and unlike grains in Orleans.
  • 持久实体不会死锁。Durable entities don't deadlock. 在 Orleans 中,可能会发生死锁,并且在消息超时之前不会解决死锁。In Orleans, deadlocks can occur and don't resolve until messages time out.
  • 持久实体可与持久业务流程结合使用,支持分布式锁定机制。Durable entities can be used in conjunction with durable orchestrations and support distributed locking mechanisms.

后续步骤Next steps