0%

前言: 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泛型类简化observed 的grain类型的开发。

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

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

代码示例

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

该接口将看起来像这样

1
2
3
4
public interface IChat : IGrainObserver
{
void ReceiveMessage(string message);
}
1
2
3
4
5
6
7
public class Chat : IChat
{
public void ReceiveMessage(string message)
{
Console.WriteLine(message);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class HelloGrain : Grain, IHello
{
private ObserverSubscriptionManager<IChat> _subsManager;

public override async Task OnActivateAsync()
{

_subsManager = new ObserverSubscriptionManager<IChat>();
await base.OnActivateAsync();
}


public async Task Subscribe(IChat observer)
{
_subsManager.Subscribe(observer);
}


public async Task UnSubscribe(IChat observer)
{
_SubsManager.Unsubscribe(observer);
}
}
1
2
3
4
5
public Task SendUpdateMessage(string message)
{
_SubsManager.Notify(s => s.ReceiveMessage(message));
return TaskDone.Done;
}
1
2
3
4
5
6
7
8

var friend = GrainClient.GrainFactory.GetGrain<IHello>(0);
Chat c = new Chat();


var obj = await GrainClient.GrainFactory.CreateObjectReference<IChat>(c);

await friend.Subscribe(obj);

常见问题:

微软是否支持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失败,会发生什么?

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

你会收到一个异常,您可以捕获和重试或做任何事情在你的应用程序逻辑中有意义的。奥尔良运行时没有立即重新创建从一个失败的筒仓谷物因为他们很多人可能并不需要立即或在所有。相反,运行时单独再现这种谷物,只有当一个新的请求到达特定的粮食。为每一粒它选择其中一个可用的筒仓作为一个新的主机。这种方法的好处是,只能对谷物的实际使用执行恢复过程,它传播的时空,跨越所有可用筒仓,从而提高了系统的响应和恢复的速度。还要注意是筒仓失败时的时间之间有延迟和奥尔良群集时检测到故障。延迟时间是检测的可配置速度和误报的可能性之间的折衷。在此过渡期间对粮食的所有调用会都失败,但后失败的检测粮食将会创建后一个新的呼吁,另一个筒仓,, 所以才会最终可用。可以找到更多的信息在这里

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

由于奥尔良使用合作多任务模型,它将不会自动抢占一粒执行但奥尔良为生成警告长执行粮食调用以便您可以检测到它们。合作多任务中有很多更好的吞吐量相比,抢占式多任务。你应该牢记那粒电话不应该执行任何长时间运行的任务,像 IO 同步和不应阻止对要完成的其他任务。所有的等待,应该是使用异步等待关键字或其他异步等待机制。谷物应该尽快返回,让其他谷物执行的最大吞吐量。

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

这可以永远不会发生在正常操作过程中,每一粒会有且仅有一个实例每 id。唯一的一次发生这种情况是当筒仓崩溃时或如果被杀了不允许正确关机。在这种情况下,还有一粒可以存在多个筒仓中就有一个的窗口 30-60 秒 (根据配置) 是从系统中删除。从粮食 Id 到其激活 (实例) 地址的映射存储在分布式目录 (采用 DHT) 和每个筒仓拥有此目录分区。当会员各两个筒仓持有不同意见时,都可以请求创建的实例和作为一个结果。两个实例的粮食可能并存。一旦群集筒仓达成会员,其中一个实例将被停用,只有一个激活会生存。你可以了解更多有关如何奥尔良管理集群在群集管理页面。此外你可以看看奥尔良的更详细的信息,但是你不需要了解它完全能够编写您的应用程序代码。你只被需要考虑的在编写您的应用程序时有两个实例的演员罕见的可能性。

先决条件

Orleans 是一个.net 类库集,为了使用它,你需要.net 4.5.1 或者更高版本,开发工具集需要visual studio 2015 或者更高版本或者其他支持的开发工具,不支持Visual Studio的简化版本或者拓展包,但是你可以直接引用Orleans ,通过NuGet.

在生产环境中,Orleans 需要持久化存储,目前只支持一下技术产品之一:

Azure 就不多说了微软的云平台产品,当然就是要花钱买微软的云平台、云数据库了

SQL Server 

Zookeeper 这又是个什么东东呢,说起它那就得提到在大数据存储、大数据运算下大名鼎鼎的Hadoop 了,它是属于Hadoop项目下的一个子项目,用来存储数据的,具体详细你就问度娘吧。

MySQL 一个数据库

Consul 这是什么,不会吧,我孤陋寡闻了,从来没听说过这么高达上的东西,好吧,问度娘:

Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更”一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。使用起来也较 为简单。Consul用Golang实现,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合。

注意:以上产品使用我们会在后续章节中用示例的方式给大家展示,就期待吧。

Orleans Tools for Visual Studio

说起来也多亏visual studio 这个集大成的开发工具,做的那么贴心,犹如毒品,难以戒掉:

正如一如既往的vs Orleans 也给我们提供了模版化的项目创建,我们只需要安装这个vs 插件,然后就可以在vs 中看到Orleans的项目模版了,

下载地址 Orleans Tools for Visual Studio

然后就是安装了,如果成功安装好了,启动vs 就可以看到如下结构

如上图所示:1 是安装好后Orleans 的一个模版集节点,选中后左边面板中会有三个项目模版。

2 是我们项目中一个服务的承载项目,这里是用来测试用的,所以是一个控制台应用程序,同时也将host所需的类库nuget进来了。

针对你项目的需要,可以是一个windows 服务,一个控制台应用,一个Windows forms 的程序,或者也可以寄宿到iis的web中。

3 是grain的实现 ,我们大部分时间就是与里面的东西打交道,它里面是一些实现了grain借口,或者业务类的一系列类集合。

4 是grain 接口定义处,为何要孤立出来呢,因为将来这些接口是要暴露出来让其他需要的地方调用吗。

好了,就介绍到这里吧,你可以去试试。

ETG.Orleans.Templates.VSIX

这个也是vs 插件,就不多啰嗦了,直接下载安装就ok

Orleans NuGet Packages

orleans(1.2.2) 包

在大多数情况下,有4个关键的NuGet包你需要使用:

Microsoft.Orleans.OrleansCodeGenerator.Build

1
PM> Install-Package Microsoft.Orleans.OrleansCodeGenerator.Build 

Microsoft.Orleans.Core

1
PM> Install-Package Microsoft.Orleans.Core

Microsoft.Orleans.Server

1
PM> Install-Package Microsoft.Orleans.Server

Microsoft.Orleans.Client

1
PM> Install-Package Microsoft.Orleans.Client

Microsoft.Orleans.OrleansServiceBus

1
PM> Install-Package Microsoft.Orleans.OrleansServiceBus

Microsoft.Orleans.OrleansHost

1
PM> Install-Package Microsoft.Orleans.OrleansHost

Microsoft.Orleans.OrleansAzureUtils

1
PM> Install-Package Microsoft.Orleans.OrleansAzureUtils

Microsoft.Orleans.OrleansProviders

1
PM> Install-Package Microsoft.Orleans.OrleansProviders

Microsoft.Orleans.CounterControl

1
PM> Install-Package Microsoft.Orleans.CounterControl

microsoft.orleans.server。

Microsoft.Orleans.OrleansManager

1
PM> Install-Package Microsoft.Orleans.OrleansManager

Microsoft.Orleans.OrleansConsulUtils

1
PM> Install-Package Microsoft.Orleans.OrleansConsulUtils

Microsoft.Orleans.OrleansZooKeeperUtils

1
PM> Install-Package Microsoft.Orleans.OrleansZooKeeperUtils

使用ZooKeeper为集群成员数据存储提供的一个插件

Microsoft.Orleans.TestingHost

1
PM> Install-Package Microsoft.Orleans.TestingHost

Microsoft.Orleans.OrleansCodeGenerator

1
PM> Install-Package Microsoft.Orleans.OrleansCodeGenerator

Microsoft.Orleans.OrleansTelemetryConsumers.AI

1
PM> Install-Package Microsoft.Orleans.OrleansTelemetryConsumers.AI

Microsoft.Orleans.OrleansTelemetryConsumers.NewRelic

1
PM> Install-Package Microsoft.Orleans.OrleansTelemetryConsumers.NewRelic

Microsoft.Orleans.Serialization.Bond

1
PM> Install-Package Microsoft.Orleans.Serialization.Bond

Bond 是一个扩展框架,用来处理系统化数据,特别适合用来处理与大数据存储和处理服务的通讯。

Bond 定义了一个丰富的类型系统和 schema 版本化规则,允许向前向后兼容。核心特性包括高性能序列化和反序列化,非常强大的通用数据传输机制。该框架是高可扩展性的,通过可插入式的序列化协议、数据流和用户定义的类型别名等。

此外 Bond 是语言和平台独立的,当前支持 C++、C# 和 Python 语言。

namespace Examples

{

using Bond;

using Bond.Protocols;

using Bond.IO.Safe;

class Program

{

static void Main()

{

var src = new Example

{

Name = "FooBar"``,

Constants = { 3.14, 6.28 }

};

var output = new OutputBuffer();

var writer = new CompactBinaryWriter<OutputBuffer>(output);

Serialize.To(writer, src);

var input = new InputBuffer(output.Data);

var reader = new CompactBinaryReader<InputBuffer>(input);

var dst = Deserialize<Example>.From(reader);

}

}

}

开发一个Grain

在开发Grain之前请先阅读Grains 这篇文章

Grain 接口

Grains通过调用各自定义在接口中的方法相互作用,一个grain实现了事先定义好的一个或多个接口,grain接口中的方法必须返回Task(如果没有返回值) 或者Task(如果有类型返回值)

如下事例样本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

public interface IPlayerGrain : IGrainWithGuidKey
{
Task<IGameGrain> GetCurrentGame();
Task JoinGame(IGameGrain game);
Task LeaveGame(IGameGrain game);
}


public class PlayerGrain : Grain, IPlayerGrain
{
private IGameGrain currentGame;


public Task<IGameGrain> GetCurrentGame()
{
return Task.FromResult(currentGame);
}


public Task JoinGame(IGameGrain game)
{
currentGame = game;
Console.WriteLine("Player {0} joined game {1}", this.GetPrimaryKey(), game.GetPrimaryKey());
return TaskDone.Done;
}


public Task LeaveGame(IGameGrain game)
{
currentGame = null;
Console.WriteLine("Player {0} left game {1}", this.GetPrimaryKey(), game.GetPrimaryKey());
return TaskDone.Done;
}
}
1
2
  
IPlayerGrain player = GrainClient.GrainFactory.GetGrain<IPlayerGrain>(playerId);
1
2
 
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
1
2
3
4
5
6

Task joinGameTask = player.JoinGame(this);

await joinGameTask;

players.Add(playerId);
1
2
3
4
5
6
7
8
List<Task> tasks = new List<Task>();
ChirperMessage chirp = CreateNewChirpMessage(text);
foreach (IChirperSubscriber subscriber in Followers.Values)
{
tasks.Add(subscriber.NewChirpAsync(chirp));
}
Task joinedTask = Task.WhenAll(tasks);
await joinedTask;

TaskDone.Done Utility Property

有没有一个标准的方式返回一个void的返回值呢,答案是肯定的,Orleans 为我们定义一个助手类:TaskDone.Done

客户端开发

一旦我们定义好一个grain的接口并且有相应的实现类,我们就可以开始编写我们的客户端代码,引入相应的dll

  • Orleans.dll
  • OrleansRuntimeInterfaces.dll

几乎任何一个客户端都会涉及到grain 工厂方法的使用,使用这个方法通过一个特殊的Id来引用,正如已经提到的grain不能被显式创建或者销毁。

1
2
3
4
5
6
7
8
9
10
GrainClient.Initialize();


Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = GrainClient.GrainFactory.GetGrain<IPlayerGrain>(playerId);

IGameGrain game = player.CurrentGame.Result;
var watcher = new GameObserver();
var observer = GrainClient.GrainFactory.CreateObjectReference<IGameObserver>(watcher);
await game.SubscribeForGameUpdates();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
namespace PlayerWatcher
{
class Program
{





static void Main(string[] args)
{
try
{
GrainClient.Initialize();


Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = GrainClient.GrainFactory.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = null;

while (game == null)
{
Console.WriteLine("Getting current game for player {0}...", playerId);

try
{
game = player.CurrentGame.Result;
if (game == null)
Thread.Sleep(5000);
}
catch (Exception exc)
{
Console.WriteLine("Exception: ", exc.GetBaseException());
}
}

Console.WriteLine("Subscribing to updates for game {0}...", game.GetPrimaryKey());


var watcher = new GameObserver();
game.SubscribeForGameUpdates(GrainClient.GrainFactory.CreateObjectReference<IGameObserver>(watcher)).Wait();



Console.WriteLine("Subscribed successfully. Press <Enter> to stop.");
Console.ReadLine();
}
catch (Exception exc)
{
Console.WriteLine("Unexpected Error: {0}", exc.GetBaseException());
}
}





private class GameObserver : IGameObserver
{

public void UpdateGameScore(string score)
{
Console.WriteLine("New game score: {0}", score);
}
}
}
}

运行应用程序
配置连接到Orleans
允许应用程序与外界交互的orleans grains,框架包括客户端库。此客户端程序库可能由桌面或移动应用程序使用,或由呈现交互式网页或公开Web服务接口的前端服务器使用。
客户端库提供了一个API编写异步客户端于orleans交互。一旦客户端库连接到orleans的网关,客户端可以发送邮件到grains,接收响应,通过观察者得到grain异步通知。

连接网关
建立一个连接,客户端通过调用GrainClient.Initialize(),他将连接到silo在配置文件(ClientConfiguration.xml )中指定的ip和端口,这个文件必须和
Orleans.dll放在同一个目录下,作为一种替代,你也可以通过编程的方式加载一个配置文件然后通过GrainClient.Initialize() 初始化来使用。
配置客户端
在客户端配置文件中(ClientConfiguration.xml),指定网关端点的ip和端口,它需要去匹配silo中配置文件(OrleansConfiguration.xml )中指定的网关

1
2
3
<ClientConfiguration xmlns="urn:orleans">
<Gateway Address="<IP address or host name of silo>" Port="30000" />
</ClientConfiguration>
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<OrleansConfiguration xmlns="urn:orleans">
<Defaults>
<Networking Address="" Port="11111" />
<ProxyingGateway Address="" Port="30000" />
</Defaults>
</OrleansConfiguration>

grain 持久化
grain持久化目标
1.允许不同的grain类型使用不同的存储类型providers(一个用Azure 表,一个用SQL Azure )相同类型的存储提供程序,但具有不同的配置(两个使用Azure表,但一个使用存储帐户)。
2.允许存储提供程序实例配置可交换(例如,开发-测试-产品)只是配置文件的变化。
3.允许额外的存储供应商被写入后提供了一个框架,由orleans团队或其他人。
4.提供一个最小的生产级存储提供程序。
5.存储提供程序完全控制了在持久化支持存储中的数据状态数据的存储方式。
推论:Orleans没有提供一个全面的ORM的存储解决方案,但允在必要的时候许自定义存储提供程序支持指定一个ORM的要求。
grain 持久化API
Grain类型可以用两种方式中的一种声明:
1.拓展Grain 如果他们没有任何持久的状态,或如果他们将处理所有自己的持久状态,或者
2.Extend Grain<T> 如果他们有一些持久态,他们希望Orleans良运行时处理。 另外,通过拓展Grain grain类型自动选择Orleans框架的系统管理的持久化。

对于本节的其他部分,我们只会考虑选择 #2 / Grain<T> ,因为条件限制。Grain 状态存储
从Grain继承的grain类(其中T是从GrainState自动导出的存储数据类型)将从指定的存储中自动加载它们的状态。

Grain将标有[StorageProvider]特性指定用于读/写Grain状态的数据存储提供程序实例的命名。

1
2
3
4
5
[StorageProvider(ProviderName="store1")]
public class MyGrain<MyGrainState> ...
{
...
}
1
2
3
4
5
6
7
8
9
<OrleansConfiguration xmlns="urn:orleans">
<Globals>
<StorageProviders>
<Provider Type="Orleans.Storage.MemoryStorage" Name="DevStore" />
<Provider Type="Orleans.Storage.AzureTableStorage" Name="store1"
DataConnectionString="DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1" />
<Provider Type="Orleans.Storage.AzureBlobStorage" Name="store2"
DataConnectionString="DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2" />
</StorageProviders>

配置存储providers

AzureTableStorage

1
2
<Provider Type="Orleans.Storage.AzureTableStorage" Name="TableStore"
DataConnectionString="UseDevelopmentStorage=true" />
  • DataConnectionString="..." (强制) - Azure 存储连接字符串。
  • TableName="OrleansGrainState" (可选) - 表存储中使用的表名称,默认OrleansGrainState
  • DeleteStateOnClear="false" (可选) - 如果是true,当清除grain时记录被删除,否则将写入一个空记录,默认为false
  • UseJsonFormat="false" (可选) - 如果为true, 使用json序列化, 使用Orleans的二进制序列化, 默认 false
  • UseFullAssemblyNames="false" (可选) - (如果 UseJsonFormat="true") 如果为true则使用程序集全名,否则使用简单名, 默认为 false
  • IndentJSON="false" (可选) - (如果 UseJsonFormat="true") 如果为true压缩json序列化, 默认false

注:表存储限制,state不应该超过64kb

AzureBlobStorage

1
2
<Provider Type="Orleans.Storage.AzureTableStorage" Name="BlobStore"
DataConnectionString="UseDevelopmentStorage=true" />
  • DataConnectionString="..." (强制) - Azure 存储连接字符串。
  • ContainerName="grainstate" (可选) - 使用blob 存储容器, 默认 grainstate
  • UseFullAssemblyNames="false" (可选) - (如果 UseJsonFormat="true") 如果为true则使用程序集全名,否则使用简单名, 默认为 false
  • IndentJSON="false" (可选) - 如果为true压缩json序列化, 默认false

MemoryStorage

1
<Provider Type="Orleans.Storage.MemoryStorage" Name="MemoryStorage"  />
  • NumStorageGrains="10" (可选) -用于存储”状态“的grain数, 默认 10

ShardedStorageProvider

1
2
3
4
5
<Provider Type="Orleans.Storage.ShardedStorageProvider" Name="ShardedStorage">
<Provider />
<Provider />
<Provider />
</Provider>

任何写操作期间Orleans运行时将以grain状态数据对象的深层副本供自己使用。在重写的情况下,运行时可以使用优化规则和启发式,以避免在某些情况下,在某些情况下执行一些或所有的深拷贝,提供预期的逻辑隔离语义被保留。

Grain状态的读写操作的示例代码

为了加入Orleans的grain状态持久性机制,必须拓展Grain类。
上述定义中的T将被此grain的特定应用程序特定的grain状态类所取代;见下面的示例。
grain类也应该有一个[ storageprovider ]特性告诉运行时存储provider(实例)使用这种类型的grain。

1
2
3
4
5
6
7
8
9
10
11
public interface MyGrainState : GrainState
{
public int Field1 { get; set; }
public string Field2 { get; set; }
}

[StorageProvider(ProviderName="store1")]
public class MyPersistenceGrain : Grain<MyGrainState>, IMyPersistenceGrain
{
...
}

Grain State Read

在grain的OnActivateAsync()方法调用之前,grain 的读取状态将会自动发生,没有应用程序代码使使这种情况发生是必需的。要查看这个状态,可以通过grain 类的Grain.State属性。

Grain State Write

在对grain的内存状态作出适当的改变,grain应该调用base.writestateasync()方法将更改写入持久存储,通过定义在存储provider的grain。此方法是异步的,并返回一个Task。

1
2
3
4
5
public Task DoWrite(int val)
{
State.Field1 = val;
return base.WriteStateAsync();
}

Grain State Refresh

如果一个grain希望显式地重新从provider读取这个grain的最新状态,grain 应该调用base.ReadStateAsync() 方法,这将重新加载grain的状态从持久存储里,通过定义的存储provider的grain类型,在grain状态存储器复制以往任何将被覆盖和取代base.readstateasync()任务完成时。

1
2
3
4
5
public async Task<int> DoRead()
{
await base.ReadStateAsync();
return State.Field1;
}

Storage Provider Framework

有编写额外的持久化provider的服务提供程序接口 – IStorageProvider。持久性提供程序API包括读和写操作grainstate数据。

1
2
3
4
5
6
7
8
9
public interface IStorageProvider
{
Logger Log { get; }
Task Init();
Task Close();

Task ReadStateAsync(string grainType, GrainId grainId, GrainState grainState);
Task WriteStateAsync(string grainType, GrainId grainId, GrainState grainState);
}

Storage Provider Semantics

任何试图执行写操作时,存储provider检测ETag违反约束导致写的Task被终端,就使用Orleans.InconsistentStateException包装底层存储异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InconsistentStateException : AggregateException
{

public string StoredEtag { get; private set; }

public string CurrentEtag { get; private set; }

public InconsistentStateException(
string errorMsg,
string storedEtag,
string currentEtag,
Exception storageException
) : base(errorMsg, storageException)
{
this.StoredEtag = storedEtag;
this.CurrentEtag = currentEtag;
}

public InconsistentStateException(string storedEtag, string currentEtag, Exception storageException)
: this(storageException.Message, storedEtag, currentEtag, storageException)
{ }
}

Data Mapping

个人存储provider必须决定如何最好地储存grain的状态–BLOB(各种格式/序列化格式)或列每场是显而易见的选择。

对于table的基本存储provider编码状态数据字段到使用orlenas二进制序列化单表列。

Application Bootstrapping within a Silo

当silos上线在应用程序需要运行一些“自动执行”功能的几种情况。

我们现在已经增加了支持这种自动运行功能通过配置“orleans silo provider”。例如:

1
2
3
4
5
6
7
8
<OrleansConfiguration xmlns="urn:orleans">
<Globals>
<BootstrapProviders>
<Provider Type="My.App.BootstrapClass1" Name="bootstrap1" />
<Provider Type="My.App.BootstrapClass2" Name="bootstrap2" />
</BootstrapProviders>
</Globals>
</OrleansConfiguration>
1
2
3
public void RegisterBootstrapProvider(string providerTypeFullName, string providerName, IDictionary<string, string> properties = null)

public void RegisterBootstrapProvider<T>(string providerName, IDictionary<string, string> properties = null) where T : IBootstrapProvider
1
2
3
4
Task Init(
string name,
IProviderRuntime providerRuntime,
IProviderConfiguration config)

Timers

描述:定时器是用来创建周期性grain的行为,不需要跨多个激活的grain实例。它本质上是一组.NET System.Threading.Timer 类,另外它是受单线程执行保护的grain激活。

每个激活可能有与它相关联的零或更多的计时器,运行时在激活的运行时上下文中执行每个定时程序。

用法:开始一个定时器,要使用 Grain.RegisterTimer 方法, 返回一个实现 IDisposable 引用的实例

1
protected IDisposable RegisterTimer(Func<object, Task> asyncCallback, object state, TimeSpan dueTime, TimeSpan period)
1
state  当执行
  • When activation collection is enabled, the execution of a timer callback does not change the activation’s state from idle to in use. This means that a timer cannot be used to postpone deactivation of otherwise idle activations.
  • The period passed to Grain.RegisterTimer is the amount of time that passes from the moment the Task returned by asyncCallback is resolved to the moment that the next invocation of asyncCallback should occur. This not only makes it impossible for successive calls to asyncCallback to overlap but also makes it so that the length of time asyncCallback takes to complete affects the frequency at whichasyncCallback is invoked. This is an important deviation from the semantics of System.Threading.Timer.
  • Each invocation of asyncCallback is delivered to an activation on a separate turn and will never run concurrently with other turns on the same activation. Note however, asyncCallback invocations are not delivered as messages and are thus not subject to message interleaving semantics. This means that invocations of asyncCallback should be considered to behave as if running on a reentrant grain with respect to other messages to that grain.

Reminders

提醒类似于定时器,但是有几个重要的区别

描述:

使用:

  • 提醒持续存在,并将继续触发在所有情况下(包括部分或全部集群重新启动)除非明确取消。
  • 提醒与一个grain,没有任何特定的激活.
  • If a grain has no activation associated with it and a reminder ticks, one will be created. e.g.: If an activation becomes idle and is deactivated, a reminder associated with the same grain will reactivate the grain when it ticks next.
  • Reminders are delivered by message and are subject to the same interleaving semantics as all other grain methods.
  • 提醒不应该被用于高频定时器,它们的周期应该在几分钟,几小时或几天内测量。

Configuration

 提醒,是持久的,依赖于存储到功能。您必须指定在提醒子系统功能之前使用的存储支持。提醒功能是通过在服务器端配置的systemstore元控制。它与Azure Table 或 SQL Server 存储一起协作。

1
2
<SystemStore SystemStoreType="AzureTable" /> OR
<SystemStore SystemStoreType="SqlServer" />
1
<ReminderService ReminderServiceType="ReminderTableGrain"/>
1
2
3
4
5
Task IRemindable.ReceiveReminder(string reminderName, TickStatus status)
{
Console.WriteLine("Thanks for reminding me-- I almost forgot!");
return TaskDone.Done;
}
1
protected Task<IOrleansReminder> RegisterOrUpdateReminder(string reminderName, TimeSpan dueTime, TimeSpan period)
  • reminderName 是一个唯一的标识,在grain上下文范围内。
  • dueTime 指定第一次提醒多久开始执行。
  • period 指定执行间隔。

由于激活的生命周期就是grain唯一的生命周期,所以你必须明确的取消,使用Grain.UnregisterReminder:

1
protected Task UnregisterReminder(IOrleansReminder reminder)
1
protected Task<IOrleansReminder> GetReminder(string reminderName)
  • It doesn’t matter (or is desirable) that the timer ceases to function if the activation is deactivated or failures occur.
  • If the resolution of the timer is small (e.g. reasonably expressible in seconds or minutes).
  • The timer callback can be started from Grain.OnActivateAsync or when a grain method is invoked.

2.这几种情况下使用提醒功能

  • When the periodic behavior needs to survive the activation and any failures.
  • To perform infrequent tasks (e.g. reasonably expressible in minutes, hours, or days).

组合提醒和定时器
You might consider using a combination of reminders and timers to accomplish your goal.
For example, if you need a timer with a small resolution that needs to survive across activations,
you can use a reminder that runs every five minutes, whose purpose is to wake up
a grain that restarts a local timer that may have been lost due to a deactivation.

Orleans Streams

Orleans v.1.0.0添加流扩展的编程模型支持,流扩展提供了一组抽象接口api,使它的流更简单和更强大的,流扩展允许开发人员以结构化的方式写操作在一系列事件上的应用程序。

流provider的可扩展性模型使得在广泛的现有的队列技术的编程模型兼容和方便,例如: Event Hubs,ServiceBusAzure Queues, 和 Apache Kafka. 而不必要写特殊代码或运行专用程序与这样的队列进行交互。

我为什么要关心?

如果你已经了解了一些关于流处理的技术如:Event HubsKafka,Azure Stream AnalyticsApache StormApache Spark Streaming, 和Reactive Extensions (Rx)

你可能会问你为什么要关心,为什么我们需要另一个流处理系统和Actor是如何相关的流?“Why Orleans Streams?”可以回答你的问题

编程模型

下面是一些Orleans 流编程模型原则:

  1. 在Orleans 的体系里 Orleans virtual actors, Orleans 流是虚拟的,也就是说,一个流总是存在的。它不是显式创建或销毁,它永远不会失败。
  2. 流的身份ID识别,这只是逻辑名称由GUID字符串表示。
  3. Orleans 流数据的生成将允许从时间和空间上减弱它们的依赖性。这意味着流的生产者和流的消费者可能会在不同的服务器上,在不同的时间,并将承受失败。
  4. Orleans 的流是轻量级和动态的,Orleans 流运行时的设计是处理高速大数据。
  5. Orleans 流是动态绑定的,Orleans 流运行时的设计处理的情况下,grain连接和断开流在一个较高的速度。
  6. Orleans 流运行时透明地管理流消费的生命周期。当应用程序订阅一个流,然后它将接收流的事件,即使是在出现故障。
  7. Orleans 流均匀分布在grain和 工作的客户端。

编程APIs

应用程序流访问APIs类似于众所周知的 Reactive Extensions (Rx) in .NET,通过使用 Orleans.Streams.IAsyncStream<T> 实现了Orleans.Streams.IAsyncObserver<T> 和 Orleans.Streams.IAsyncObservable<T> 的接口。
在下面的一个典型例子产生的一些数据,这是一个HTTP请求的服务在云中运行的请求。Orleans客户端运行在前端服务器接收HTTP调用和发布数据流匹配装置:

1
2
3
4
5
6
7
public async Task OnHttpCall(DeviceEvent deviceEvent)
{

IStreamProvider streamProvider = GrainClient.GetStreamProvider("myStreamProvider");
IAsyncStream<DeviceEventData> deviceStream = streamProvider.GetStream<DeviceEventData>(deviceEvent.DeviceId);
await chatStream.OnNextAsync(deviceEvent.Data);
}
1
2
3
4
5
6
7
8
9
public class ChatUser: Grain
{
public async Task JoinChat(string chatGroupName)
{
IStreamProvider streamProvider = base.GetStreamProvider("myStreamProvider");
IAsyncStream<string> chatStream = streamProvider.GetStream<string>(chatGroupName);
await chatStream.SubscribeAsync((string chatEvent) => Console.Out.Write(chatEvent));
}
}

Stream Providers

流可以通过各种形状和形式的物理信道,可以有不同的语义。

Orleans 流的设计是支持多种Stream Providers的概念,这是系统中的一个可拓展点,Orleans 目前提供两种Stream providers。

基本的Tcp 通信Simple Message Stream Provider和云队列Azure Queue Stream Provider,你可以在这里(Stream Providers)找到更消息的介绍

Stream 意义

Stream Subsription Semantics: Orleans Streams guarantee Sequential Consistency for Stream Subsription operations. Specificaly, when consumer subscribes to a stream, once the Task representing the subsription operation was successfuly resolved, the consumer will see all events that were generated after it has subscribed. In addition, Rewindable streams allow to subscribe from an arbitrary point in time in the past by using StreamSequenceToken (more details can be found here).

Individual Stream Events Delivery Guarantees: Individual event delivery guarantees depend on individual stream providers. Some provide only best-effort at-most-once delivery (such as Simple Message Streams), while others provide at-least-once delivery (such as Azure Queue Streams). It is even possible to build a stream provider that will guarantee exactly-once delivery (we don’t have such a provider yet, but it is possible to build one with the extensability model).

Events Delivery Order: Event order also depends on a particular stream provider. In SMS streams, the producer explicitelly controls the order of events seen by the consumer by controlling the way it publishes them. Azure Queue streams do not guarantee FIFO order, since the underlaying Azure Queues do not guarantee order in failure cases. Applications can also control their own stream delivery ordering, by usingStreamSequenceToken.

流实施

 Orleans Streams Implementation提供了一个高层次的内部实施概述。

Streams 的可拓展性

Orleans Streams Extensibility 描述如何用新的功能扩展流。

Code Samples

更多的例子:here.
更多的资料:

调试符号

在开发期间Orleans调试比较简单,直接附加进程,但是对于在生产环境来说,就无法断点调试了,采用跟踪是最好的办法。

标记(Symbols):

Symbols for Orleans binaries are published to https://nuget.smbsrc.net symbols server. Add it to the list of symbols server in the Visual Studio options under Debugging/Symbols for debugging Orleans code. Make sure there is traling slash in the URL. Visual Studio 2015 has a bug with parsing it.

Orleans之Hello World

接触Orleans 有一段时间了,之前也翻译了一系列官网文档,今天我们就来一个实际的例子,来看看到底如何用这个东西来开发项目,当然经典的也是醉人的,我们就从HelloWorld开始吧。

通过前面的知识准备我们知道Orleans 项目需要n个服务端(就是silohost),n个客户端(就是调用方),然后就是提供的actors(在Orleans 中成为grain),废话少说。

首先建立一个解决方案,叫做OrleansSamples

然后,增加一个模块解决方案,叫做HelloWorlds,在解决方案下增加两个类库Sample.Interfaces,Sample.Implements,其中Sample.Implements引用Sample.Interfaces,

这两个项目中引用Orleans的核心库,你可以手动一个一个引用进来,但还是老老实实的用nuget吧。

nuget的引用两种方式一种,通过图形化的方式,另一种通过命令的方式,命令:Install-Package Microsoft.Orleans.Core (注:在这里可以找到所需的包http://dotnet.github.io/orleans/NuGets)

记得引用完后如果在nuget里有更新就更新一下,对于新版本,可能里面有些库会没有进来,否则就会报错,反正我是这么做,做完这一切,项目结构如下:

在Sample.Interfaces中增加一个接口IUserService,并且继承接口IGrainWithIntegerKey(关于这个接口有姊妹接口,关于这些接口后续会陆续讲到)

代码如下:

复制代码

复制代码

namespace Sample.Interfaces
{
  public interface IUserService:IGrainWithIntegerKey
  {
    Task Exist(string mobileNumber);
  }
}

复制代码

复制代码

在Sample.Implements项目中增加实现类UserService,并且继承Grain(Grain这个基类同时也提供了相应的泛型实现Grain<>,关于他们的不同点,以及功能,后续会讲到),且实现IUserService接口

代码如下:

复制代码

复制代码

namespace Sample.Implements
{
  public class UserService : Grain, IUserService
  {
    public Task Exist(string mobileNumber)
    {
      return Task.FromResult(mobileNumber==”18612478956”);
    }
  }
}

复制代码

复制代码

好了到此为止,我们已经开发好actor虽然简单,接下来我们接着增加服务启动寄宿项(关于寄宿项,可以是控制台、windows服务、winfrom、asp.net ),这里我们采用控制台,下面我们创建一个服务控制台应用程序(Server)

引用上面创建两个项目:Sample.Implements、Sample.Interfaces。(注:其实这两个项目不一定要引用进来,只要在生成项目的目录下存在他们的编译好的dll即可,silo用来自动加载启动这个他们)

引用orleans项目中服务端的类库(使用nuget命令:Install-Package Microsoft.Orleans.Server

项目结构如下:

代码如下:

复制代码

复制代码

namespace Server
{
  class Program
  {
    static void Main(string[] args)
    {
      using (var host = new SiloHost(“Default”))
      {
        host.InitializeOrleansSilo();
        host.StartOrleansSilo();
        Console.WriteLine(“启动成功!”);
        Console.ReadLine();
        host.StopOrleansSilo();
      }
    }
  }
}

复制代码

复制代码

好一切准备就绪,我们F5吧,

当你看到这得时候是不是觉得成功了,真的成功了吗,不一定吧!

哦对了怎么没看到日志呢,好我们在项目目录下看看日志:

果然有日志文件,一看文件名称,直接告诉我们发生错误了,好让我们打开看看吧。

从发生的异常看出,好像少了一个配置文件,在orleans 服务启动时,需要一个配置文件,这个配置文件可以是OrleansConfiguration.xml或者orleans.config或者orleans.config.xml

好知道原因了,知道该怎么做了吧,在server根目录下创建一个xml文件OrleansConfiguration.xml,将该文件的属性“复制到输出目录”值更改为”如果较新则复制”

该文件中填充配置内容,如下(详细配置请看配置一节,此处不解释)

复制代码

复制代码

View Code

再次启动F5,当看到启动成功的输出时,我们再次看看生成的日志:

这次发现比之前生成的东西多了,但是当我们继续往下浏览的时候发现有个异常:

Exc level 0: System.IO.FileNotFoundException: 未能加载文件或程序集“Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60”或它的某一个依赖项。系统找不到指定的文件。
在 Orleans.Runtime.Startup.ConfigureStartupBuilder.ConfigureStartup(String startupTypeName)
在 Orleans.Runtime.Startup.ConfigureStartupBuilder.Orleans.Runtime.Startup.IStartupBuilder.ConfigureStartup(String startupTypeName)
在 Orleans.Runtime.Silo..ctor(String name, SiloType siloType, ClusterConfiguration config, ILocalDataStore keyStore)

原来是少了程序集:Microsoft.Extensions.DependencyInjection

知道原因了,我们通过nuget来引用这个类库,引用成功,再次运行,然后查看日志,异常消失,但是有个问题,每次打开日志文件要看,是否有错误,或者一些关于服务的监控内容,这样是不是很麻烦,其实我们可以更改一下配置信息,让它输出到控制台,这样在开发过程中就方便多了,可以时时看到动态信息,如下:

打开OrleansConfiguration.xml 文件找到<Tracing DefaultTraceLevel=”Info” TraceToConsole=”false” TraceToFile=”{0}-{1}.log” 这个节点,将TraceToConsole的值更改为true保存,再次运行,如下:

好了一切都完美了,接下来我们在继续开发客户端。

在解决方案下创建一个控制台应用程序Client,引用客户端相关类库:Install-Package Microsoft.Orleans.Client

引用项目:Sample.Interfaces

代码如下:

复制代码

namespace Client
{
class Program
{
static void Main(string[] args)
{
System.Threading.Thread.Sleep(15000);
GrainClient.Initialize();

        while (true)
        {
            Console.WriteLine("请输入用户手机号:");
            var mobileNumber = Console.ReadLine();
            //这里由于我们采用的grain继承的是IGrainWithIntegerKey ,所以我们采用调用数值类型的key=10来创建这个grain,
            //可能有人会问key是干嘛的,他是唯一标识这个grain的,当你指定一个key的时候,Orleans 会创建一个,它首先到
            //你的存储介质中找(如果你配置了的话,默认采用内存存储,这种方式适合开发期,生产环境需要保持状态的,所以需要配置到能持久化存储的地方去,比如sqlserver等)
            //如果找到了就直接返回,如果没找到就根据你指定的这个key然后创建一个,这个就是grain的激活,具体详细的,可以看官方问的关于Grain一章。
            var userService = GrainClient.GrainFactory.GetGrain<IUserService>(10);
            //C#的一种新的表达式语法,这样就方便多了,省的我们拼接字符串。
            Console.WriteLine($"用户{mobileNumber},{(userService.Exist(mobileNumber).Result?"已经存在":"不存在")}");
        }

    }
}

}

复制代码

View Code

在client项目下记得要创建配置文件,文件名称叫做ClientConfiguration.xml

内容如下:

复制代码

复制代码

View Code

注:要记得更改文件属性哦

一切准备就绪,下来让改一下启动方式为多项目启动,然后就F5等待飞吧!

终于看到胜利的果实了,哈哈!接下来我们接着说说另外一种开发方式以及发布方式。

上面的这种开发方式为了说明开发的那些具体步骤,需要引用那些类库,以及客户端如何去调用,步骤比较麻烦,尤其是服务端的开发、引用类库,也没有相应的单元测试,接下来我们看看另外一种服务端的开发方式。

跟上面的大体步骤一样

1.创建接口类库

2.创建实现类库

3.开发测试服务寄宿程序

在开始之前首先要确认一下你是否安装了Orleans的vs模版插件,如果安装了那么如下图:

如果没有安装,赶紧去下载一个吧地址在:http://dotnet.github.io/orleans/NuGets

找到这一节,如下图:

点开里面有你想要的插件,然后安装重启vs

1.创建接口类库

2.创建实现类库

 

3.创建服务寄宿程序

服务创建完之后,发现下面有自动生成的一个类OrleansHostWrapper,并且在Program下自动生成了很多代码,代码的大体意思就是,将服务端启动程序的逻辑封装在OrleansHostWrapper,然后启动是单独创建一个应用程序域,增加一些测试例子代码,方便多了吧,我们不需要写任何服务端的服务启动代码,在实际开发过程中我们只需要关心业务模块

即接口创建,接口实现,方便多了吧。

将相应的代码贴入进去,还记得上面出现的那个异常吗,记得要将Microsoft.Extensions.DependencyInjection类库引用进来哦,F5吧。

一切如预期所料,成功!

接下来我们创建一个单元测试程序库,方便服务端的程序单元测试Sample.Test

引用项目Sample.Implements、Sample.Interfaces

1
引用Orleans测试包 PM> Install-Package Microsoft.Orleans.TestingHost

复制代码

[ClassCleanup]
public static void ClassCleanup()
{
// Optional.
// By default, the next test class which uses TestignSiloHost will
// cause a fresh Orleans silo environment to be created.
StopAllSilosIfRunning();
}

复制代码

View Code

测试代码如下:

复制代码

namespace Sample.Test
{
[TestClass]
public class UserServiceTest: TestingSiloHost
{
[ClassCleanup]
public static void ClassCleanup()
{
// Optional.
// By default, the next test class which uses TestignSiloHost will
// cause a fresh Orleans silo environment to be created.
StopAllSilosIfRunning();
}

    \[TestMethod\]
    public async void TestExist()
    {
        var grain = GrainFactory.GetGrain<IUserService>(10);
        bool bo = await grain.Exist("18612478956");
        Assert.IsTrue(bo);
    }
}

}

复制代码

View Code

记得增加两个配置文件,

ClientConfigurationForTesting.xml

复制代码

复制代码

View Code

OrleansConfigurationForTesting.xml

复制代码

复制代码

View Code

更改xml文件属性为:如果较新则复制

测试项目结构大体如下:

好,测试编写完成,如何操作你懂得,这里就不废话了。

 接下来说一下部署,部署吧,各有各的妙招,控制台、winform、windows服务等等,这里我说一个框架自带的一个部署控制台怎么用

记得Orleans 里面有这么一个程序OrleansHost.exe,他是干什么用的呢,对了就是用来部署的。

我们来看看他的源码,弄清楚他到底是做了一件什么事情

如下图:

打开这个文件,可以发现这个文件跟上面我们通过模版创建的server中的文件OrleansHostWrapper很相似,对了,这个就是为了保持开发部署的一致性,所以这个就可以用来直接部署了

我们在server的Debug下找到相应的程序,将程序复制到某个盘符比如D:\demo下面

如下图:

然后将配置文件拷贝进来OrleansConfiguration.xml

一切准备就绪,我们运行把,双击StartOrleans.cmd服务启动

运行成功.
你会发现这个服务部署于我们的开发互不影响,当我们开发好一个grain的时候,直接编译丢到这个部署目录下,别的地方就可以访问了,可以让我们重点关注业务逻辑,而不需要关心那些复杂的配置或者服务的开启关闭等等。

实例代码

 Orleans 之 监控工具的使用

这一节,我们来说说orleans 中的几个实用工具,OrleansHost、OrleansCounterControl、OrleansManager、ClientGenerator。

1.OrleansHost

这个工具是一个主机寄宿或者部署的一个控制台应用程序,下面我们看一下他的用法。

从那里获取呢,直接点的办法就是在源码包里找到这个项目然后编译后得到的就是你需要的。

另外一种就是当你创建服务端然后安装了Microsoft.Orleans.Server包的时候,编译这个项目也会在编译包下生成出来。

我是直接编译源码包得到,如下:

一些dll,值得注意的是那个Configuration包,里面有个文件OrleansConfiguration.xsd,这个就是服务端文件配置所需的xsd验证文件,如果我们不知道配置文件需要那些节点的时候

可以查看该文件,可以将其复制到你的vs安装目录的xml环境中,就可以获得智能提示了,如下:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\Xml\Schemas

我的安装目录,如果你的安装目录有变,你可以灵活调整了

看图

然后我们在OrleansHost.exe所在目录下创建OrleansConfiguration.xml文件用vs打开

添加根节点OrleansConfiguration,然后增加xmlns=”urn:orleans”,我们就可以得到智能提示了,如下图

接下来,我们配置一个基本的配置文件,内容如下

复制代码

复制代码

View Code

好我们来运行一下这个看看,双击StartOrleans.cmd,程序跑完,有日志文件生成,大概浏览一遍,不太顺畅,有一些错误,少了一些dll的引用,少了OrleansCodeGenerator.dll,OrleansDependencyInjection.dll,再到源码包中直接生成该项目,然后将其生成产物拷贝到StartOrleans目录下,再次运行,一切如预料的那样,成功:

这里我整理的一个OrleansHost的部署包:OrleansHost

如有需要的可以直接下载使用。

再来看看他的一些使用的参数:

该工具有如下参数:/? 、/help、-?、-help、/debug、deploymentid=[value]、deploymentgroup=[value]。

这次我们先冲控制台进入该程序所在目录:

cmd-> d: -> cd D:\demo\OrleansHost  ,如下:

如果我们什么参数都不带,改程序就直接启动

如果下面我们带上如上参数看看效果:OrleansHost /? 这个是寻求帮助的一个命令

 OrleansHost /debug   这个是以调试环境启动

OrleansHost  deploymentid=[value] 这个是给部署的当前silo起一个部署时的名称然后启动(这里的value将来是设置为siloname的)

OrleansHost  deploymentgroup=[value]这个值暂时还没有启用,说不定后续版本会有用处

有个了这个工具我们直接将开发好的grain编译好的程序发布到该程序目录下就可以了,然后重启,不信你可以试一试,哈哈!

2.OrleansCounterControl

在于OrleansHost相同的目录下还有一个exe,就是OrleansCounterControl

如下图:

我们先用cmd进入该程序所在目录下

cd C:\Demo

接下来,我们先看看OrleansCounterControl有哪些参数

OrleansCounterControl /r,/register、/u、/unregister、/f、/force、/pause、/?、/help,我们来看看这些参数命令。

以管理员运行,如果OrleansCounterControl不带任何参数的时候,将运行服务端的一些序列化初始化,并输出一些控制台日志,且注册windows计数器

/r  或/register windows计数器注册

/u 或/unregister 取消windows计数器

/f 或 /force 删除计数器

/pause  是否有退出时提示

/? 或/help 帮助提示.

这些命令可以组合使用,如下图:OrleansCounterControl  /r  /pause

OrleansCounterControl  /r  /?

总结:这个工具用来进行管理程序的windows计数器管理工具.

3.OrleansManager

命令行定位到该程序目录下(此工具主要是用来在客户端,或者别的地方查看服务端上silo或者grain的一些状态信息。)

接下来看看命令参数:

OrleansManager [ ] [/?] [-?] 执行这个命令时会有一些帮助提示

OrleansManager grainstats [silo1,silo2,…] 用来查看统计查看silo的address、激活的数量、grain类型等。

             fullgrainstats [silo1,silo2,…] 查看全部的grain的状态

         collect  [silo1,silo2,…]  

        unregister [silo1,silo2,…] 取消注册

         lookup  [silo1,silo2,…]

        grainreport [silo1,silo2,…] 

具体就不再这里讲解了,大家在开发过程中可以去探索。

4.ClientGenerator

先来看看工具传入的参数

参数要用【””】 来包装字符串,字符串内的参数可以用多个用【;】来分割,每一段代表一个路径,绝对路径,或者网络服务地址。

具体参数使用如下:

/r:[path];[path];[path];[path]
/reference:[path];[path];[path];[path]    这个可以使用的是一个目录或者是一个文本文件内指定多个文件。

/in:[path] 如果是单个文件使用,如a.cs,或者b.cs 。

/bootstrap  
/boot

/sources:
/src:

就说到这里吧。