Orleans 知多少 | 3. Hello Orleans
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包。 - Orleans.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
项目引用。
3. 第一个Grain
Grain作为Orleans的第一公民,以及Virtual Actor的实际代言人,想吃透Orleans,那Grain就是第一道坎。
先看一个简单的Demo,我们来模拟统计网站的实时在线用户。
在Orlean s.Grains
添加ISessionControl
接口,主要用户登录状态的管理。
public interface ISessionControlGrain : IGrainWithStringKey
{
Task Login(string userId);
Task Logout(string userId);
Task<int> GetActiveUserCount();
}
可以看见Grain的定义很简单,只需要指定继承自IGrain的接口就好。这里面继承自IGrainWithStringKey
,说明该Grain 的Identity Key(身份标识)为string
类型。同时需要注意的是
Grain 的方法申明,返回值必须是: Task、Task、ValueTask。
紧接着定义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.");
}
}
}
代码讲解:
- 构造函数中通过借助
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();
- 在
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中暴露的方法。
var sessionControl = _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
Orleans 知多少 | 3. Hello Orleans的更多相关文章
- .NET分布式框架 | Orleans 知多少
引言 公司物联网项目集成Orleans以支持高并发的分布式业务,对于Orleans也是第一次接触,本文就分享下个人对Orleans的理解. 这里先抛出自己的观点:Orleans 是一个支持有状态云生应 ...
- Orleans 知多少 | 2. 核心概念一览
Orleans 术语解读 上面这张图中包含了Orleans中的几个核心概念: Grain Silo Orleans Cluster Orleans Client 从这张图,我们应该能理清他们之间的关系 ...
- Orleans 知多少 | Orleans 中文文档上线
Orleans 简介 Orleans是一个跨平台框架,用于构建健壮,可扩展的分布式应用程序 Orleans建立在.NET开发人员生产力的基础上,并将其带入了分布式应用程序的世界,例如云服务. Orle ...
- Orleans是什么 (一)
官网:http://dotnet.github.io/orleans/ 文档:http://dotnet.github.io/orleans/What's-new-in-Orleans 源码:http ...
- Orleans部署
一.配置指南 1,客户端配置 2,服务端配置 3,典型配置 4,配置.NET垃圾收集 5,SQL系统存储 二.监控 1,运行时监视 2,silo错误代码监测 3,客户端错误代码监测 三.解决部署问题 ...
- Orleans核心功能
一.Grain持久性 二.定时器和提醒 三.依赖注入 四.观察者 五.无状态工作者Grains 六.流 一.Grain持久化 1,Grain持久化目标 ①允许不同类型的存储提供者使用不同类型的存储提供 ...
- ORLEANS REMOTE DEPLOYMENT
Orleans Remote Deployment Table of Contents Overview: 1 Prerequisites. 2 Deployment Steps. 2 Orleans ...
- Microsoft Orleans 之 入门指南
Microsoft Orleans 在.net用简单方法构建高并发.分布式的大型应用程序框架. 原文:http://dotnet.github.io/orleans/ 在线文档:http://dotn ...
- github.com/dotnet/orleans
Orleans is a framework that provides a straight-forward approach to building distributed high-scale ...
随机推荐
- tcp居然会数据延迟40ms被发送
tcpdump是很好的tcp分析工具,在此配合nc命令来学习tcpdump nc -l 8000 tcpdump -S -n -i lo tcp and host 127.0.0.1 and port ...
- spring中集成shiro进行安全管理
shiro是一款轻量级的安全框架,提供认证.授权.加密和会话管理四个基础功能,除此之外也提供了很好的系统集成方案. 下面将它集成到之前的demo中,在之前spring中使用aop配置事务这篇所附代码的 ...
- 离线环境下进行pip包安装
内网服务器不能上网,但是需要在上面安装python-package 通过另外一台能上网的主机B 1. 下载需要离线安装的Packages 在B上执行如下命令: 安装单个Package $ pip in ...
- jmeter基础使用
1.ServerAgent是服务端的插件2.下载成功后,复制JmeterPlugins-Extras.jar和JmeterPlugins-Standard.jar两个文件,放到jmeter安装文件中的 ...
- 对Servlet执行流程的初步认识
这里我们以Post方式请求Serclet为例 1.找到 中的URL地址 Form表单的Post请求HelloServlet(Action="HelloServlet")发起时, A ...
- pip换源
PIP 下载慢,给你Python3的pip换个源 一键换源 文章来源:企鹅号 - 从零开始学习python 要实现一键换源需要安装一个模块 pip install pqi PQI相关命令 PQI获取当 ...
- Ansible常用模块基本操作
Ansible是一个系列文章,我会尽量以通俗易懂.诙谐幽默的总结方式给大家呈现这些枯燥的知识点,让学习变的有趣一些. 前言 对于任何一个框架,一个应用,为了更便于推广,便于使用,便于商业化,都会顺便提 ...
- POJ 2533——Longest Ordered Subsequence(DP)
链接:http://poj.org/problem?id=2533 题解 #include<iostream> using namespace std; ]; //存放数列 ]; //b[ ...
- 从React-Native坑中爬出,我记下了这些
吐槽 如果React-Native是个人,我估计已经想要打死他了... 上一篇文章 当React开发者初次走进React-Native的世界 前言 最近因为业务需要,做了一些关于React-Nativ ...
- ASP.NET Web API 2系列(二):灵活多样的路由配置
1. 导言 路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的在于利用注册的路由对请求的URL进行解析以确定目标HTTPController和Action的名称, ...