Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Klient umožňuje nekernelovému kódu interakci se Orleans clusterem. Klienti umožňují kódu aplikace komunikovat s zrnky a datovými proudy hostovanými v clusteru. Klienta lze získat dvěma způsoby, podle toho, kde hostujete kód klienta: ve stejném procesu jako silo nebo v samostatném procesu. Tento článek popisuje obě možnosti, počínaje doporučeným přístupem: spoluhostování kódu klienta ve stejném procesu jako odstupňovaný kód.
Spolu hostovaní klienti
Pokud hostujete kód klienta ve stejném procesu jako kód zrnka, můžete klienta 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 jeho dodatečných znalostí o clusteru.
Tento přístup nabízí několik výhod, včetně snížené režie na síť a procesor, snížení latence a vyšší propustnosti a spolehlivosti. Klient používá znalosti topologie a stavu clusteru silo a nepotřebuje samostatnou bránu. Tím se vyhnete síťovému skoku a procesu serializace/deserializace, čímž se zvýší spolehlivost minimalizací počtu požadovaných uzlů mezi klientem a zrnem. Pokud je grain bezstavovou pracovní úlohou nebo se aktivuje na stejném silu, kde je klient hostovaný, není potřeba žádná serializace ani síťová komunikace, což klientovi umožňuje dosáhnout zlepš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 nutnost nasazení a monitorování dvou různých binárních souborů aplikací.
Tento přístup má také nevýhody, především že kód zrnitosti už není izolovaný od procesu klienta. Problémy s kódem klienta, jako je blokování vstupně-výstupních operací nebo soutěžení o zámky, které způsobuje vyhladovění vláken, můžou mít vliv na výkon grainového kódu. Bez takových vad kódu může dojít k efektu hlučného souseda jednoduše proto, že se klientský kód spouští na stejném procesoru jako kód zrno, což zvyšuje zatížení mezipaměti procesoru a zvyšuje konkurenci místních prostředků. Kromě toho je identifikace zdroje těchto problémů obtížnější, protože systémy monitorování nemohou logicky rozlišovat mezi klientským kódem a odstupňovaným kódem.
Navzdory těmto nevýhodám je spoluhostování kódu klienta s kódem zrnitosti oblíbenou možností a doporučeným přístupem pro většinu aplikací. Výše uvedené nevýhody jsou často minimální v praxi z následujících důvodů:
- Klientský kód je často velmi tenký (například překládání příchozích požadavků HTTP do volání vstupních bodů). Proto jsou efekty hlučného souseda minimální a srovnatelné s náklady na jinak požadovanou bránu.
- Pokud dojde k problému s výkonem, váš typický pracovní postup pravděpodobně zahrnuje nástroje, jako jsou profilátory procesoru a ladicí programy. Tyto nástroje zůstávají efektivní při rychlé identifikaci zdroje problému, a to i při provádění kódu klienta i odstupňovaného kódu ve stejném procesu. Jinými slovy, zatímco metriky se stávají hrubší a méně schopné přesně identifikovat zdroj problému, podrobnější nástroje jsou stále účinné.
Získání klienta z hostitele
Pokud hostujete pomocí obecného hostitele .NET, klient je automaticky dostupný v kontejneru injektáže závislostí hostitele. Můžete ho vložit do služeb, jako jsou ASP.NET kontrolery nebo IHostedService implementace.
Případně můžete získat klientské rozhraní, například IGrainFactory nebo IClusterClient z ISiloHost:
var client = host.Services.GetService<IClusterClient>();
await client.GetGrain<IMyGrain>(0).Ping();
Externí klienti
Klientský kód může běžet mimo Orleans cluster, kde je hostován kód grainu. V tomto případě externí klient funguje jako konektor nebo kanál ke clusteru a ke všem komponentám aplikace. Klienty na front-endových webových serverech obvykle používáte k připojení ke clusteru Orleans, který slouží jako střední vrstva a zajišťuje provádění obchodní logiky pomocí zrn.
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é instance (grainy) by měly požadavek zpracovat.
- Používá balíček NuGet Orleans k provedení jednoho nebo více volání metod do jednotky grain.
- 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.
Inicializovat klienta Grain
Než budete moct použít klienta grain k volání závitů hostovaných v clusteru Orleans, musíte ho nakonfigurovat, inicializovat a připojit ke clusteru.
Zadejte konfiguraci prostřednictvím UseOrleansClient a několik doplňkových tříd možností obsahující 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:
Použití TokenCredential s identifikátorem URI služby je doporučeným přístupem. Tento model zabraňuje ukládání tajných kódů v konfiguraci a využívá k zabezpečenému ověřování ID Microsoft Entra.
DefaultAzureCredential poskytuje řetěz přihlašovacích údajů, který bezproblémově funguje v místních vývojových a produkčních prostředích. Během vývoje používá vaše přihlašovací údaje k Azure CLI nebo sadě Visual Studio. V produkčním prostředí v Azure automaticky používá spravovanou identitu přiřazenou k vašemu prostředku.
Návod
DefaultAzureCredential funguje bezproblémově napříč místním vývojem a produkčním prostředím. Při vývoji používá vaše přihlašovací údaje k Azure CLI nebo sadě Visual Studio. V produkčním prostředí v Azure automaticky používá spravovanou identitu prostředku. Pokud chcete zvýšit výkon a laditelnost v produkčním prostředí, zvažte jeho nahrazení určitými přihlašovacími údaji, jako je ManagedIdentityCredential. Další informace najdete v pokynech k použití pro DefaultAzureCredential.
using Azure.Identity;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleansClient(clientBuilder =>
{
clientBuilder.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
});
clientBuilder.UseAzureStorageClustering(options =>
{
options.ConfigureTableServiceClient(
new Uri("https://<your-storage-account>.table.core.windows.net"),
new DefaultAzureCredential());
});
});
using var host = builder.Build();
await host.StartAsync();
Při spuštění host je klient nakonfigurován a dostupný prostřednictvím jeho vytvořené instance poskytovatele služeb.
Zadejte konfiguraci prostřednictvím ClientBuilder a několik doplňkových tříd možností obsahující 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.ConfigureTableServiceClient(connectionString))
.ConfigureApplicationParts(
parts => parts.AddApplicationPart(typeof(IValueGrain).Assembly))
.Build();
Nakonec je potřeba volat metodu Connect() na vytvořeném klientském objektu pro připojení ke clusteru Orleans . Jedná se o asynchronní metodu vracející Task, takže potřebujete počkat na jeho dokončení pomocí await nebo .Wait().
await client.Connect();
Volání zrn
Volání do zrnek z klienta se nijak neliší od provádění takových volání zevnitř kódu zrnek. Použijte stejnou IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) metodu (kde T je rozhraní cílového zrna) v obou případech k získání odkazů na zrna. Rozdíl spočívá v tom, který objekt továrny vyvolá IGrainFactory.GetGrain. V kódu klienta to provedete prostřednictvím připojeného klientského objektu, jak ukazuje následující příklad:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Task joinGameTask = player.JoinGame(game)
await joinGameTask;
Volání metody grain vrátí hodnotu Task nebo Task<TResult>, podle požadavků pravidel rozhraní grain. Klient může pomocí klíčového slova await asynchronně čekat na vrácené Task bez blokování vlákna, nebo v některých případech pomocí metody Wait() blokovat aktuální vlákno.
Hlavním rozdílem mezi voláním zrn z klientského kódu a z jiného zrna je jednovláknový model provádění zrn. Modul Orleans runtime omezuje zrnka na jednovláknové, zatímco klienti můžou být vícevláknové.
Orleans neposkytuje žádnou takovou záruku na straně klienta, takže je na klientovi, aby spravovala svou souběžnost pomocí vhodných synchronizačních konstruktorů pro své prostředí – zámky, události Tasksatd.
Příjem oznámení
Ně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 oznámení, když někdo, koho sleduje, publikuje novou zprávu.
Použití Pozorovatelů je jedním z mechanismů, který umožňuje exponování objektů na straně klienta jako cíle připomínající granule, které mají být vyvolány granulemi. Volání směrem k pozorovatelům neposkytuje žádnou informaci o úspěchu nebo selhání, protože se odesílají jako jednosměrné zprávy s nejlepšími možnými snahami. Proto je zodpovědností kódu vaší aplikace vytvořit mechanismus spolehlivosti vyšší úrovně nad pozorovateli, pokud je to potřeba.
Dalším mechanismem pro doručování asynchronních zpráv klientům je Streams. Streamy zpřístupňují informace o úspěchu nebo selhání jednotlivých doručení zpráv, což umožňuje spolehlivou komunikaci zpět klientovi.
Připojení klienta
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í na odkazy na grainy získané 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 označující, co se nepovedlo. Můžete zaregistrovat objekt IClientConnectionRetryFilter pro zpracování výjimky a rozhodnout se, jestli se má opakovat. Pokud nezadáte žádný filtr opakování nebo pokud filtr opakování vrátí false, klient natrvalo vzdá pokusy o opakování.
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 zavolání metody IClusterClient.Connect() poprvé.
- Při volání na odkazy na grainy získané z připojeného klienta clusteru.
V prvním případě vyvolá Connect metoda výjimku označující, co se nepovedlo. To je obvykle (ale ne nutně) SiloUnavailableException. V takovém případě je instance klienta clusteru nepoužitelná a měla by být odstraněna. Volitelně můžete metodě zadat funkci Connect filtru opakování, která může například před dalším pokusem počkat na zadanou dobu. Pokud nezadáte žádný filtr opakování nebo pokud filtr opakování vrátí false, klient natrvalo vzdá pokusy o opakování.
Pokud Connect se klient clusteru úspěšně vrátí, je zaručeno, že ho bude možné použít, dokud ho nezlikvidíte. To znamená, že i když u klienta dochází k problémům s připojením, snaží se o obnovu bez časového omezení. Můžete nakonfigurovat přesné chování obnovení 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 během volání grain dojde k problému s připojením, se na straně klienta vyvolá SiloUnavailableException. Můžete to zpracovat takto:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
try
{
await player.JoinGame(game);
}
catch (SiloUnavailableException)
{
// Lost connection to the cluster...
}
V této situaci není odkaz na zrno neplatný; Můžete zkusit znovu provést volání stejného odkazu později, když bylo připojení znovu navázáno.
Injekce závislostí
Doporučeným způsobem, jak vytvořit externího klienta v programu pomocí obecného hostitele .NET, je vložit IClusterClient instanci singleton prostřednictvím injektáže závislostí. Tuto instanci lze pak 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 bude připojeno, není nutné ručně vytvářet klienta; Orleans automaticky jednoho poskytne 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();
}
}
Zaregistrujte službu takto:
await Host.CreateDefaultBuilder(args)
.UseOrleansClient(builder =>
{
builder.UseLocalhostClustering();
})
.ConfigureServices(services =>
{
services.AddHostedService<ClusterClientHostedService>();
})
.RunConsoleAsync();
Příklad
Tady je rozšířená verze předchozího příkladu zobrazující klientskou aplikaci, která se připojuje na Orleans, najde účet hráče, přihlásí se k odběru aktualizací herní relace, jíž je hráč součástí, pomocí pozorovatele a zobrazuje oznámení, dokud není program ručně ukončen.
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.TableServiceClient = new TableServiceClient(
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()}");
}
internal static class ExternalClientExample
{
private static string connectionString = "UseDevelopmentStorage=true";
public static async Task RunWatcherAsync()
{
try
{
var client = new ClientBuilder()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "MyOrleansService";
})
.UseAzureStorageClustering(
options => options.ConfigureTableServiceClient(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);
}
}