orleans —————— 為什么有這個框架 [ 一]
前言
簡單說明一下,為什么有orleans 這個框架。
正文
orleans 這個框架的理論基礎是 actor, 在1973年提出,當初是為了大量處理高并發計算機的并行模型,其核心思想是將系統中獨立的計算過程抽象為actor。
高并發場景有什么問題呢,那就是同時修改一個對象的時候,那么會出現線程不安全的情況。
actor 模型是怎么做的呢? actor 模型做法是同一時刻僅響應一個外部的請求。
當actor 模型同時受到多個外部的請求的時候,通過一定的規則(通常是先入先出)對外部請求進行排序一次執行。
有人覺得這樣可能會很慢,那么如果有很多個actor模型呢? 那么其實這個壓力就被平坦了。
如果每個都是actor模型的話,那么就有一個問題,actor之間是否能通信呢? 怎么通信?
actor 之間也是可以通信的,對于這一點來說,跟外部訪問actor 沒有什么區別。
那么orleans 就是為了實現這種思想的。
在orleans 中,actor模型的基礎響應單位為grain。
每一個grain 由服務(service),狀態(State) 及標識(identtiy)組成。
grain 示例:
那么grain 是怎么作為標識的呢?
當程序使用guid 進行grain 尋址的時候,n0 和 n1 分布由guid 字節數組的0-7 和 8-15 位組成。
采用長整數尋址的時候,n1 為地址位,n0 為0 位。
這個typecodeData 是什么呢? 是最低為4個字節的標識及其所標識對象的類別:
類別如下:
IGrainWithGuidKey:帶有 Guid 鍵的 grain 標記接口。
IGrainWithIntegerKey:帶有 Int64 鍵的 grain 標記接口。
IGrainWithStringKey:帶有 string 鍵的 grain 標記接口。
IGrainWithGuidCompoundKey:帶有組合鍵的 grain 標記接口。
IGrainWithIntegerCompoundKey:帶有組合鍵的 grain 標記接口。
那個keyExt 就是我們可以額外擴展的字段。
var n0 = BitConverter.ToUInt64(new Guid(guididentity).ToByteArray(), 0);
var n1 = BitConverter.ToUInt64(new Guid(guididentity).ToByteArray(), 8);
var identitiy = string.Format("GrainReference={0:x16}{1:x16}{2}+{3}", n0, n1, "06ffffffe23dcf33", ln)
ln 是 keyext。
06ffffffe23dcf33 這個固定的是什么呢? 06 表示標識符。 后面的ffffffe23dcf33 表示了服務的標識。
grain 通過接口調用來定義接口的標識類型:
調用方式:
那么grain的喚醒和休眠是什么樣的呢?
因為系統的資源有限,不可能在內存中加載全部的grain。
在orleans 應用程序的內部,grain 示例對象實際會自動在休眠狀態(persisted) 和 活躍狀態 (volatile) 間進行切換,
在空閑狀態下釋放對系統資源的占用。
在休眠狀態的grain實例對象不占用任何運行時資源(cpu和內存),其內部狀態由orleans 運行時通過存儲服務進行存儲。
當orleans 應用程序首次啟動時,其內部所以的grain實例都默認處于休眠態。
Grain對象的休眠過程僅由Orleans運行時觸發:當Orleans運行時監測到活躍態Grain實例的閑置時間超過閾值時,Orleans運行時將自動保存其當前狀態并對其所占用的運行時資源進行回收。
這里就有一個問題,有一些比較熱門的grain,如果監測到非活躍就回收了,那么可以多次激活的情況。
那么可以這樣:
Task<string> IHello.SayHello(string greeting)
{
TimeSpan timeSpan = TimeSpan.FromSeconds(500);
DelayDeactivation(timeSpan);
logger.LogInformation($"SayHello message received: greeting = '{greeting}'");
return Task.FromResult($"You said: '{greeting}', I say: Hello!");
}
使用DelayDeactivation 延遲一下回收時間。
如果呢,你想馬上回收,怎么處理呢?
public async Task<string> NoThing()
{
DeactivateOnIdle();
return string.Empty;
}
這樣可以讓其盡快回收,下一次gc 就會回收掉。
這里值得注意的是:DelayDeactivation(timeSpan); 這個timespan 可以設置為負數,如果為負數表示取消。
在orleans 中,狀態也是有幾種類型的。
比如繼承: IpersistentStat
grain 基類中可以重載:
IPersistentState
這里非常值得注意一點: grain 的初始化過程是先于onactivateasync 函數調用的,因此storageprovider 中任何讀取錯誤都將直接導致grain 實例激活失敗。
在此值和orleans 運行時并不會繼續調用onactivateasync ,會由運行時拋出Orleans.BadProviderConfigException異常
對于狀態寫入場景而言,StorageProvider中的任何錯誤也都將由WriteStateAsync方法拋出,開發人員在業務邏輯中需要對依賴WriteStateAsync方法的邏輯分支進行單獨的異常處理。
IGrainStorage接口的所有API參數列表中都接受一個類型為IGrainState的輸入參數,而IGrainState接口中定義了一個字符串類型的屬性ETag,該字段在IPersistentState<T>接口中是可見的,實際上ETag是存儲服務在并發讀寫場景下用以區分狀態版本的“數據版本標識位”字段:當需要對外部存儲狀態進行刷新時,可以將狀態的ETag指定為上一次讀取該狀態時獲得的ETag值,若該ETag與當前實際存儲的ETag值相同,則證明在上一次狀態讀取到當前時刻外部存儲的狀態值沒有發生變化,可以進行直接狀態的刷新;若ETag不相符,則表明在此段時間內有其他邏輯對外部存儲的狀態進行了更新,此次狀態更新操作面臨數據不一致的情況(類似于CPU并發場景下的CAS操作);若業務場景中需要跳過外部狀態的版本校驗邏輯,則可以在調用外部狀態寫入API時將狀態的ETag值保留為null。而在實現IGrainStorage接口時,任何在讀寫API方法中檢測到讀寫ETag約束不一致時都需要拋出一個InconsistentStateException異常以終止該API調用并將異常信息傳遞給上層調用者。
結
下一節實操一些例子。