1. 引言
是的,Orleans v3.0.0 已经发布了,并已经彻底支持 .NET Core 3.0。因此,Orleans 系列是时候继续了,抱歉,让你们久等了。万丈高楼平地起,这一节咱们就先来了解下Orleans的基本使用。编程
2. 模板项目讲解
在上一篇文章中,咱们了解到Orleans 做为.NET 分布式框架,其主要包括三个部分:Client、Grains、Silo Host(Server)。所以,为了方便讲解,建立以下的项目结构进行演示:安全
这里有几点须要说明:微信
Orleans.Grains:类库项目,用于定义Grain的接口以及实现,须要引用
Microsoft.Orleans.CodeGenerator.MSBuild
和Microsoft.Orleans.Core.Abstractions
NuGet包。sessionOrleans.Server:控制台项目,为 Silo 宿主提供宿主环境,须要引用
Microsoft.Orleans.Server
和Microsoft.Extensions.Hosting
NuGet包,以及Orleans.Grains
项目。并发Orleans.Client:控制台项目,用于演示如何借助Orleans Client创建与Orleans Server的链接,须要引用
Microsoft.Orleans.Client
和Microsoft.Extensions.Hosting
NuGet包,同时添加Orleans.Grains
项目引用。app
3. 第一个Grain
Grain做为Orleans的第一公民,以及Virtual Actor的实际代言人,想吃透Orleans,那Grain就是第一道坎。先看一个简单的Demo,咱们来模拟统计网站的实时在线用户。在 Orleans.Grains
添加 ISessionControl
接口,主要用户登陆状态的管理。框架
public interface ISessionControlGrain : IGrainWithStringKey{ Task Login(string userId); Task Logout(string userId); Task<int> GetActiveUserCount();}
async
分布式
ide
能够看见Grain的定义很简单,只须要指定继承自IGrain的接口就好。这里面继承自 IGrainWithStringKey
,说明该Grain 的Identity Key(身份标识)为 string
类型。同时须要注意的是Grain 的方法申明,返回值必须是:Task、Task
SessionControlGrain
来实现 ISessionControlGrain
接口。
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的实现要继承自 Grain
基类。代码中咱们定义了一个 List<string>
集合用于保存登陆用户。
4. 第一个Silo Host(Server)
定义一个Silo用于暴露Grain提供的服务,在 Orleans.Server.Program
中添加如下代码用于启动Silo Host。
static Task Main(string[] args){ Console.Title = typeof(Program).Namespace; // define the cluster configuration 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()); } ) .ConfigureServices(services => { services.Configure<ConsoleLifetimeOptions>(options => { options.SuppressStatusMessages = true; }); }) .ConfigureLogging(builder => { builder.AddConsole(); }) .RunConsoleAsync();}
Host.CreateDefaultBuilder()
:建立泛型主机提供宿主环境。UseOrleans
:用来配置Oleans。UseLocalhostClustering()
:用于在开发环境下指定链接到本地集群。Configure<ClusterOptions>
:用于指定链接到那个集群。Configure<EndpointOptions>
:用于配置silo与silo、silo与client之间的通讯端点。开发环境下可仅指定回环地址做为集群间通讯的IP地址。ConfigureApplicationParts()
:用于指定暴露哪些Grain服务。
以上就是开发环境下,Orleans Server的基本配置。对于详细的配置也能够先参考Orleans Server Configuration。后续也会有专门的一篇文章来详解。
5. 第一个Client
客户端的定义也很简单,主要是建立 IClusterClient
对象创建于Orleans Server的链接。由于 IClusterClient
最好能在程序启动之时就创建链接,因此能够经过继承 IHostedService
来实现。在 Orleans.Client
中定义 ClusterClientHostedService
继承自 IHostedService
。
public class ClusterClientHostedService : IHostedService{ public IClusterClient Client { get; } private readonly ILogger<ClusterClientHostedService> _logger; public ClusterClientHostedService(ILogger<ClusterClientHostedService> logger, ILoggerProvider loggerProvider) { _logger = logger; Client = new ClientBuilder() .UseLocalhostClustering() .Configure<ClusterOptions>(options => { options.ClusterId = "Hello.Orleans"; options.ServiceId = "Hello.Orleans"; }) .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Build(); } public Task StartAsync(CancellationToken cancellationToken) { var attempt = 0; var maxAttempts = 100; var delay = TimeSpan.FromSeconds(1); return Client.Connect(async error => { if (cancellationToken.IsCancellationRequested) { return false; } if (++attempt < maxAttempts) { _logger.LogWarning(error, "Failed to connect to Orleans cluster on attempt {@Attempt} of {@MaxAttempts}.", attempt, maxAttempts); try { await Task.Delay(delay, cancellationToken); } catch (OperationCanceledException) { return false; } return true; } else { _logger.LogError(error, "Failed to connect to Orleans cluster on attempt {@Attempt} of {@MaxAttempts}.", attempt, maxAttempts); return false; } }); } public async Task StopAsync(CancellationToken cancellationToken) { try { await Client.Close(); } catch (OrleansException error) { _logger.LogWarning(error, "Error while gracefully disconnecting from Orleans cluster. Will ignore and continue to shutdown."); } }}
代码讲解:
1.构造函数中经过借助 ClientBuilder()
来初始化 IClusterClient
。其中 UseLocalhostClustering()
用于链接到开发环境中的localhost 集群。并经过 Configure<ClusterOptions>
指定链接到哪一个集群。(须要注意的是,这里的ClusterId必须与Orleans.Server中配置的保持一致。
Client = new ClientBuilder() .UseLocalhostClustering() .Configure<ClusterOptions>(options => { options.ClusterId = "Hello.Orleans"; options.ServiceId = "Hello.Orleans"; }) .ConfigureLogging(builder => builder.AddProvider(loggerProvider)) .Build();
2. 在 StartAsync
方法中经过调用 Client.Connect
创建与Orleans Server的链接。同时定义了一个重试机制。
紧接着咱们须要将 ClusterClientHostedService
添加到Ioc容器,添加如下代码到 Orleans.Client.Program
中:
static Task Main(string[] args){ Console.Title = typeof(Program).Namespace; return Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton<ClusterClientHostedService>(); services.AddSingleton<IHostedService>(_ => _.GetService<ClusterClientHostedService>()); services.AddSingleton(_ => _.GetService<ClusterClientHostedService>().Client); services.AddHostedService<HelloOrleansClientHostedService>(); services.Configure<ConsoleLifetimeOptions>(options => { options.SuppressStatusMessages = true; }); }) .ConfigureLogging(builder => { builder.AddConsole(); }) .RunConsoleAsync();}
对于 ClusterClientHostedService
,并无选择直接经过 services.AddHostedService<T>
的方式注入,是由于咱们须要注入该服务中提供的 IClusterClient
(单例),以供其余类去消费。
紧接着,定义一个 HelloOrleansClientHostedService
用来消费定义的 ISessionControlGrain
。
public class HelloOrleansClientHostedService : IHostedService{ private readonly IClusterClient _client; private readonly ILogger<HelloOrleansClientHostedService> _logger; public HelloOrleansClientHostedService(IClusterClient client, ILogger<HelloOrleansClientHostedService> logger) { _client = client; _logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { // 模拟控制台终端用户登陆 await MockLogin("Hello.Orleans.Console"); // 模拟网页终端用户登陆 await MockLogin("Hello.Orleans.Web"); } /// <summary> /// 模拟指定应用的登陆 /// </summary> /// <param name="appName"></param> /// <returns></returns> public async Task MockLogin(string appName) { //假设咱们须要支持不一样端登陆用户,则只须要将项目名称做为身份标识。 //便可获取一个表明用来维护当前项目登陆状态的的单例Grain。 var sessionControl = _client.GetGrain<ISessionControlGrain>(appName); ParallelLoopResult result = Parallel.For(0, 10000, (index) => { var userId = $"User-{index}"; sessionControl.Login(userId); }); if (result.IsCompleted) { //ParallelLoopResult.IsCompleted 只是返回全部循环建立完毕,并不保证循环的内部任务建立并执行完毕 //因此,此处手动延迟5秒后再去读取活动用户数。 await Task.Delay(TimeSpan.FromSeconds(5)); var activeUserCount = await sessionControl.GetActiveUserCount(); _logger.LogInformation($"The Active Users Count of {appName} is {activeUserCount}"); } } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Closed!"); return Task.CompletedTask; ; }}
代码讲解:这里定义了一个 MockLogin
用于模拟不一样终端10000个用户的并发登陆。
经过构造函数注入须要的
IClusterClient
。经过指定Grain接口以及身份标识,就能够经过Client 获取对应的Grain,进而消费Grain中暴露的方法。
varsessionControl=_client.GetGrain<ISessionControlGrain>(appName);
这里须要注意的是,指定的身份标识为终端应用的名称,那么在整个应用生命周期内,将有且仅有一个表明这个终端应用的Grain。使用
Parallel.For
模拟并发ParallelLoopResult.IsCompleted
只是返回全部循环任务建立完毕,并不表明循环的内部任务执行完毕。
6. 启动第一个 Orleans 应用
先启动 Orleans.Server
:再启动
Orleans.Client
:
从上面的运行结果来看,模拟两个终端10000个用户的并发登陆,最终输出的活动用户数量均为10000个。回顾整个实现,并无用到诸如锁、并发集合等避免并发致使的线程安全问题,但却输出正确的指望结果,这就正好说明了Orleans强大的并发控制特性。
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; } ....}
7. 小结
经过简单的演示,想必你对Orleans的编程实现有了基本的认知,并体会到其并发控制的强大之处。这只是简单的入门演练,Orleans不少强大的特性,后续再结合具体场景进行详细阐述。源码已上传至GitHub:Hello.Orleans
本文分享自微信公众号 - 微服务知多少(dotnet-microservice)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。