2.编程模型

这节内容概括了Orleans编程模型,并从《光环4》当前的服务摘取摘取部分代码进行展示。(4.1章会进一步讨论)

2.1 虚拟参与者

每一个参与者有由其类型及主键(128位GUID)唯一标识组成的唯一标识。Orleans编程模型基于.NET 4.5。参与者是Orleans的基础、孤立的分布式单元。 参与者像对象一样拥有行为和可变的状态。其状态被内置持久储存。孤立的参与者以为着他们不会共享内存,因此两个参与者只能通过消息传递的方式进行交互。

在Orleans中,参与者的虚拟化包括以下四个方面:

  1. 持续存在:参与者是持续存在的、虚拟的物理实体。参与者不能被明确的创建或销毁,并且其虚拟存在的特征不会受服务请求的崩溃的影响。因此参与者持久存在,并可寻址。

  2. 自动实例化:Orleans的运行时在内存中自动将参与者实例化的过程被称为激活。参与者在任何时间任何位置被激活0次或更多。参与者在没有待处理请求时不会进行实例化。当一个新的请求发送给未实例化的参与者时:Orleans运行时会自动选择一个服务器进行对实例进行激活,在此服务器上实例化的.NET对象使参与者生效,然后调用他的ActivateAsync方法进行实例化。如果此参与者在此服务器实例化失败,运行时会自动的在另一台新服务器进行新的操作。当应用响应并重新构建失败的参与者时,Orleans不需要像Erlang及Akka一样维护XX树。内存中未使用的参与者实例会的回收是运行时资源管理器工作的一部分。回收操作发生时,Orleans将触发DeactivateAsync方法使参与者有机会进行回收操作。

  3. 位置透明:参与者的实例化会在不同的时间和空间进行,甚至有些情况根本就不会有确切的物理地址。与参与者交互或者基于参与者运行的程序无需关系参与者环境的位置。这种模型类似于虚拟内存:随着时间的推移,给定的逻辑内存页可以映射至多个物理地址,移出分页或不映射的情况也会偶尔出现。就像操作系统在硬盘上自动划出内存页进行内存映射一样,Orleans运行时会在未实例化的参与者接受到请求时对参与者进行实例化。

  4. 自动伸缩:当下,Orleans为参与者类型提供两种激活模式:单点激活模式(默认):使用无状态的工人模型,并且只允许同时激活一个参与者。Orleans会根据吞吐量的增长,自动按需创建新的参与者。“自适应”意味着同一个参与者的实例彼此之间没有状态的交互。因此,无状态的工人模式适用于状态不变或无状态的接口,例如只读缓存。

用虚拟实体取代物理实体实现的参与者对Orleans编程模型和接口产生了巨大的影响。自动激活使参与者不需要显式的激活或关闭,位置透明使参与者的生命周期得到托管,持久存在实现了参与者的故障重启。以上功能大幅简化了参与者模式的编程模型。

2.2 参与者接口

参与者彼此之间的交互是通过强类型接口的方法、属性声明来完成的:参与者接口中的所有的方法或属性都应该是异步的;其返回类型也应该在接口中声明。。详情参见2.4节。

public interface IGameActor : IActor
{
    Task<string> GameName { get; }
    Task<List<IPlayerActor> CurrentPlayers { get; }
    Task JoinGame(IPlayerActor game);
    Task LeaveGame(IPlayerActor game);
}

2.3 参与者依赖

参与者引用是一种强类型的参与者代理,来允许其他参与者或者是非参与者的代码来调用参与者的方法或属性。参与者的引用可以通过调用工厂类中的GetActor方法获得。工厂类会在Orleans编译时自动生成,并且指定参与者环境的主键。参与者依赖也可以通过远程方法或属性的返回值来获得。参与者引用能够在参与者的方法调用时作为输入参数进行传递。

public static class GameActorFactory
{
    public static IGameActor GetActor(Guid gameId);
}

在Orleans中,参与者的引用通过发送者本地创建,不需要绑定或注册阶段即可直接使用。参与者的引用是虚拟的。并且不会暴露给程序员目标参与者的任何位置信息。同样也不会存在绑定之类的概念。在传统的RPC模型(例如Java RMI,CORBA, 或者WCF)中,程序员通常需要通过外部的注册或内部显式的绑定服务中的虚拟依赖。在简化编程的过程的同时也最大限度通过无需绑定或解析远端服务,而将请求直接加入到管线中来提高吞吐量。

2.4 约定

参与者通过发送异步消息进行交互。大多数现代的分布式系统编程模型中,这些信息交换以方法调用的方式暴露。然而,与传统的模型的区别在于,Orleans方法调用立即返回一个未来约定的结果,而不是在结果返回前一直锁定。无需明确的线程管理的约定使并发性得到保证。

Promises have a three-state lifecycle. Initially, a promise is unresolved; it represents the expectation of receiving a result at some future time. When the result is received, the promise becomes fulfilled and the result becomes the value of the promise. If an error occurs, either in the calculation of the result or in the communication, the promise becomes broken. 约定的生命周期中有三种状态:起初,约定处于未解决状态;其相当于在未来的某个时刻返回预期的结果。当结果返回时,约定转换为实现的同时,结果转换为约定的值。当错误发生时,不论计算结果是否处于传递过程,约定变为损坏。

约定通过以下两种方式暴露:System.Threading.Tasks.Task的实例来表示类型的最终值,通过System.Threading.Tasks.Task类表示执行结果返回值为空。

The main way to use a promise is to schedule a closure (or continuation) to execute when the promise is resolved. Closures are usually implicitly scheduled by using the await C# keyword on a promise. In the example below the compiler does stack ripping and transforms the code after‘await’into a closure that exe- cutes after the promise is resolved. Thus, the developer writes code, including error handling, that executes asyn- chronously but looks sequential and hence more natural.

IGameActor gameActor = GameActorFactory.GetActor(gameId);
try{
    string name = await gameActor.GameName;
    Console.WriteLine(“Game name is ” + name);
}
catch(Exception){
    Console.WriteLine(“The call to actor failed”); 
}

2.5 Turns

Actor activations are single threaded and do work in chunks, calledturns. An activation executes one turn at a time. A turn can be a method invocation or a closure executed on resolution of a promise. While Orleans may execute turns of different activations in parallel, each activation always executes one turn at a time.

The turn-based asynchronous execution model allows for interleaving of turns for multiple requests to the same activation. Since reasoning about interleaved execution of multiple requests is challenging, Orleans by default avoids such interleaving by waiting for an activation to finish processing one request before dispatching the next one. Thus, an activation does not receive a new request until all promises created during the processing of the current request have been resolved and all of their associated closures executed. To override this default behavior, an actor class may be marked with the [Reentrant] attribute. This indicates that an activation of that class may be given another request to process in between turns of a previous request, e.g. while waiting for a pending IO operation. Execution of turns of both requests is still guaranteed to be single threaded, so the activation is still executing one turn at a time. But turns belonging to different requests of a reentrant actor may be freely interleaved.

2.6 Persistence

The execution of actor requests may modify the actor state, which may or may not be persistent. Orleans provides a facility to simplify persistence management. An actor class can declare a property bag interface that represents the actor state that should be persisted. The runtime then provides each actor of that type with a state object that implements that interface and exposes methods for persisting and refreshing the state.

// State property bag interface
public interface IGameState : IState
{
    GameStatus Status { get; set }
    List<IPlayerActor> Players { get; set;}
}
// Actor class implementation
public class GameActor : ActorBase<IGameState> IGameActor
{
    Task JoinGame(IPlayerActor game) {
    // Update state property bag this.State.Players.Add(IPlayerActor); // Checkpoint actor state
    return this.State.WriteStateAsync();
}

The interaction with the underlying storage is implemented viapersistence providers, which serve as adaptors for specific storage systems: SQL database, column store, blob store, etc.

2.7 Timers and Reminders

Orleans的计时器有两种实现。透明的计时器与.NET的计时器接口相似,但提供了一个独立的线程保证稳定。他们随参与者的创建产生,在参与者关闭时消失。

A reminder is a timer that fires whether or not the actor is active. Thus, it transcends the actor activation that created it, and continues to operate until explicitly deleted. If a reminder fires when its actor is not activated, a new activation is automatically created to process the reminder message, just like any other message sent to that actor. Reminders are reliable persistent timers that produce messages for actors that created them while allowing the runtime to reclaim system resources by deactivating those actors, if necessary, in between reminder ticks. Reminders follow the conceptual model of virtual actors that eternally exist in the system and are activated in memory only as needed to process incoming requests. Reminders are a useful facility to execute infrequent periodic work despite failures and without the need to pin anactor’s activation in memory forever. Reminder是一种参与者激活状态无关的计时器。其超越了创建他的参与者的生命周期。并在完成接下来的操作后被显式删除。如果一个remander触发时那个参与者处于未激活状态,一个

results matching ""

    No results matching ""