Orleans在默认情况下只创建一个grain的实例,并以单线程模型执行。如果同一个grain实例,在Orleans存在多个实例,就会产生并发冲突,单线程执行模型就可以完全避免并发冲突了。
但在特殊场景下,有些实例是需要创建多个实例或者以非单线程的执行方式来满足性能的需要;
如何支持创建多个实例
对于了解负载均衡的人,如果web服务器支持无状态(分布式Sesson或者cookie身份识别),会很容易做负载。同样的,对于grain来说,如果是无状态的,那么在系统中创建任意多的实例都是一样的,不存在状态不同步的问题。
那么如何在Orleans支持这样的grain呢?
Orleans提供了[StatelessWorker]的Attribute,标记为StatelessWorker的,Orleans会自动调整该grain的实例数量来满足系统的需要。而对于标记为StatelessWorker的Grain,一般只是根据参数调用其它的grain行为,自身没有也不需要保持任何状态。
当标记为StatelessWorker的Grain实例数量不足以应对当前系统的处理请求时,Orleans会自动在cluster中的其它Silo(grain的宿主容器)创建新的Grain实例,用以满足系统的处理需求。当系统热点过去后,如果该Grain一直闲置,是对系统资源的一种浪费,Orleans会自动释放这些闲置的Grain占用的资源。这种方式很像云计算中的弹性云计算的方式。
如何支持非单线程执行模式
分布式应用程序的本质是并行,但是并行带来了更多的复杂性。有两个方式可以降低这种复杂性
1.对Actor实例的内部状态以单线程方式访问
2.Actor之间不共享任何数据,仅仅通过消息进行交互
单线程的数据访问,避免了数据征用,大大降低了分布式应用的复杂性。所以Orleans默认是单线程的执行模式。但这种执行模式,也带来了死锁发生的可能性。
比如A发消息给B,等待B的响应
B收到消息后,又发消息给A,B开始等待A的响应
A由于正在等待之前发给B消息的响应,而无法处理B新发来的消息
A和B之间相互等待,产生了死锁。
而对于A和B之间2次消息并不会产生并发冲突,对于这样的,Orleans提供一种方式,允许grain以非单线程的模型执行。 [Reentrant] Attribute,标记了这个属性的Grain允许多次进入,但此处并非脱离了grain的单线程执行模型。graincode仍然运行在单线程模型下,只是允许请求交错执行。
标记了[Reentrant]之后,我们在看之前A B的示例。
A发消息给B,等待B的响应
B收到消息后,又发消息给A,B开始等待A的响应
A因为标记了[Reentrant] ,可以接受并处理B发来的消息,A处理完毕后发回响应给B
B收到响应后,完成自己的处理过程,返回响应给A,完成整个调用