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
表中了。如下图所示:
推荐链接:你必须知道的ML.NET开发指南
推荐链接:你必须知道的Office开发指南
推荐链接:你必须知道的IOT开发指南
推荐链接:你必须知道的Azure基础知识
推荐链接:你必须知道的PowerBI基础知识

关注我的公众号『微服务知多少』,我们微信不见不散。
阅罢此文,如果您觉得本文不错并有所收获,请【打赏】或【推荐】,也可【评论】留下您的问题或建议与我交流。 你的支持是我不断创作和分享的不竭动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
2017-02-10 Asp.net mvc 知多少(六)