Orleans 知多少 | 4. 有状态的Grain
引言
Orleans 的优势之一就是:支持有状态服务的水平扩展。那这一节我们就来看看如何来了解下有状态的Grain。
第一个有状态的Grain
先来看下上节中定义的Grain:SessionControlGrain
public class SessionControlGrain : Grain, ISessionControlGrain
{
private List<string> LoginUsers { get; set; } = new List<string>();
public Task Login(string userId)
{
//获取当前Grain的身份标识(因为ISessionControlGrain身份标识为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)
{
//获取当前Grain的身份标识
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>();
用来保存登录状态,其是保存在内存中的,一旦服务奔溃或重启,维护的状态数据就会丢失。
很显然,这在真实应用场景中不被允许。
在第一节中,已经对有状态和无状态有了解释,关键的区别在于:状态数据的是否持久化。因此上面针对ISessionControlGrain
的实现SessionControlGrain
是无状态的。
那接下来就来看看如何用有状态的Grain来实现!
针对统计登录用户的需求来说,其中的状态数据就是在线用户列表,所以可以直接定义一个LoginState
来将行为和数据解耦。
/// <summary>
/// 登录状态
/// </summary>
public class LoginState
{
public List<string> LoginUsers { get; set; } = new List<string>();
public int Count => LoginUsers.Count;
}
紧接着就可以重新实现一个ISessionControlGrain
,如下:
/// <summary>
/// 有状态的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)
{
//获取当前Grain的身份标识
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);
}
}
对比两个Grain的实现,有状态的Grain主要有以下变化:
- 继承自
Grain<T>
,其中T
用来指定当前Grain的附属状态对象。 - Grain中通过
this.State
来操作状态 - 通过调用
this.WriteStateAsync();
来显式持久化状态。
那Grain的状态保存到哪里去了呢?
Grain 状态仓库(Grain Storage)
持久化方式
开发环境下,可使用内存作为Grain的状态仓库。仅需在构建Orleans Silo时配置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());
}
)
存在内存中,只是为了方便开发,显然在生产环境中是万万不可的。因此,可选择其他存储介质进行持久化。比如数据库等,Orleans 官方维护的状态持久化提供者有以下几种:
- Microsoft.Orleans.Persistence.AdoNet :封装了对SQL 数据库的支持,目前支持SQL Server、MySQL/MariaDB、PostgreSQL、Oracle。可参考 ADO.NET Grain Persistence。
- Microsoft.Orleans.Persistence.AzureStorage:封装了对Azure 存储介质的支持,比如 Azure Blob Storage, Azure Table Storage, 以及 Azure CosmosDB。 可参考 Azure Storage Grain Persistence。
- Microsoft.Orleans.Persistence.DynamoDB :封装了对 Amazon DynamoDB 的支持。可参考Amazon DynamoDB Grain Persistence。
当然除此之外,社区也维护系列开源项目支持将状态数据持久化到其他介质。
接下来就来讲解如何持久化状态数据到SQL Server 数据库。
持久化到 SQL Server
SqlServer的配置并没有想象的那样简单,根据官方文档: Configuring ADO.NET Providers、 ADO.NET Database Configuration,你会发现需要执行以下几步:
- Orleans Server 端添加对
Microsoft.Orleans.Persistence.AdoNet
NuGet包的引用 - 添加SQL Server 客户端驱动
System.Data.SqlClient
NuGet包的引用 - 创建SQL Server数据库,可使用VS 自带的localdb。
- 依次执行以下脚本,SQLServer-Main.sql、SQLServer-Persistence.sql 创建用于存储相关状态表。
- 添加配置代码
为了简化配置,我做了一个简单的包装项目Orleans.AdoNet.Extensions,以简化SqlServer、MySql、Oracle和PostgreSql 的配置。以Sql Server 为例,仅需:
- 通过Nuget包管理器安装
Orleans.AdoNet.SqlServer
包 - 安装后会打开一个readme.txt,复杂全部,并执行到数据库
- 服务端添加以下配置即可。
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;
});
重新运行项目,查询数据库,你会发现状态数据,实际上是持久化到Storage
表中了。如下图所示: