Orleans Klienty
Klient umožňuje interakci s clusterem bez odstupňovaného Orleans kódu. Klienti umožňují kódu aplikace komunikovat s zrnky a datovými proudy hostovanými v clusteru. Klienta můžete získat dvěma způsoby v závislosti na tom, kde je kód klienta hostovaný: ve stejném procesu jako sil nebo v samostatném procesu. Tento článek popisuje obě možnosti, počínaje doporučenou možností: spoluhostit kód klienta ve stejném procesu jako kód odstupňovaného kódu.
Pokud je kód klienta hostovaný ve stejném procesu jako kód odstupňovaného kódu, klient se dá získat přímo z kontejneru injektáže závislostí hostitelské aplikace. V takovém případě klient komunikuje přímo se silem, ke kterému je připojený, a může využít dodatečné znalosti, které má silo o clusteru.
To poskytuje několik výhod, včetně snížení režie sítě a procesoru a snížení latence a zvýšení propustnosti a spolehlivosti. Klient využívá znalosti topologie a stavu clusteru silo a nepotřebuje používat samostatnou bránu. Tím se vyhnete síťovému směrování a serializaci nebo deserializaci. Tím se také zvyšuje spolehlivost, protože počet požadovaných uzlů mezi klientem a agregačním intervalem je minimalizovaný. Pokud se jedná o bezstavový pracovní interval nebo se jinak aktivuje na silu, kde je klient hostovaný, není potřeba provádět serializaci ani síťovou komunikaci a klient může získat další zvýšení výkonu a spolehlivosti. Spoluhostování klientského a odstupňovaného kódu také zjednodušuje nasazení a topologii aplikací tím, že eliminuje potřebu nasazení a monitorování dvou různých binárních souborů aplikací.
K tomuto přístupu existují také traktory, především proto, že kód zrnitosti už není izolovaný od procesu klienta. Problémy v klientském kódu, jako je blokování vstupně-výstupních operací nebo kolize zámků, které způsobují hladové vlákno, můžou mít vliv na výkon odstupňovaného kódu. I bez vad kódu, jako je výše uvedené, mohou efekty hlučného souseda vést jednoduše tak, že se klientský kód spustí na stejném procesoru jako kód zrnitosti, což obecně zatíží mezipaměť procesoru a další kolize místních prostředků. Kromě toho je identifikace zdroje těchto problémů teď obtížnější, protože systémy monitorování nemohou rozlišit, co je logicky klientský kód od odstupňovaného kódu.
I přes tyto traktory je spoluhostování klientského kódu s odstupňovaným kódem oblíbenou možností a doporučeným přístupem pro většinu aplikací. Chcete-li vypracovat, výše uvedené traktory jsou minimální v praxi z následujících důvodů:
- Klientský kód je často velmi tenký, například překlad příchozích požadavků HTTP do volání odstupňovaného volání, a proto efekty hlučného souseda jsou minimální a srovnatelné s náklady na jinak požadovanou bránu.
- Pokud dojde k problému s výkonem, typický pracovní postup pro vývojáře zahrnuje nástroje, jako jsou profilátory procesoru a ladicí programy, které jsou stále efektivní při rychlé identifikaci zdroje problému, i když má spuštěný klientský i odstupňovaný kód ve stejném procesu. Jinými slovy, metriky se stávají hrubějšími a méně schopnými přesně identifikovat zdroj problému, ale podrobnější nástroje jsou stále efektivní.
Pokud hostování pomocí obecného hostitele .NET, klient bude k dispozici v kontejneru injektáže závislostí hostitele automaticky a může být vložen do služeb, jako jsou kontrolery ASP.NET nebo IHostedService implementace.
Případně lze klientské rozhraní, jako IGrainFactory je nebo IClusterClient lze získat z ISiloHost:
var client = host.Services.GetService<IClusterClient>();
await client.GetGrain<IMyGrain>(0).Ping();
Klientský kód může běžet mimo Orleans cluster, kde je hostovaný kód odstupňovaného intervalu. Externí klient proto funguje jako konektor nebo konduita clusteru a všechna zrnka aplikace. Klienti se obvykle používají na front-endových webových serverech pro připojení ke Orleans clusteru, který slouží jako střední vrstva s zrnami provádějícími obchodní logiku.
V typickém nastavení front-endový webový server:
- Přijme webový požadavek.
- Provádí nezbytné ověřování a ověřování autorizace.
- Určuje, které agregační intervaly mají požadavek zpracovat.
- Používá Microsoft .Orleans. Balíček NuGet klienta pro volání jedné nebo více metod do agregací.
- Zpracovává úspěšné dokončení nebo selhání volání agregace a všechny vrácené hodnoty.
- Odešle odpověď na webový požadavek.
Než bude možné použít odstupňovaný klient pro volání zrn hostovaných v clusteru Orleans , je potřeba ho nakonfigurovat, inicializovat a připojit ke clusteru.
Konfigurace je poskytována prostřednictvím UseOrleansClient a několika doplňkových tříd možností, které obsahují hierarchii vlastností konfigurace pro programovou konfiguraci klienta. Další informace naleznete v tématu Konfigurace klienta.
Představte si následující příklad konfigurace klienta:
// Alternatively, call Host.CreateDefaultBuilder(args) if using the
// Microsoft.Extensions.Hosting NuGet package.
using IHost host = new HostBuilder()
.UseOrleansClient(clientBuilder =>
{
clientBuilder.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
});
clientBuilder.UseAzureStorageClustering(
options => options.ConfigureTableServiceClient(connectionString))
})
.Build();
host
Po spuštění se klient nakonfiguruje a zpřístupní prostřednictvím vytvořené instance poskytovatele služeb.
Konfigurace je poskytována prostřednictvím ClientBuilder a několika doplňkových tříd možností, které obsahují hierarchii vlastností konfigurace pro programovou konfiguraci klienta. Další informace naleznete v tématu Konfigurace klienta.
Příklad konfigurace klienta:
var client = new ClientBuilder()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
.UseAzureStorageClustering(
options => options.ConnectionString = connectionString)
.ConfigureApplicationParts(
parts => parts.AddApplicationPart(typeof(IValueGrain).Assembly))
.Build();
Nakonec je potřeba volat Connect()
metodu na vytvořeném klientském objektu, aby se připojil ke clusteru Orleans . Jedná se o asynchronní metodu Task
, která vrací . Takže musíte počkat na jeho dokončení s nebo await
.Wait()
.
await client.Connect();
Provádění volání z klienta se nijak neliší od provádění takových volání v rámci odstupňovaného kódu. Stejná IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) metoda, kde T
je rozhraním cílového agregace, se používá v obou případech k získání odkazů na agregační intervaly. Rozdíl je v tom, co objekt továrny je vyvolán IGrainFactory.GetGrain. V kódu klienta to uděláte prostřednictvím připojeného objektu klienta, jak ukazuje následující příklad:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Task joinGameTask = player.JoinGame(game)
await joinGameTask;
Volání metody zrnitosti vrátí Task nebo podle Task<TResult> požadavků pravidel rozhraní pro agregační intervaly. Klient může pomocí klíčového await
slova asynchronně očekávat vrácené Task
bez blokování vlákna nebo v některých případech Wait()
metoda blokovat aktuální vlákno spuštění.
Hlavním rozdílem mezi voláním zrn z klientského kódu a z jiného zrnka je model provádění zrn s jedním vláknem. Zrna jsou omezena na jednovláknové moduly Orleans runtime, zatímco klienti mohou být vícevláknové. Orleans neposkytuje žádnou takovou záruku na straně klienta, a proto je na klientovi, aby spravovala svou souběžnost pomocí jakýchkoli konstruktorů synchronizace, které jsou vhodné pro své prostředí – zámky, události a Tasks
.
Existují situace, kdy jednoduchý vzor odpovědi na požadavek nestačí a klient potřebuje přijímat asynchronní oznámení. Uživatel může například chtít být upozorněn, když někdo publikuje novou zprávu, kterou sleduje.
Použití pozorovatelů je jedním z takových mechanismů, které umožňují vystavit objekty na straně klienta jako cíle podobné zrnitosti, aby se vyvolaly zrnky. Volání pozorovatelů neposkytuje žádné informace o úspěchu nebo selhání, protože se odesílají jako jednosměrná zpráva s nejlepším úsilím. Proto je odpovědností kódu aplikace vytvořit mechanismus spolehlivosti vyšší úrovně nad pozorovateli, pokud je to potřeba.
Dalším mechanismem, který lze použít k doručování asynchronních zpráv klientům, je Toky. Toky zveřejnit informace o úspěchu nebo selhání doručení jednotlivých zpráv, a proto umožnit spolehlivou komunikaci zpět klientovi.
Existují dva scénáře, ve kterých může dojít k problémům s připojením klienta clusteru:
- Když se klient pokusí připojit k silu.
- Při volání odkazů na agregační intervaly, které byly získány z připojeného klienta clusteru.
V prvním případě se klient pokusí připojit k silu. Pokud se klient nemůže připojit k žádnému silu, vyvolá výjimku, která indikuje, co se nepovedlo. Můžete zaregistrovat, IClientConnectionRetryFilter jestli se má výjimka zpracovat, a rozhodnout se, jestli se má opakovat nebo ne. Pokud není k dispozici žádný filtr opakování nebo pokud se filtr opakování vrátí false
, klient se v dobrém stavu vrátí.
using Orleans.Runtime;
internal sealed class ClientConnectRetryFilter : IClientConnectionRetryFilter
{
private int _retryCount = 0;
private const int MaxRetry = 5;
private const int Delay = 1_500;
public async Task<bool> ShouldRetryConnectionAttempt(
Exception exception,
CancellationToken cancellationToken)
{
if (_retryCount >= MaxRetry)
{
return false;
}
if (!cancellationToken.IsCancellationRequested &&
exception is SiloUnavailableException siloUnavailableException)
{
await Task.Delay(++ _retryCount * Delay, cancellationToken);
return true;
}
return false;
}
}
Existují dva scénáře, ve kterých může dojít k problémům s připojením klienta clusteru:
- Při počátečním zavolání IClusterClient.Connect() metody.
- Při volání odkazů na agregační intervaly, které byly získány z připojeného klienta clusteru.
V prvním případě metoda Connect
vyvolá výjimku, která indikuje, co se nepovedlo. To je obvykle (ale ne nutně) a SiloUnavailableException. V takovém případě je instance klienta clusteru nepoužitelná a měla by být odstraněna. Funkci filtru opakování lze volitelně zadat metodě Connect
, která může například čekat na zadanou dobu před provedením dalšího pokusu. Pokud není k dispozici žádný filtr opakování nebo pokud se filtr opakování vrátí false
, klient se v dobrém stavu vrátí.
Pokud Connect
se klient clusteru úspěšně vrátí, je zaručeno, že bude použitelný, dokud nebude uvolněn. To znamená, že i když u klienta dochází k problémům s připojením, pokusí se obnovit neomezeně dlouho. Přesné chování obnovení lze nakonfigurovat u objektu GatewayOptions poskytovaného ClientBuilderobjektem, například:
var client = new ClientBuilder()
// ...
.Configure<GatewayOptions>(
options => // Default is 1 min.
options.GatewayListRefreshPeriod = TimeSpan.FromMinutes(10))
.Build();
V druhém případě, kdy dojde k problému s připojením během volání agregačního intervalu, SiloUnavailableException se na straně klienta vyvolá chyba. To se dá zpracovat takto:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
try
{
await player.JoinGame(game);
}
catch (SiloUnavailableException)
{
// Lost connection to the cluster...
}
Odkaz na agregační interval není v této situaci neplatný; volání by se mohlo opakovat na stejném odkazu později, když bylo připojení znovu navázáno.
Doporučeným způsobem, jak vytvořit externího klienta v programu, který používá .NET Generic Host, je vložit IClusterClient instanci singleton prostřednictvím injektáže závislostí, která se pak dá přijmout jako parametr konstruktoru v hostovaných službách, ASP.NET kontrolery atd.
Poznámka
Při spoluhostování Orleans sila ve stejném procesu, ke kterému se připojíte, není nutné ručně vytvořit klienta; Orleans automaticky poskytne jeden a bude spravovat jeho životnost odpovídajícím způsobem.
Při připojování ke clusteru v jiném procesu (na jiném počítači) je běžným vzorem vytvoření hostované služby takto:
using Microsoft.Extensions.Hosting;
namespace Client;
public sealed class ClusterClientHostedService : IHostedService
{
private readonly IClusterClient _client;
public ClusterClientHostedService(IClusterClient client)
{
_client = client;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Use the _client to consume grains...
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}
public class ClusterClientHostedService : IHostedService
{
private readonly IClusterClient _client;
public ClusterClientHostedService(IClusterClient client)
{
_client = client;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// A retry filter could be provided here.
await _client.Connect();
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _client.Close();
_client.Dispose();
}
}
Služba se pak zaregistruje takto:
await Host.CreateDefaultBuilder(args)
.UseOrleansClient(builder =>
{
builder.UseLocalhostClustering();
})
.ConfigureServices(services =>
{
services.AddHostedService<ClusterClientHostedService>();
})
.RunConsoleAsync();
Zde je rozšířená verze výše uvedeného příkladu klientské aplikace, která se připojuje k Orleans, najde účet hráče, přihlásí se k odběru aktualizací herní relace, na kterou je hráč součástí pozorovatele, a vytiskne oznámení, dokud program ručně neukončí.
try
{
using IHost host = Host.CreateDefaultBuilder(args)
.UseOrleansClient((context, client) =>
{
client.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
.UseAzureStorageClustering(
options => options.ConfigureTableServiceClient(
context.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"]));
})
.UseConsoleLifetime()
.Build();
await host.StartAsync();
IGrainFactory client = host.Services.GetRequiredService<IGrainFactory>();
// Hardcoded player ID
Guid playerId = new("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
IGameGrain? game = null;
while (game is null)
{
Console.WriteLine(
$"Getting current game for player {playerId}...");
try
{
game = await player.GetCurrentGame();
if (game is null) // Wait until the player joins a game
{
await Task.Delay(TimeSpan.FromMilliseconds(5_000));
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.GetBaseException()}");
}
}
Console.WriteLine(
$"Subscribing to updates for game {game.GetPrimaryKey()}...");
// Subscribe for updates
var watcher = new GameObserver();
await game.ObserveGameUpdates(
client.CreateObjectReference<IGameObserver>(watcher));
Console.WriteLine(
"Subscribed successfully. Press <Enter> to stop.");
}
catch (Exception e)
{
Console.WriteLine(
$"Unexpected Error: {e.GetBaseException()}");
}
await RunWatcherAsync();
// Block the main thread so that the process doesn't exit.
// Updates arrive on thread pool threads.
Console.ReadLine();
static async Task RunWatcherAsync()
{
try
{
var client = new ClientBuilder()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
.UseAzureStorageClustering(
options => options.ConnectionString = connectionString)
.ConfigureApplicationParts(
parts => parts.AddApplicationPart(typeof(IValueGrain).Assembly))
.Build();
// Hardcoded player ID
Guid playerId = new("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = null;
while (game is null)
{
Console.WriteLine(
$"Getting current game for player {playerId}...");
try
{
game = await player.GetCurrentGame();
if (game is null) // Wait until the player joins a game
{
await Task.Delay(TimeSpan.FromMilliseconds(5_000));
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.GetBaseException()}");
}
}
Console.WriteLine(
$"Subscribing to updates for game {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.");
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(
$"Unexpected Error: {e.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
{
public void UpdateGameScore(string score)
{
Console.WriteLine("New game score: {0}", score);
}
}
Zpětná vazba k produktu .NET
.NET je open source projekt. Vyberte odkaz pro poskytnutí zpětné vazby: