Orleans 解決并發(fā)之痛(二):Grain 狀態(tài)

Grains 是 Orleans 應(yīng)用程序的構(gòu)建塊,它們是彼此孤立的原子單位,分布的,持久的, 一個典型的 Grain 是有狀態(tài)和行為的一個單實(shí)例,每個 Grain 實(shí)例的在單線程內(nèi)執(zhí)行,Grain 之間共享數(shù)據(jù)通過消息傳遞,Grains 是由 Silo 自動化管理。

Grain 之間傳遞消息過程中也可能出現(xiàn)死鎖的情況,如:Grain A 發(fā)送消息給 Grain B,并等待它的完成,此時(shí) Grain B 發(fā)送一個消息給 Grain A,也等待其完成,這時(shí)候出現(xiàn)相互等待而造成死鎖。Orleans 對 Grain 之間產(chǎn)生的死鎖問題解決也是非常簡單的,只需要在 Grain上加 [Reentrant] 屬性,具體可查看官方 Concurrency

Grain 狀態(tài)有好幾種存儲方式,比如:AzureTableStorage、AzureBlobStorage、SQLStorage、MemoryStorage 等,我們還可以自定義存儲。MemoryStorage 在測試項(xiàng)目使用沒問題,但實(shí)際生產(chǎn)環(huán)境要使用其他持久存儲的方式,因?yàn)橐坏┮粋€ Silo 被關(guān)閉,內(nèi)存存儲的狀態(tài)將會消失。

在分布式下,State 的使用可以減少很多對數(shù)據(jù)庫層面的壓力。當(dāng)然也不是所有的 Grain 都推薦使用 State,還是看實(shí)際業(yè)務(wù)需求。我們可以想象一個場景,一個商品的庫存如果保存在 State 中,所有請求都共享這個 State,在判斷是否有剩余商品的時(shí)候是不是就不需要每次都去查詢數(shù)據(jù)庫了?

定義接口

public interface IPersonGrain : IGrainWithStringKey
{
  Task SayHelloAsync();
}

實(shí)現(xiàn)接口

public class PersonGrain : Grain, IPersonGrain
{
  public Task SayHelloAsync()
  {
    string primaryKey = this.GetPrimaryKeyString();

    Console.WriteLine($"{primaryKey} said hello!");

    return Task.CompletedTask;
  }
}

為了實(shí)現(xiàn)狀態(tài)存儲,我們需要創(chuàng)建一個 class:

public class PersonGrainState
{
  public bool SaidHello { get; set; }
}

修改代碼,實(shí)現(xiàn)的 PersonGrain 不應(yīng)該再繼承 Grain,而是 Grain<T>

[StorageProvider(ProviderName = "OrleansStorage")]
public class PersonGrain : Grain<PersonGrainState>, IPersonGrain
{
  public async Task SayHelloAsync()
  {
    string primaryKey = this.GetPrimaryKeyString();

    bool saidHelloBefore = this.State.SaidHello;
    string saidHelloBeforeStr = saidHelloBefore ? " already" : null;

    Console.WriteLine($"{primaryKey}{saidHelloBeforeStr} said hello!");

    this.State.SaidHello = true;
    await this.WriteStateAsync();
  }
}

第一次調(diào)用這個方法的時(shí)候 this.State.SaidHello 為 false,輸出 xxx said hello! 。然后我們通過 WriteStateAsync 修改 SaidHello 為 true,當(dāng)?shù)诙伪徽{(diào)用的時(shí)候,從 State 里取出的 SaidHello 已經(jīng)變成了 true,則輸出 xxx already said hello!

Orleans 提供了非常簡單的 API 來處理持久化裝狀態(tài),看方法名就知道什么意思了,WriteStateAsync()、ReadStateAsync() 、 ClearStateAsync()。

同時(shí)在 PersonGrain 加了一個 StorageProvider 屬性,參數(shù) ProviderName 賦值為 OrleansStorage,這里需要對 Silo 的配置文件(OrleansConfiguration.xml)做調(diào)整,添加 StorageProviders 配置,Type 表示存儲方式,Name 表示名稱,程序內(nèi)指定的 ProviderName 需要和配置中這個名稱保持一致。

注意:
當(dāng) Name為Default 時(shí),如果某個 Grain 使用 Default 來存儲,可以不需要加 StorageProvider 屬性。StorageProviders 下可以有多個 Provider,每個 Provider 的 Type 可以不一樣,每個 Grain 指定的存儲方式也可以不一樣,ProviderName 指定是誰就用誰存儲。

<?xml version="1.0" encoding="utf-8" ?>
<OrleansConfiguration xmlns="urn:orleans">
  <Globals>
    <SeedNode Address="localhost" Port="11111" />
    <StorageProviders>
      <Provider Type="Orleans.Storage.MemoryStorage"
                Name="OrleansStorage" />
    </StorageProviders>
  </Globals>
  <Defaults>
    <Networking Address="localhost" Port="11111" />
    <ProxyingGateway Address="localhost" Port="30000" />
  </Defaults>
</OrleansConfiguration>

為了驗(yàn)證 Grain 之間是獨(dú)立的,在 Client 加入以下代碼:

 var joe = GrainClient.GrainFactory.GetGrain<IPersonGrain>("Joe");
joe.SayHelloAsync();
joe.SayHelloAsync();

var sam = GrainClient.GrainFactory.GetGrain<IPersonGrain>("Sam");
sam.SayHelloAsync();
sam.SayHelloAsync();

測試結(jié)果:

Test Result

SQL Server 持久存儲 State

上面提到 State 以內(nèi)存存儲的方式并不適合生產(chǎn)環(huán)境,那下面我們使用 SQL Server 來實(shí)現(xiàn)。

在 Silo 程序集中安裝依賴包:
Install-Package Microsoft.Orleans.OrleansSqlUtils
Install-Package System.Data.SqlClient
創(chuàng)建數(shù)據(jù)庫和表:
  1. 在 SQL Server 中創(chuàng)建一個數(shù)據(jù)庫,命名如:OrleansStorage(隨意);
  2. 在解決方案下找到目錄:packages\Microsoft.Orleans.OrleansSqlUtils.1.5.0\lib\net461\SQLServer,目錄下有一個 .sql 文件,在 OrleansStorage 數(shù)據(jù)庫下執(zhí)行這個 sql 腳本即可;
修改 OrleansConfiguration.xml 的 StorageProviders 節(jié)點(diǎn)為:
<StorageProviders>
  <Provider Type="Orleans.Storage.AdoNetStorageProvider"
            Name="OrleansStorage"
            AdoInvariant="System.Data.SqlClient"
            DataConnectionString="Server=.;Database=OrleansStorage;User ID=sa;Password=123456;"/>
</StorageProviders>
重新啟動 Silo 和 Client:

執(zhí)行完成后查看數(shù)據(jù)庫中表 Storage 的內(nèi)容,數(shù)據(jù)的值是二進(jìn)制是方式存儲。


storage

之后不管重啟多少次,輸出的結(jié)果都是 xxx already saild hello!

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    草里有只羊閱讀 18,529評論 0 85
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 租的房子有點(diǎn)遭,不知道該如何整理,慢慢來吧!加油吧!在未來的九個月會有美好的時(shí)光。沒有醫(yī)院的全貌照片,改天附上
    木在林夕閱讀 285評論 0 0
  • 我可以失敗,但不能慫。 前一段時(shí)間看《奇葩說》,里面有一個辯題:“我們終究會變成自己討厭的人”是不是一件壞事?當(dāng)時(shí)...
    一個90后閱讀 237評論 0 0

友情鏈接更多精彩內(nèi)容