Orleans稍微复杂的例子—互动

时间:2022-08-25 11:05:19

  这是Orleans系列文章中的一篇.首篇文章在此

  我费力费心的翻译过官方的教程,但是本人英语词汇量不高,可是架不住电子词典啊…只要肯花时间,我这些内容谁都可以做出来.所以这个事例告诉我们一个道理,那就是码农有三好,钱多话少死得早.我也许只有后两好.

当初阿尔法狗在围棋上战胜人类的时候,人工智能一时大热,不管老小,都大肆谈论一番深度神经网络学习算法等等,我当时也是一时兴起去拿出我崭新的书本,说起来都是泪,大学并没有好好学习,都玩游戏了…不过不管如何,数学系毕业的,底子好点,翻看完必要的知识后,就去看深度神经网络算法,去了解它们的原理,看到最后,我发现,这不就是一种全分类算法吗?顿时觉得高大上的深度学习网络没有那么神秘了.经常看到这样的图片:

Orleans稍微复杂的例子—互动

  我们拿过来用,借用微软的话:一个服务本质上是多个可通信actor的集合,用上面的图片好像正合适.

Grain类是轻量级的,为了更好的设计,现实中往往使用grain类来代表颗粒度很细的东西.比如一个商城,可以用GrainA来代表店铺,用GrainB来代表商品…商铺管理商品,所以GrainB就会和GrainA之间有互动,在系统中,对每个商品进行单独控制,就需要很多的GrainB的实例,这在Orleans中很容易做到,更难得的是,可以对GrainB或者GrainA发生的事件进行记录,实现eventsourcing. Actor的建模方式,使得eventsourcing显得很自然,也很容易处理.

  让多个Grain互动起来才有更多的可能性,一个服务本质上是多个可通信actor的集合.所以在这个例子中,我们创造多个Grain实例,让它们彼此之间有联系.我们使用”现实中的经理于员工的关系”这样一个背景模型来构造这个例子.

步骤

我把它们添加到basic项目里,接口如下图

Orleans稍微复杂的例子—互动

还是跟以前一样,定义接口.这里注意这些接口只有方法,尽量避免使用属性.虽然现实当中一个经理同时也是一个员工,但是这里的经理接口并没有继承自员工接口.这样做是基于以下考虑:这两个接口都要被扩展成Grain类,以后对员工和经理的状态值进行持久化存储的时候,”非继承”的两个grain类会带来一些方便.稍微说一下持久化:意思就是对Grain类的各个字段进行持久化存储(存到数据库或者文件都可以),方便下一次Grain激活的时候读取各个字段的值.

好了,不管如何,我就是这么勤快的人,直接写了两个接口,而不是选择继承.现在我勤快的实现这两个Grain类.

如下图:

Orleans稍微复杂的例子—互动

大部分基础工作做完了,我们再client调用它们.

Orleans稍微复杂的例子—互动

运行之后的截图如下:

Orleans稍微复杂的例子—互动

上个例子虽然正确的运行了,但是隐藏一个小坑,真正正确的操作应该如下图

Orleans稍微复杂的例子—互动

这是因为Grain的方法都是异步的.如果不添加await,在一些耗时的操作中也许会出现先后顺序的错误.不过对于这个例子,添加不添加与否,并不影响结果.

解释一下

  以前说过,Orleans是基于actor模型构建的,但是Orleans把actor模型做到了极致,它是虚拟的actor模型.一个grain的生命周期,是从一个激活开始,到一个反激活结束.它既然是”激活”而不是构造,这就要求grain类不应该有构造函数,即便是没有参数的构造函数.一个grain从来不是    从构建到销毁,而是处于激活或者非激活两个状态. 可以在OnActivateAsync()中控制Graini的激活行为,实现类似于”构造”的过程Orleans保证这个方法会在所有其他方法之前调用.在上一个例子中就展现了如何使用这个方法.

  在多线程的编程中,最讨厌的两个事情是死锁和错误处理.有时候,在开发的时候测试一百次都不会死锁,但是到部署到客户哪里,一次就给你死锁.这一般是因为测试的环境简单化了,没有考虑到客户环境的并发量以及并发的时间点.更因为这两个因素很多时候根本无法考虑.

  消息死锁

  Orleans编程可以做到全程无锁,它同时使得多线程编程逻辑上更容易处理,错误更容易捕捉.但是这不代表不需要设计.为了展现一下,我再此修改员工和经理.这次我们设计一个问候消息如下:

public class GreetingData
{
public Guid From { get; set; }
public string Message { get; set; }
public int Count { get; set; }
}

  我设想,新员工加入的时候经理给一个问候,同时新员工回答thanks,这样来来回回的消息,就像两个乒乓球机器人一样对发对打.加上一个count,本意是想着来回的次数不要超过3次.不然就进入了死循环了.

修改IEmployee 中的Greeting方法的参数.

Task Greeting(GreetingData data);

修改对接口的实现.

public async Task Greeting(GreetingData data)
{
Console.WriteLine("{0} said: {1}", data.From, data.Message); // stop this from repeating endlessly
if (data.Count >= ) return; // send a message back to the sender
var fromGrain = GrainFactory.GetGrain<IEmployee>(data.From);
await fromGrain.Greeting(new GreetingData
From = this.GetPrimaryKey(),
Message = "Thanks!",
Count = data.Count + });
}

也更新Manager类,这样它就会发送一个新的消息.

public async Task AddDirectReport(IEmployee employee)
{
_reports.Add(employee);
await employee.SetManager(this);
await employee.Greeting(new GreetingData {
From = this.GetPrimaryKey(),
Message = "Welcome to my team!" });
}

现在经理说”Welcome to my team!" ,员工应该回复一个Thanks.

  如果运行这个程序,就造成了一个死锁,这是因为经理发送消息给员工,并等待回应,员工回答Thanks,并等待经理回应.问题就在于员工的消息永远得不到回答,因为经理正在忙着等待它第一个消息完成呢,在经理那里,员工的回答必须排队等待.这就是一个死锁!!同样的还有死循环.在Actor编程中死循环往往容易处理(在任何编程中都容易处理),因为它容易定位.但是死锁就稍微困难点.

  Orleans提供了日志,可以记录并提示可能的死锁,它一般用WARNING 体现出来,结尾是About to break its promise. 注意在服务器繁忙的时候,网络不好的时候,都有可能出现这个警告.

  为了 应对这样的死锁,Orleans提供了一个特性[Reentrant],它可以标志一个Grain,使得这个类在处理其他消息的时候,稍微富有”弹性”,可以同时接受其他新消息的到达,(只是”到达”这个步骤”弹性”化,”处理消息”这个步骤依然是刚性的”单线程约束”).使用方法很简单.如下:

[Reentrant]
public class Employee : Grain, IEmployee
{
...
}

处理建议

  Orleans系统分为客户端和服务端.对于网站来说,Orleans客户端通常是asp或者其他的http框架,而Orleans服务端通常是数据库操作端等等.在一个中大型的Orleans系统中,多重Actor互相关联,最好是自上而下的设计,遵循好消息流或者数据流的流动方向,让它们的流动可以分支,但是小心处理回流,(需要回流的地方请小心设计,不过这种情况也不会很多,如果很多,请检查自己的数据设计是否合理).

  在用一个Grain去管理多个Grain类的想法中,还有一点要注意的是,尽量不要产生过于繁忙的Grain实例.由于Grain实例是”单线程机制”的,一个实例操作过于繁忙会影响效率.上例中,如果一个经理直接下属有一百万个人.那么这样设计的程序过热点就是这个经理.此时需要分散过热点.一个简单的办法是按照员工主标识hash表分段,每一段分给一个经理…此时组合主标识就会派上用场.

  吃瓜群众应该觉得此处应该有总结:

接口项目就是平时说的”消息协议”.发送消息就是调用接口.在方法内部可以不用锁而随意读写字段.避免死锁和过热的grain实例.

  上例中设计了单独的问候类,在.net世界里,在作为参数传递和使用的时候,.net是传递实例的地址.但是这种机制在并发和多线程的情况下,并不能很好的工作.在Orleans世界里,作为消息的参数(即Grain方法的参数),传递的时候默认都是对类实例进行一个深度拷贝,在跨机器的情况下,这是很有必要的.但是如果消息发送的目的地就是在本地silo里,如果还是需要进行深度拷贝,就显得多余和浪费了.Orleans提供了一个特性[immutable],用它来标明一个类一旦建立就不会再对它进行修改.如果消息目的地是本地silo,使用[immutable]标明的类作为参数时,Orleans就会跳过对此参数的深度拷贝,而是只传递一个引用.使用非常简单.如下:

[Immutable]
public class GreetingData
{
public Guid From { get; set; }
public string Message { get; set; }
public int Count { get; set; }
}

好了,第二个例子就说完了.有点Orleans实际项目的影子了,但是还是不够…

似乎应该更进一步,那就是存储,把Grain的状态值保存起来,下一次激活的时候,恢复它的状态值,这样才能符合现实需要.可以尝试在OnActivateAsync里控制读取动作,在OnDeactivateAsync中控制存储动作.不过微软提供了更为通用的存储中间件(StorageProvider),使得这些工作变得容易.所以完全没有必要自己另外实现一个(当然你愿意也行).要想使用这些存储中间件,就必须配置一下.可以使用配置文件,也可以在代码里配置.所以持久化就按下暂且不表,我来说说配置.

Orleans稍微复杂的例子—互动的更多相关文章

  1. Orleans例子再进一步

    Orleans例子再进一步 这是Orleans系列文章中的一篇.首篇文章在此 步骤 现在我想再添加一个方法,到IGrains项目内,这个方法里面有个延迟3秒,然后返回一个Task<string& ...

  2. DataTables学习:从最基本的入门静态页面,使用ajax调用Json本地数据源实现前端开发深入学习,根据后台数据接口替换掉本地的json本地数据,以及报错的处理地方,8个例子(显示行附加信息,回调使用api,动态显示和隐藏列&period;&period;&period;),详细教程

    一.DataTables  个人觉得学习一门新的插件或者技术时候,官方文档是最根本的,入门最快的地方,但是有时候看完官方文档,一步步的动手写例子,总会出现各种莫名其妙的错误,需要我们很好的进行研究出错 ...

  3. ASP&period;NET Core 中文文档 第二章 指南(4&period;2)添加 Controller

    原文:Adding a controller 翻译:娄宇(Lyrics) 校对:刘怡(AlexLEWIS).何镇汐.夏申斌.孟帅洋(书缘) Model-View-Controller (MVC) 架构 ...

  4. 从随机过程到马尔科夫链蒙特卡洛方法(MCMC)

    从随机过程到马尔科夫链蒙特卡洛方法 1. Introduction 第一次接触到 Markov Chain Monte Carlo (MCMC) 是在 theano 的 deep learning t ...

  5. Make 命令教程

    http://www.ruanyifeng.com/blog/2015/02/make.html 作者: 阮一峰 日期: 2015年2月20日 代码变成可执行文件,叫做编译(compile):先编译这 ...

  6. &lbrack;Web API&rsqb; Web API 2 深入系列&lpar;4&rpar; Action的选择

    目录 ApiController HttpActionDescriptor IHttpActionSelector ApiController 在上节中,讲到如何选择并激活对应的IHttpContro ...

  7. C&num;中正则表达式在replace中的应用!

    多少年来,许多的编程语言和工具都包含对正则表达式的支持,.NET基础类库中包含有一个名字空间和一系列可以充分发挥规则表达式威力的类,而且它们也都与未来的Perl 5中的规则表达式兼容.     此外, ...

  8. 【转载】调试利器 autoexp&period;dat

    转载:http://www.cppblog.com/flyinghare/archive/2010/09/27/127836.html autoexp.dat入门(调试时自定义变量显示) VC在调试状 ...

  9. ETL利器Kettle实战应用解析系列二 【应用场景和实战DEMO下载】

    本文主要阅读目录如下: 1.应用场景 2.DEMO实战 3.DEMO下载 1.应用场景 这里简单概括一下几种具体的应用场景,按网络环境划分主要包括: 表视图模式:这种情况我们经常遇到,就是在同一网络环 ...

随机推荐

  1. Cannot retrieve definition for form bean null on action错误

    Cannot retrieve definition for form bean null on action错误 1. 如果jsp页面中要用到<html:form action="& ...

  2. 什么是Ajax&quest; (转载于疯狂客的BLOG)

    Ajax的定义 Ajax不是一个技术,它实际上是几种技术,每种技术都有其独特这处,合在一起就成了一个功能强大的新技术. Ajax包括: XHTML和CSS,使用文档对象模型(Document Obje ...

  3. 利用反射来实现获取成员的指定特性&lpar;Attribute&rpar;信息

    在开发过程中,我们经常需要自定义一些特性,来辅助我们完成对对象或者枚举进行管理.我们需要知道如何获取对象使用的特性信息. 以下举个学习用的例子. 我们自定义一个特性类,这个特性设置在一个数据段内是否执 ...

  4. 关于eclipse新建web项目&comma;提示&colon;&quot&semi;The superclass &quot&semi;javax&period;servlet&period;http&period;HttpServlet&quot&semi; was not found on the Java&quot&semi;解决办法

    新建jsp页面老提示: Multiple annotations found at this line: - The superclass "javax.servlet.http.HttpS ...

  5. Code&colon;Blocks中文输出乱码解决方法

    0x01 问题描述 将CB的编码格式设置为UTF-8之后,在CMD窗口输出中文乱码. 0x02 解决办法 控制台显示的时候缺省的是使用系统默认的字符集,比如windows下用的是GBk,但是默认情况下 ...

  6. 提高PHP性能的一些小知识

    自PHP面世起以其良好的跨平台性,高效的开发机制有WEB领域占有很大份额.因为它的运行机制是脚本解释运行执行后相关资源都会被回收,所以PHP开发人员很少关心他的资源占用所导致性能问题,但本人是个追求极 ...

  7. PAT1113&colon; Integer Set Partition

    1113. Integer Set Partition (25) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue ...

  8. SqlServer 案例:已有汽车每日行驶里程数据,计算其每日增量

    需求说明 某公司某项业务,需要获得用户每日行车里程数.已知能获得该车每日提交的总里程数,如何通过 T-SQL 来获得其每日增量里程? 解决方案 首选需要对数据进行编号,利用开窗函数 OVER() 实现 ...

  9. Spark算子之aggregateByKey详解

    一.基本介绍 rdd.aggregateByKey(3, seqFunc, combFunc) 其中第一个函数是初始值 3代表每次分完组之后的每个组的初始值. seqFunc代表combine的聚合逻 ...

  10. 计算auc-python&sol;awk

    1.自己写的计算auc的代码,用scikit-learn的auc计算函数sklearn.metrics.auc(x, y, reorder=False)做了一些测试,结果是一样的,如有错误,欢迎指正. ...