什么是grain客户端?
术语“客户端”(有时也称为“grain客户端”),用于与grain交互的应用程序代码,但它本身不是grain逻辑的一部分。客户端代码在Orleans服务器集群(称为silo)之外运行,而集群中承载了grain。因此,客户端充当了集群和应用程序的所有grain的连接器或管道。
通常,前端Web服务器上的客户端,被用来连接到Orleans集群,该集群作为中间层,其中的grain执行业务逻辑。在典型设置中,前端Web服务器:
- 收到网络请求
- 执行必要的身份验证和授权验证
- 决定哪些grain应处理请求
- 使用Grain Client对grain进行一个或多个方法调用
- 处理grain调用是成功完成还是失败,以及任何的返回值
- 发送Web请求的响应
Grain客户端的初始化
在grain客户端可用于调用Orleans集群中承载的grain之前,需要对其进行配置,初始化并连接到群集。
通过ClientBuilder
和一些附加选项类,来进行配置 ,这些附加选项类包含了用于以编程方式配置客户端的配置属性层级结构。
更多信息可以在“ 客户端配置”指南中找到。
客户端配置示例:
var client = new ClientBuilder()
// Clustering information
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
// Clustering provider
.UseAzureStorageClustering(options => options.ConnectionString = connectionString)
// Application parts: just reference one of the grain interfaces that we use
.ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IValueGrain).Assembly))
.Build();
最后,我们需要在构造的客户端对象上,调用Connect()
方法,使其连接到Orleans集群。这是一个异步方法,返回一个Task
。所以我们需要用一个await
或者.Wait()
,来等待它的完成。
await client.Connect();
调用grain
从客户端调用grain,与从grain代码中进行这些调用没有什么实质上的不同。在两种情况下,都使用相同的GetGrain<T>(key)
方法,其中T
是目标grain接口,以获得grain引用。细微的区别是,通过什么样的工厂对象,我们来调用GetGrain
。在客户端代码中,我们通过连接的客户端对象来实现。
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Task t = player.JoinGame(game)
await t;
对grain方法的调用,将返回一个Task
或一个 Task<T>
,这是grain接口规则所要求的。客户端可以使用await
关键字,来异步地等待返回的Task
而不阻塞线程,或者在某些情况下,使用Wait()
方法,阻塞的当前执行的线程。
从客户端代码和从另一个grain中调用grain的主要区别,在于grain的单线程执行模型。grain被Orleans运行时限制为单线程,而客户端可能是多线程的。Orleans在客户机端不提供任何这样的保证,因此由客户端可以使用适合其环境的任何同步构造(锁、事件、Tasks
等),来管理自己的并发性。
接收通知
在某些情况下,简单的请求-响应模式是不够的,客户端需要接收异步通知。例如,用户可能希望在她正在关注的某个人发布新消息时收到通知。
观察者就是这样一种机制,它使客户端对象暴露为类似grain的目标,从而被grain调用。对观察者的调用不提供任何成功或失败的指示,因为它们作为单向消息,尽力而为地发送。因此,应用程序代码有责任在必要时,在观察者之上构建更高级别的可靠性机制。
另一种可用于向客户端传递异步消息的机制是Streams。Streams暴露了每个消息传递成功或失败的指示,因此能够与客户端进行可靠的通信。
示例
以下是上述示例的客户端应用程序的扩展版本,该客户端应用程序连接到Orleans,查找玩家帐户,用一个观察者来订阅玩家的游戏会话更新,并打印出通知,直到手动终止程序。
namespace PlayerWatcher
{
class Program
{
/// <summary>
/// Simulates a companion application that connects to the game
/// that a particular player is currently part of, and subscribes
/// to receive live notifications about its progress.
/// </summary>
static void Main(string[] args)
{
RunWatcher().Wait();
// Block main thread so that the process doesn't exit.
// Updates arrive on thread pool threads.
Console.ReadLine();
}
static async Task RunWatcher()
{
try
{
var client = new ClientBuilder()
// Clustering information
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
// Clustering provider
.UseAzureStorageClustering(options => options.ConnectionString = connectionString)
// Application parts: just reference one of the grain interfaces that we use
.ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IValueGrain).Assembly))
.Build();
// Hardcoded player ID
Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = null;
while (game == null)
{
Console.WriteLine("Getting current game for player {0}...", playerId);
try
{
game = await player.GetCurrentGame();
if (game == null) // Wait until the player joins a game
{
await Task.Delay(5000);
}
}
catch (Exception exc)
{
Console.WriteLine("Exception: ", exc.GetBaseException());
}
}
Console.WriteLine("Subscribing to updates for game {0}...", game.GetPrimaryKey());
// Subscribe for updates
var watcher = new GameObserver();
await game.SubscribeForGameUpdates(
await client.CreateObjectReference<IGameObserver>(watcher));
Console.WriteLine("Subscribed successfully. Press <Enter> to stop.");
}
catch (Exception exc)
{
Console.WriteLine("Unexpected Error: {0}", exc.GetBaseException());
}
}
}
/// <summary>
/// Observer class that implements the observer interface. Need to pass a grain reference to an instance of this class to subscribe for updates.
/// </summary>
class GameObserver : IGameObserver
{
// Receive updates
public void UpdateGameScore(string score)
{
Console.WriteLine("New game score: {0}", score);
}
}
}
}