Microsoft Orleans 之 入门指南

前言:Orleans 提供了构建大型分布式运算的应用程序框架,而不需要学习那些复杂的并发或者可拓展的模式。它由微软研究院创建和维护,并且它已被一些微软产品组广泛应用于微软的Azure,以及其他一些公司。

        Orleans 在2015年一月微软将其源代码开源,迅速得到开发者的认可,借助于活跃的社区和开发团队的贡献,框架不断的被完善和新功能的增加,微软研究院也将一直为该项目投资下去。

背景:云应用程序和服务本质上是并行的和分布式的。他们也互动性和动态性,往往需要近实时的云实体之间的直接相互作用。对于今天的程序构建来说这样的应用是非常困难的。随着工作量的增长,通常需要昂贵的设计和架构的迭代,开发过程需要专家级的程序员。

  今天大多数高性能应用程序是通过标准的SOA构建的,比如,亚马逊、google或者Facebook 一个简单页面的渲染展示,是通过数以百计的复杂的SOA服务协作和管理。事实上,这样一个复杂的服务是很难通过一个服务来构建完成的。

     SOA的模块化服务化、大数据、高并发已经服务动态分配和不间断的运行,不停的挑战程序员处理这些问题的能力,但是现有提供的这些工具或者框架不足以解决这些问题。

      无状态的多层模型到存储层划分问题。它经常需要在无状态层中进行缓存以获得可接受的性能,增加了复杂性,并引入了缓存一致性问题。

Actors: Actor 模型对细粒度的单个Actor对象的支持很好,每个Actor对象之间彼此孤立并且都是轻量级的,可以保证这些Actor独立建模。他们通过异步消息传递,从而使Actor彼此直接沟通。

       值得注意的是,一个Actor是在逻辑意义的单线程上执行,同时Actor的状态的封装和其他Actor之间的隔离,这大大的简化了Actor代码层面的复杂性,开发者使用的Actor不必担心临界区、互斥锁,锁的水准,和其他复杂的难以预测的问题,这些与实际应用程序逻辑是无关的,Actor是在可用的硬件资源池内动态创建的。这使得Actors模型在负载均衡上比SOA的模块化分割更容易维护和拓展。

  在过去的十年里,Erlang 一直是传统Actor模式的最流行的实现,面对上述SOA的挑战,业内重新审视Actor 模式,这促使出现了新的解决方案:Scala actors,Akka,DCell

Virtual Actors:

Orleans 是一种改进的Actors模式,大量借鉴了Erlang和分布式对象系统中的实现,添加静态类型,消息indirection 和Actors的虚拟化,将它们暴露在一个集成的编程模型中。

而Erlang是一个纯粹的函数式语言有其自己特定的虚拟机,Orleans 的编程模型,直接利用.NET 和面向对象的能力,它提供了一个框架,使复杂的分布式应用程序的开发更容易,并使所得到的应用程序的可扩展性更强。

不像其他系统如Erlang或Akka 的Actors , Orleans Grains 的Actors 是虚拟Actors。他们通过异步消息传递,这大大不同于同步方法调用。

在Orleans 框架里 grains 的管理和激活类似于操作系统的虚拟内存管理,grain 的激活是通过创建一个服务端的copy ,如果后续它不再使用的时就自动变为未激活状态。

如果一个消息被发送到grain且在任何服务器上都没有激活状态的grain时,运行时管理器将自动创建一个新的grain,因为grains是虚拟的,所以他们永远不会失败,即使目前主机所有服务器激活失败。

这使得没必要测试是否有grain存在的必要性,以及故障跟踪;Orleans 运行时做这一切都是自动的。

更多的内容细节请阅读如下连接

Read the MSR Technical Report on Orleans

优点:

 Orleans的优点有  1,提高开发人员的开发效率,即使不是专业开发人员。

                         2,不需要做太多的工作,默认支持很强的可拓展性。

下面详细讲解这些优点

开发人员的生产率

Orleans 的编程模型提供了以下关键的抽象,提出了专家和非专家的程序员的生产率,保证系统服务。

1.遵循面向对象的规范:Actor是实现声明的.net类,与Actors接口的异步方法,因此,开发人员可以把Actor的方法作为远程对象的方法直接调用。透明地调用Actor的方法和故障的处理。

2.单线程执行:确保Actor只在一个线程上执行,与其他Actor的隔离,保证不用担心并发的问题,也不会担心使用数据共享而必须使用锁机制等问题,对于非专家程序员开发分布式程序变得更容易。

3.透明激活:只有当需要处理一个消息时,运行时才激活一个Actor,区分开了通过代码控制创建Actor引用 和通过物理内存激活Actor 的概念,这中机制在应用程序中是透明的,开发人员不需要关系一个Actor是如何创建的。很多适合一个Actor的激活或者销毁,是由虚拟内存决定的。应用程序不断轮训所有的“内存空间”的逻辑上创建的Actors,无论他们是在物理内存在任何特定的点。Actor的生命周期是应用管理,此功能明显改善了传统Actor模型。

4.位置透明:通过Actor的对象引用来调用其他机器上的包含业务逻辑的Actor,在Orleans 运行时,这两个Actor之间是相互透明的。

  如果应用程序找不到一个Actor,这有可能是当前这个Actor被垃圾回收了或者是还没有被激活。

5.透明集成持久化存储:Orleans 允许发布持久化到内存中的Actor的状态的映射,它同步更新确保成功持久化到内存中后可以获得一个正确的结果。

可以直接拓展或者更改现有持久化机制。

6.错误自动传播:运行时调用链上分布式的try/catch将未处理的错误与异常自动处理。因此,错误不会在应用程序中丢失。这允许开发人员在适当的地方放置错误处理逻辑,而无需在每个级别手动传播错误。

 默认的透明的可扩展性

实践证明Orleans 编程模型 提供一个较低级别的系统功能就可以很轻易的实现开发人员的程序拓展和服务器拓展,下面详解在性能和拓展点上的关键点:

1.应用状态的细粒度的分割:

通过使用Actor作为直接寻址实体,程序员隐式地打破了他们的应用程序的整体状态,虽然orleans 没有明确指出如何划分一个大或者小的Actor模型,但是在大多数情况下,每一个自然程序实体代表一个Actor来多划分出更多的Actor来说是有好处的,比如一个用户账户、一个订单等等。随着Actor被单独寻址,它们的物理逻辑在运行时被抽象出来了

2.自适应资源管理:假设在负载均衡和通信的请求不失败的情况下因为Actor位置的透明或者运行时的管理动态调整了硬件资源的配置 决定迁移整个计算集群的Actors,而相互通信的Actor之间没有彼此的位置,可以根据吞吐量的需要动态创建一个或多个运行时的演员特定的Actor副本,而无需对应用程序代码进行任何更改。

3.多路转换通信:在Orleans Actors的逻辑终结点(endpoints)和消息,他们之间是复用在一组固定的所有Tcp连接(TCP sockets),这使得主机一组数量庞大的Actors组的寻址不会影响到系统的性能,另外actors的激活和停用不会影响物理终结点的注册/注销的性能成本,如TCP端口或HTTP URL,甚至关闭TCP连接。

4.高效的调度:大量单线程Actors的运行时的调度执行运行在每个处理器核心的的自定义线程池中,和用非阻断连续Actor的代码(在Orleans 的编程模型必须的)在一个高效协作多线程方式运行应用程序代码没有竞争。这使得系统能够达到高吞吐量和大大提高了中央处理器的利用率(高达90%),具有极高的稳定性。

事实上,在系统中的Actor的数量的增长和负载不会导致额外的线程或其他操作系统有助于单个节点和整个系统的可扩展性。

5.显式同步:Orleans 的编程模式明确指导开发人员使用异步执行编写异步非阻塞代码的分布式应用程序,

Orleans 的编程模型使分布式应用程序的异步性和指导程序员编写的非阻塞异步代码,结合异步消息传递和高效调度,这使一个大程度的分布式并行性和整体吞吐量,没有明确的使用多线程。

Grains:

分布式的单位Grains(Actors)

并发是分布式应用程序固有的特点,这使得增加了它的复杂性,而Actor模型的特殊性和具有创造性的特点,使这样的事情变得简单了。

Actor以两种方式做到这一点:

1.通过提供单个线程访问一个Actor实例的内部状态。

2.除通过消息传递外,不在Actor实例之间共享数据。

Grains 是Orleans 应用程序的构建块,它们是彼此孤立的原子单位,分布的,持久的。一个典型的grain是有状态和行为的一个单实例。

执行单元:单线程执行模型背后的原理是Actor(remote)轮流调用其方法。如,从Actor A 到 Actor B的消息将被放置在一个队列中 ,当所有优先级比较高的服务处理完成才去调用队列相关的处理程序。这使我们能够避免所有使用锁来保护Actor的状态,因为它本质上是保护,防止数据竞争。然而,它也可能会出现一些问题,消息的循环传递。如:如果A发送消息给B从它的一个方法,并等待它的完成,和B发送一个消息,也等待其完成,应用程序就将会出现死锁的问题。

 Grains 激活-grain运行时实例

当运行一个grain的时候,Orleans 确保在一个Orleans silos 有一个grain的一个实例,当发现任何的silo中都没有一个grain的一个实例时,运行时就会创建一个,这个过程被称为激活。

如果使用的是一个已经持久化了的grain,运行时就自动去后台读取并激活,Orleans 控制着整个激活或者停用的过程,当编码一个grain时,开发人员假定所有的grain都是被激活的。

grain激活在块中执行工作,并在它移动到下一个之前完成每一个块。大块的工作包括响应来自其他grain或外部客户请求的方法调用,并止于前一块完成关闭。对应于一个工作块的基本的执行单元被称为一个turn。

在并行期间orleans 执行很多属于不同的激活的turn,每个激活是允许同时执行它的turns,这意味着没必要使用锁或者其他同步的方法去阻止数据的争夺和多线程的危险,如上所述,无论如何,预定关闭的truns的不可预测的交错,可能会导致grain的状态与计划关闭时时不同的,所以开发人员仍然需要注意交错错误。

激活模式

Orleans 支持两种模式:

1.单激活模式(默认):单激活模式(默认),创建每一个grain的同时激活。

2.无边界工作模式:创建自主激活的grain,以提高吞吐量。 “自主”意味着有相同grain不同的激活之间状态不一致。因此,这种模式适合于无本地状态保留grain,或grain的本地状态是不变的,比如作为持久态的高速缓存grain

Silos:

Orleans silo 是一个主机服务,用来执行Orleans grains,它监听一个端口,用来监听从silo到silo的消息或者从其他客户端到silo的消息的,典型的silo就是,每台机器运行一个silo。

cluster:

大量的silos 同时在一起工作就形成了orleans的集群,orleans运行完全自动化的集群管理。

所有silo都使用一个动态更新的共享成员存储库,并有助于协调集群管理,通过阅读共享存储库了解对方的位置,在任何时候,一个silo可以通过注册在共享存储中连接到一个集群。

这种方式的集群可以在运行时动态扩展。Orleans 提供弹性和可用性从群集中删除无效的silos。

对于Orleans 管理集群深入详细的文档,请阅读集群管理。

接下来我们看看Orleans框架客户端的使用 。

Clients:

Orleans 和客户端代码

Orleans 包括两个不同的部分:Orleans 基础部分(grains) 和客户端部分

Orleans 的一部分是由应用程序的运行时服务称silos grains 组成,在调度限制下的运行时执行的Grain代码和确保内部在Orleans 编程模型。

客户端部分通常是一个web前端,通过少量的Orleans 客户端库连接到Orleans 部分,使得客户端代码可以通过引用服务端的一个grain的引用进行通讯。

例如:一个ASP.NET web应用程序运行在服务端的部分可以是Orleans 的客户端。客户端部分运行在.net 应用程序池的主线程中,和不受调度的限制和Orleans 运行时的保证。

下一步,我们将看看一个grains如何接收异步消息,或推送数据。

客户端观察者

 有一种情况,其中一个简单的消息/响应模式是不够的,客户端需要接收异步通知。例如,一个用户可能希望在一个新的即时消息发布的时候被一个朋友通知。

客户端观察者是一允许异步通知客户端的一种机制。观察者是一个继承自IGrainObserver的单向异步接口,它的所有方法都的返回值必须是void。grain 给这个观察者发送一个通知是通过调用这个接口的一个方法,除了它没有返回值,因此grain不需要依赖于结果,Orleans运行时将确保单项通知,grain 发布了这样的一个通知同时也提供了相应的add observers或者remove observers的 API。

另外,通常它也提供了一些使用的用来取消这些订阅的方法,Grain 开发者可以使用Orleans 的这个ObserverSubscriptionManager<T>泛型类简化observed 的grain类型的开发。

订阅一个通知,客户端必须先创建一个实现了观察者接口的C#本地类的对象,它调用观察者工厂的方法(CreateObjectReference()),重定向到C#对象进入grain对象引用,然后可以将其传递给通知grain的订阅方法。

该模型也可以被其他grains用于接收异步通知。不像在客户端订阅的情况,订阅grain只是实现Observer接口的一个面,并通过自身的引用(如:this.AsReference<IMyGrainObserverInterface>)

代码示例

假设我们有一个周期性发送信息给客户的grain,为简单起见,在我们的例子中的消息将是一个字符串。我们首先定义客户端接收消息的的接口。

该接口将看起来像这样

public interface IChat : IGrainObserver
{
    void ReceiveMessage(string message);
}


唯一特殊的是继承了IGrainObserver,现在任何用户想观察这些信息应该实现一个实现了IChat接口的类。
如下:
public class Chat : IChat
{
    public void ReceiveMessage(string message)
    {
        Console.WriteLine(message);
    }
}


现在在服务器上,我们应该有一个grain,发送这些聊天信息给客户端。grain也应该有一个为客户端订阅和退订的消息机制。
订阅的grain可以用类ObserverSubscriptionManager:
class HelloGrain : Grain, IHello
{
    private ObserverSubscriptionManager<IChat> _subsManager;

    public override async Task OnActivateAsync()
    {
        // We created the utility at activation time.
        _subsManager = new ObserverSubscriptionManager<IChat>();
        await base.OnActivateAsync();
    }

    // Clients call this to subscribe.
    public async Task Subscribe(IChat observer)
    {
        _subsManager.Subscribe(observer);
    }

    //Also clients use this to unsubscribe themselves to no longer receive the messages.
    public async Task UnSubscribe(IChat observer)
    {
        _SubsManager.Unsubscribe(observer);
    }
}

可以使用ObserverSubscriptionManager<IChat>的实例给客户端发送通知消息,这个发送消息的方法接受一个Action<T>的委托或者一个表达式,您可以调用接口上的任何方法,将其发送给客户端。
在我们的案例只有一个方法ReceiveMessage,我们的服务器上的发送代码会看起来像这样:
public Task SendUpdateMessage(string message)
{
    _SubsManager.Notify(s => s.ReceiveMessage(message));
    return TaskDone.Done;
}


现在我们的服务器来发送信息观察客户的方法,用于订阅/退订和客户端两种方法实现了一个类能够观察grain的消息。最后一步是创建基于我们以前实现的聊天类客户观察员 Chat 引用 ,并让它收它订阅的消息。

代码看起来像这样:
//First create the grain reference
var friend = GrainClient.GrainFactory.GetGrain<IHello>(0);
Chat c = new Chat();

//Create a reference for chat usable for subscribing to the observable grain.
var obj = await GrainClient.GrainFactory.CreateObjectReference<IChat>(c);
//Subscribe the instance to receive messages.
await friend.Subscribe(obj);


现在我们的服务器上调用grain的方法SendUpdateMessage ,所有订阅的客户端将收到消息。在我们的客户代码,Chat 的实例将变量c输出到控制台。
注意:支持观察员可能会在未来的版本中删除,取而代之的是一个简单的消息流短信,它可以支持相同的概念,更多的灵活性和可靠性。

常见问题:

微软是否支持orleans?

orleans的源代码已经在GitHub上的MIT许可下发布。微软将继续投资在orleans和接受社区的代码贡献。

可以获的一个“启动”的License?

License 已经放在源代码releases 下

Orleans 适用于生产环境吗?

答案是肯定的

什么时候使用Grain 什么时候使用一个普通对象?

有两种方法来回答这个问题,运行时和建模的角度。

从运行时角度看:grain内创建的对象是不能远程调用,Grain 的位置透明在系统的任何位置都可以访问,这样他们就可以被自动放置或部署任何服务器,和当他们寄宿的服务器故障时可以重启。

从建模的角度看:在你的 Orleans-based应用程序中有四种基本的组件,通信接口、消息载体、grains和grains的数据保存,对象用户承载grains的数据,通信接口是一些有一定限制的常规接口。

仍然存在一些问题就是,在一个给定的系统中的实体应被建模为grain吗?

一般来说,你应该使用一个grain来模拟一个独立的实体,它有一个公开的通信接口与系统的其他组件,并有自己的生命周期,即,它可以独立存在于其他组件中。例如,在社会网络中的一个用户是一个grain,而它的名字不是。用户的新闻墙可能是一个grain,而它所接收到的消息的列表不是(因为墙是由其他用户访问,而列表的消息是一个私人数据)。希望这个网站上的样本将有助于你识别一些模式,并看到你自己的场景的相似之处。

如何提高Grain 的高访问?

一个grain的吞吐量是有限的,它是运行在一个单线程中,因此,最好是避免设计一个单一的grain来接收不成比例的请求。

有各种各样的模式,有助于防止单一grain的超载,即使在逻辑上它是一个通信的中心点。

例如:如果grain是 一个 大量grains的 统计或者计数的聚合器,一个行之有效的方法是增加一个控制数量的中间聚合grains和分配每个报告的grains(使用module在key或hashcode)到中间聚合器,使负载或多或少均匀地分布在所有中间聚合器的grains,在他们的定期部分聚集到中央聚合器grains

 

如何取消或者是取消激活grain?

一般不需要强制一个grain失活,作为orleans运行时自动检测并关闭闲置激活的grain并回收系统资源。在特殊的情况下,当你认为你需要立即使grain失活时,可以通过调用基类方法base.DeactivateOnIdle()。

我可以告诉orleans,在哪里激活grain吗?

它可以通过限制策略( restrictive placement strategies)来实现,但我们一般认为这是一个反模式,所以不建议。如果你发现自己需要指定激活特定的silo grain,应该让你的系统来处理,这样就可以充分利用orleans框架。

通过解决这样的问题,表明应用程序承担了资源管理器的负担,而不一定有足够的信息系统的全局状态多的好。

可以多个数据中心独立部署吗?

orleans部署目前仅限于一个单一的数据中心。

我可以热部署grain添加或更新他们吗?

目前是不可以。

如何升级grains的版本?

orleans目前不支持即时更新grain代码。要升级到新版本的grain代码,您需要提供并启动一个新的部署,切换过来,然后关闭旧部署。

云缓存服务中可以保存grain的状态吗?

它可以通过云存储,但是默认没有提供,你可以自己创建一个。

我可以从公共网络连接到orleans 的 slios?

orleans是托管服务的后端部分,你应该创建一个前端服务器的客户端连接到你的服务端。它可以基于Web API项目,HTTP,Socket服务器,一个signalr服务器或其他东西。你真的可以从Internet连接到orleans,但从安全的角度考虑,不是一个好的实践。

如果调用一个grain返回之前slio失败,会发生什么?

您将收到一个异常,您可以捕获和重试或做任何其他在您的应用程序逻辑中有意义的。原文如下,

You’ll receive an exception which you can catch and retry or do anything else which makes sense in your application logic. The Orleans runtime does not immediately recreate grains from a failed silo because many of them may not be needed immediately or at all. Instead, the runtime recreates such grains individually and only when a new request arrives for a particular grain. For each grain it picks one of the available silos as a new host. The benefit of this approach is that the recovery process is performed only for grains that are actually being used and it is spread in time and across all available silos, which improves the responsiveness of the system and the speed of recovery. Note also that there is a delay between the time when a silo fails and when the Orleans cluster detects the failure. The delay is a configurable tradeoff between the speed of detection and the probability of false positives. During this transition period all calls to the grain will fail, but after the detection of the failure the grain will be created, upon a new call to it, on another silo, so it will be eventually available. More information can be found here.

如果一个grain调用需要太多的时间来执行,会发生什么?

 

 

Since Orleans uses a cooperative multi-tasking model, it will not preempt the execution of a grain automatically but Orleans generates warnings for long executing grain calls so you can detect them. Cooperative multi-tasking has a much better throughput compared to preemptive multi-tasking. You should keep in mind that grain calls should not execute any long running tasks like IO synchronously and should not block on other tasks to complete. All waiting should be done asynchronously using the await keyword or other asynchronous waiting mechanisms. Grains should return as soon as possible to let other grains execute for maximum throughput.

什么情况下分割用例(同时在多个silos中激活的同一个grain)?

This can never happen during normal operations and each grain will have one and only one instance per ID. The only time this can occur is when a silo crashes or if it’s killed without being allowed to properly shutdown. In that case there is a 30-60 seconds window (based on configuration) where a grain can exist in multiple silos before one is removed from the system. The mapping from grain IDs to their activation (instance) addresses is stored in a distributed directory (implemented with DHT) and each silo owns a partition of this directory. When membership views of two silos differ, both can request creation of an instance and as a result. two instances of the grain may co-exist. Once the cluster silos reach an agreement on membership, one of the instances will be deactivated and only one activation will survive. You can find out more about how Orleans manages the clusters at Cluster Management page. Also you can take a look at Orleans’s paper for a more detailed information, however you don’t need to understand it fully to be able to write your application code. You just need to consider the rare possibility of having two instances of an actor while writing your application.

 

posted @ 2016-06-29 23:22  Adolph_Xue  阅读(4662)  评论(0编辑  收藏  举报