Grains 是 Orleans 應(yīng)用程序的構(gòu)建塊廉沮,它們是彼此孤立的原子單位颓遏,分布的,持久的废封, 一個(gè)典型的 Grain 是有狀態(tài)和行為的一個(gè)單實(shí)例州泊,每個(gè) Grain 實(shí)例的在單線程內(nèi)執(zhí)行,Grain 之間共享數(shù)據(jù)通過(guò)消息傳遞漂洋,Grains 是由 Silo 自動(dòng)化管理遥皂。
Grain 之間傳遞消息過(guò)程中也可能出現(xiàn)死鎖的情況,如:Grain A 發(fā)送消息給 Grain B刽漂,并等待它的完成演训,此時(shí) Grain B 發(fā)送一個(gè)消息給 Grain A,也等待其完成贝咙,這時(shí)候出現(xiàn)相互等待而造成死鎖样悟。Orleans 對(duì) Grain 之間產(chǎn)生的死鎖問(wèn)題解決也是非常簡(jiǎn)單的,只需要在 Grain上加 [Reentrant] 屬性庭猩,具體可查看官方 Concurrency窟她。
Grain 狀態(tài)有好幾種存儲(chǔ)方式,比如:AzureTableStorage蔼水、AzureBlobStorage震糖、SQLStorage、MemoryStorage 等趴腋,我們還可以自定義存儲(chǔ)吊说。MemoryStorage 在測(cè)試項(xiàng)目使用沒(méi)問(wèn)題论咏,但實(shí)際生產(chǎn)環(huán)境要使用其他持久存儲(chǔ)的方式,因?yàn)橐坏┮粋€(gè) Silo 被關(guān)閉颁井,內(nèi)存存儲(chǔ)的狀態(tài)將會(huì)消失厅贪。
在分布式下,State 的使用可以減少很多對(duì)數(shù)據(jù)庫(kù)層面的壓力雅宾。當(dāng)然也不是所有的 Grain 都推薦使用 State养涮,還是看實(shí)際業(yè)務(wù)需求。我們可以想象一個(gè)場(chǎng)景秀又,一個(gè)商品的庫(kù)存如果保存在 State 中单寂,所有請(qǐng)求都共享這個(gè) State,在判斷是否有剩余商品的時(shí)候是不是就不需要每次都去查詢數(shù)據(jù)庫(kù)了吐辙?
定義接口
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)存儲(chǔ),我們需要?jiǎng)?chuàng)建一個(gè) 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)用這個(gè)方法的時(shí)候 this.State.SaidHello 為 false,輸出 xxx said hello!
威沫。然后我們通過(guò) WriteStateAsync 修改 SaidHello 為 true贤惯,當(dāng)?shù)诙伪徽{(diào)用的時(shí)候,從 State 里取出的 SaidHello 已經(jīng)變成了 true棒掠,則輸出 xxx already said hello!
Orleans 提供了非常簡(jiǎn)單的 API 來(lái)處理持久化裝狀態(tài)孵构,看方法名就知道什么意思了,WriteStateAsync()烟很、ReadStateAsync() 颈墅、 ClearStateAsync()。
同時(shí)在 PersonGrain 加了一個(gè) StorageProvider 屬性雾袱,參數(shù) ProviderName 賦值為 OrleansStorage恤筛,這里需要對(duì) Silo 的配置文件(OrleansConfiguration.xml)做調(diào)整,添加 StorageProviders 配置芹橡,Type 表示存儲(chǔ)方式毒坛,Name 表示名稱,程序內(nèi)指定的 ProviderName 需要和配置中這個(gè)名稱保持一致林说。
注意:
當(dāng) Name為Default 時(shí)煎殷,如果某個(gè) Grain 使用 Default 來(lái)存儲(chǔ),可以不需要加 StorageProvider 屬性腿箩。StorageProviders 下可以有多個(gè) Provider豪直,每個(gè) Provider 的 Type 可以不一樣,每個(gè) Grain 指定的存儲(chǔ)方式也可以不一樣度秘,ProviderName 指定是誰(shuí)就用誰(shuí)存儲(chǔ)顶伞。
<?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();
測(cè)試結(jié)果:
SQL Server 持久存儲(chǔ) State
上面提到 State 以內(nèi)存存儲(chǔ)的方式并不適合生產(chǎn)環(huán)境,那下面我們使用 SQL Server 來(lái)實(shí)現(xiàn)唆貌。
在 Silo 程序集中安裝依賴包:
Install-Package Microsoft.Orleans.OrleansSqlUtils
Install-Package System.Data.SqlClient
創(chuàng)建數(shù)據(jù)庫(kù)和表:
- 在 SQL Server 中創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)滑潘,命名如:OrleansStorage(隨意);
- 在解決方案下找到目錄:packages\Microsoft.Orleans.OrleansSqlUtils.1.5.0\lib\net461\SQLServer锨咙,目錄下有一個(gè) .sql 文件语卤,在 OrleansStorage 數(shù)據(jù)庫(kù)下執(zhí)行這個(gè) 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>
重新啟動(dòng) Silo 和 Client:
執(zhí)行完成后查看數(shù)據(jù)庫(kù)中表 Storage 的內(nèi)容酪刀,數(shù)據(jù)的值是二進(jìn)制是方式存儲(chǔ)粹舵。
之后不管重啟多少次,輸出的結(jié)果都是 xxx already saild hello!
骂倘。