引言
Orleans 的優(yōu)勢(shì)之一就是:支持有狀態(tài)服務(wù)的水平擴(kuò)展昙衅。那這一節(jié)我們就來(lái)看看如何來(lái)了解下有狀態(tài)的Grain。
第一個(gè)有狀態(tài)的Grain
先來(lái)看下上節(jié)中定義的Grain:SessionControlGrain
public class SessionControlGrain : Grain, ISessionControlGrain
{
private List<string> LoginUsers { get; set; } = new List<string>();
public Task Login(string userId)
{
//獲取當(dāng)前Grain的身份標(biāo)識(shí)(因?yàn)镮SessionControlGrain身份標(biāo)識(shí)為string類型粘招,GetPrimaryKeyString());
var appName = this.GetPrimaryKeyString();
LoginUsers.Add(userId);
Console.WriteLine($"Current active users count of {appName} is {LoginUsers.Count}");
return Task.CompletedTask;
}
public Task Logout(string userId)
{
//獲取當(dāng)前Grain的身份標(biāo)識(shí)
var appName = this.GetPrimaryKey();
LoginUsers.Remove(userId);
Console.WriteLine($"Current active users count of {appName} is {LoginUsers.Count}");
return Task.CompletedTask;
}
public Task<int> GetActiveUserCount()
{
return Task.FromResult(LoginUsers.Count);
}
}
上面的Grain中定義屬性private List<string> LoginUsers { get; set; } = new List<string>();
用來(lái)保存登錄狀態(tài),其是保存在內(nèi)存中的偎球,一旦服務(wù)奔潰或重啟洒扎,維護(hù)的狀態(tài)數(shù)據(jù)就會(huì)丟失。
很顯然衰絮,這在真實(shí)應(yīng)用場(chǎng)景中不被允許袍冷。
在第一節(jié)中,已經(jīng)對(duì)有狀態(tài)和無(wú)狀態(tài)有了解釋猫牡,關(guān)鍵的區(qū)別在于:狀態(tài)數(shù)據(jù)的是否持久化胡诗。因此上面針對(duì)ISessionControlGrain
的實(shí)現(xiàn)SessionControlGrain
是無(wú)狀態(tài)的。
那接下來(lái)就來(lái)看看如何用有狀態(tài)的Grain來(lái)實(shí)現(xiàn)淌友!
針對(duì)統(tǒng)計(jì)登錄用戶的需求來(lái)說(shuō)煌恢,其中的狀態(tài)數(shù)據(jù)就是在線用戶列表,所以可以直接定義一個(gè)LoginState
來(lái)將行為和數(shù)據(jù)解耦震庭。
/// <summary>
/// 登錄狀態(tài)
/// </summary>
public class LoginState
{
public List<string> LoginUsers { get; set; } = new List<string>();
public int Count => LoginUsers.Count;
}
緊接著就可以重新實(shí)現(xiàn)一個(gè)ISessionControlGrain
瑰抵,如下:
/// <summary>
/// 有狀態(tài)的Grain
/// </summary>
public class SessionControlStateGrain : Grain<LoginState>, ISessionControlGrain
{
public Task Login(string userId)
{
var appName = this.GetPrimaryKeyString();
this.State.LoginUsers.Add(userId);
this.WriteStateAsync();
Console.WriteLine($"Current active users count of {appName} is {this.State.Count}");
return Task.CompletedTask;
}
public Task Logout(string userId)
{
//獲取當(dāng)前Grain的身份標(biāo)識(shí)
var appName = this.GetPrimaryKey();
this.State.LoginUsers.Remove(userId);
this.WriteStateAsync();
Console.WriteLine($"Current active users count of {appName} is {this.State.Count}");
return Task.CompletedTask;
}
public Task<int> GetActiveUserCount()
{
return Task.FromResult(this.State.Count);
}
}
對(duì)比兩個(gè)Grain的實(shí)現(xiàn),有狀態(tài)的Grain主要有以下變化:
- 繼承自
Grain<T>
器联,其中T
用來(lái)指定當(dāng)前Grain的附屬狀態(tài)對(duì)象二汛。 - Grain中通過(guò)
this.State
來(lái)操作狀態(tài) - 通過(guò)調(diào)用
this.WriteStateAsync();
來(lái)顯式持久化狀態(tài)婿崭。
那Grain的狀態(tài)保存到哪里去了呢?
Grain 狀態(tài)倉(cāng)庫(kù)(Grain Storage)
持久化方式
開發(fā)環(huán)境下肴颊,可使用內(nèi)存作為Grain的狀態(tài)倉(cāng)庫(kù)氓栈。僅需在構(gòu)建Orleans Silo時(shí)配置AddMemoryGrainStorageAsDefault()
即可,如下所示:
return Host.CreateDefaultBuilder()
.UseOrleans((builder) =>
{
builder.UseLocalhostClustering()
.AddMemoryGrainStorageAsDefault()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "Hello.Orleans";
options.ServiceId = "Hello.Orleans";
})
.Configure<EndpointOptions>(options => options.AdvertisedIPAddress = IPAddress.Loopback)
.ConfigureApplicationParts(parts =>
parts.AddApplicationPart(typeof(ISessionControlGrain).Assembly).WithReferences());
}
)
存在內(nèi)存中婿着,只是為了方便開發(fā)颤绕,顯然在生產(chǎn)環(huán)境中是萬(wàn)萬(wàn)不可的。因此祟身,可選擇其他存儲(chǔ)介質(zhì)進(jìn)行持久化。比如數(shù)據(jù)庫(kù)等物独,Orleans 官方維護(hù)的狀態(tài)持久化提供者有以下幾種:
- Microsoft.Orleans.Persistence.AdoNet :封裝了對(duì)SQL 數(shù)據(jù)庫(kù)的支持袜硫,目前支持SQL Server、MySQL/MariaDB挡篓、PostgreSQL婉陷、Oracle」傺校可參考 ADO.NET Grain Persistence秽澳。
- Microsoft.Orleans.Persistence.AzureStorage:封裝了對(duì)Azure 存儲(chǔ)介質(zhì)的支持,比如 Azure Blob Storage, Azure Table Storage, 以及 Azure CosmosDB戏羽。 可參考 Azure Storage Grain Persistence担神。
- Microsoft.Orleans.Persistence.DynamoDB :封裝了對(duì) Amazon DynamoDB 的支持∈蓟ǎ可參考Amazon DynamoDB Grain Persistence妄讯。
當(dāng)然除此之外,社區(qū)也維護(hù)系列開源項(xiàng)目支持將狀態(tài)數(shù)據(jù)持久化到其他介質(zhì)酷宵。
接下來(lái)就來(lái)講解如何持久化狀態(tài)數(shù)據(jù)到SQL Server 數(shù)據(jù)庫(kù)亥贸。
持久化到 SQL Server
SqlServer的配置并沒(méi)有想象的那樣簡(jiǎn)單,根據(jù)官方文檔: Configuring ADO.NET Providers浇垦、 ADO.NET Database Configuration炕置,你會(huì)發(fā)現(xiàn)需要執(zhí)行以下幾步:
- Orleans Server 端添加對(duì)
Microsoft.Orleans.Persistence.AdoNet
NuGet包的引用 - 添加SQL Server 客戶端驅(qū)動(dòng)
System.Data.SqlClient
NuGet包的引用 - 創(chuàng)建SQL Server數(shù)據(jù)庫(kù),可使用VS 自帶的localdb男韧。
- 依次執(zhí)行以下腳本,SQLServer-Main.sql朴摊、SQLServer-Persistence.sql 創(chuàng)建用于存儲(chǔ)相關(guān)狀態(tài)表。
- 添加配置代碼
為了簡(jiǎn)化配置煌抒,我做了一個(gè)簡(jiǎn)單的包裝項(xiàng)目Orleans.AdoNet.Extensions仍劈,以簡(jiǎn)化SqlServer、MySql寡壮、Oracle和PostgreSql 的配置贩疙。以Sql Server 為例讹弯,僅需:
- 通過(guò)Nuget包管理器安裝
Orleans.AdoNet.SqlServer
包 - 安裝后會(huì)打開一個(gè)readme.txt,復(fù)制全部这溅,并執(zhí)行到數(shù)據(jù)庫(kù)
- 服務(wù)端添加以下配置即可组民。
Host.CreateDefaultBuilder()
.UseOrleans((builder) =>
{
var connectionString =
@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Hello.Orleans;Integrated Security=True;Pooling=False;Max Pool Size=200;MultipleActiveResultSets=True";
//use AdoNet for Persistence
builder.AddSqlServerGrainStorageAsDefault(options =>
{
options.ConnectionString = connectionString;
options.UseJsonFormat = true;
});
重新運(yùn)行項(xiàng)目,查詢數(shù)據(jù)庫(kù)悲靴,你會(huì)發(fā)現(xiàn)狀態(tài)數(shù)據(jù)臭胜,實(shí)際上是持久化到Storage
表中了。如下圖所示: