Orleans例子再進一步
這是Orleans系列文章中的一篇.首篇文章在此
步驟
現在我想再添加一個方法,到IGrains項目內,這個方法里面有個延遲3秒,然后返回一個Task<string>.就叫做DelayedMsg吧,如下圖所示:
我調用了這個DelayedMsg,同時又調用了SayHello函數,看看效果:注意這個DelayedMsg的調用方法沒有await.
雖然我的SayHello的調用時間緊隨着DelayedMsg之后,
可是發現SayHello仍然延遲了3秒才出來,這是因為SayHello必須等待DelayedMsg方法執行完畢.如圖:
這說明一件事情,就是grain接受消息是異步的。而且Grain處理消息是“單線程約束”的。
如果有兩個Grain實例,一個調用DelayMsg,而另一個調用SayHello,可以發現此時的SayHello會立馬執行,如圖:
這又說明兩個不同的Grain實例是在不同的線程執行的。
解釋一下
好了,初步的例子就展現到此,現在來解釋一下下…
1.往Grain發送消息,實質上是調用它的接口,但是不論何時,不論何地,哪怕是不同的計算機,調用具有相同標識的Grain里的任何一個方法,這些方法都會在單一的線程中被執行。所以如果Grain有個list類型的私有字段,在操作的這個字段的時候就無需架鎖。因為list根本不會出現“臟讀”。所以在Orleans的世界里,消息的處理可以做到全程無鎖。
2.Grain的標識:Orleans使用以下類型作為Grain的標識:Guid,Long,String,以及組合標識。在面向對象的環境中,做到嚴格區分一個實例和一個實例的引用,並不是很容易,有時候很容易混淆,就算混淆了對程序也不會有太大影響.在分布式的環境里想要用一個對象的引用來代表這個對象,這是做不到的.因為對象的引用只有在一定的地址空間內才有效,而在分布式的環境下,系統有可能分布在多台機器上.
這就要求用一個新的東西來代表Grain.這就是使用標識的原因.使用標識時,Orleans要求主標識對於一個特定的grain類來說,要唯一.因此一個grain的完整標識必須時由它的類型和它的主標識組成.在實際使用的過程中,很少去關注grain的完整標識,在記錄日志的時候,完整標識也許有點用.大部分時候只需要知道主標識就可以了,Orleans提供了幾個方法,可以調用它們得到grain實例的主標識:在Grain類的內部,可以通過調用this. GetPrimaryKeyXXX()得到本實例的標識.而在Grain的外部,則只能通過指定主標識來往對應的對象發送消息,比如以下語句:
GrainClient.GrainFactory.GetGrain<IExample>(1) ;
簡單一句話,Orleans通過標識來區分grain的所有實例.要想使用Grain必須通過GetGrain方法得到一個對應實例的引用,再通過調用此引用的方法往對應對象發送消息.
組合標識對應的可以繼承一個IGrainWithGuidCompoundKey或者IGrainWithIntegerCompoundKey,如下所示.
public interface IExampleGrain : Orleans.IGrainWithIntegerCompoundKey { Task Hello(); }
在客戶端,你可以在GrainFactory.GetGrain方法中設置第二個參數.
var grain = GrainClient.GrainFactory.GetGrain<IExample>(0, "a string!");
為了在grain內部獲得主標識,我們需要調用GetPrimaryKey 的方法.
public class ExampleGrain : Orleans.Grain, IExampleGrain { public Task Hello() { string keyExtension; long primaryKey = this.GetPrimaryKey(out keyExtension); Console.WriteLine("Hello from " + keyExtension); return TaskDone.Done; } }
3.Grain是虛擬的Actor---.Orleans的Actor模型更進一步,它是”虛擬Actor”的模型.任何時候都可以直接使用GetGrain方法得到一個Grain的引用.就好像對應的Grain對象早就創建好了,等待被人引用一樣.這種表現背后的工作都由silo來完成,當你調用GetGrain<IgrainA>方法后,意圖得到grainA類的一個實例的引用,silo會在服務端的內存中通過復制來激活一個grainA的實例.這個復制內存的過程在Orleans中稱之為激活(activation),所以每一個grain類都可以重寫一個方法就是OnActivationAsyc.在這個方法里可以實現類似於初始化的動作.
激活一個Grain實例之后,如果長時間沒有新的消息到達此實例,這個Grain也許會被反激活,進入休眠狀態,這時候可以通過重寫OnDeactivateAsync來干預反激活過程,大部分應該是些掃尾的工作.
不用顯式的創建和銷毀一個grain對象.關於虛擬Actor的進一步討論,會在下一篇中繼續進行.
4.Grain內部如果想要發送消息給其他的Grain.必須使用類內部的GetGrain<>方法,而不能通過GrainClient.GrainFactory.GetGrain<>方法.
5.Grain可以有字段,這些字段可以是公共的,也可以是靜態的.但是如果這樣設計,使用者必須小心處理.
其實在Orleans的世界里,公共字段沒有太大的用處,因為你要想得到Grain的實例,必須通過GetGrain方法,而此方法返回的引用里並沒有字段,而只有方法(因為是接口)…
靜態字段也許有用,比如有人想要用一個靜態字段來保存所有活動的GrainA類實例,一個辦法就是使用靜態字段.但是這是一個麻煩的辦法,因為此字段有可能”臟讀”.要想有效的控制此字段的讀寫,就要架鎖..這破壞了很多Orleans帶來的好處..,簡單的辦法就是再創造一個grainB類型,讓它的一個實例來統一管理所有的GrainA實例.這樣就還是一個純潔的Orleans世界.
好了,這樣簡單Orleans完全體就說完了.可是這個例子還是太過於簡單了.雖然體現了Orleans的系統的主要方面,但是並不是所有的關鍵方面.仍然是一個類似於WCF程序或者類似於RPC的框架.吃瓜群眾的c#理論知識還是很豐富的.
在orleans框架里,Grain就是對應的理論模型的actor,所以很多語境下,actor和grain是相同的.這個必須要強調下.
為了進一步破壞吃瓜群眾的陰毛..我決定弄一個稍微復雜的例子,讓Orleans擺脫這些帽子.