Pelatihan
Modul
Bangun aplikasi Orleans pertama Anda dengan ASP.NET Core 8.0 - Training
Pelajari cara membangun aplikasi terdistribusi cloud-native dengan Orleans.
Browser ini sudah tidak didukung.
Mutakhirkan ke Microsoft Edge untuk memanfaatkan fitur, pembaruan keamanan, dan dukungan teknis terkini.
Klien memungkinkan kode non-bijian untuk berinteraksi dengan Orleans kluster. Klien memungkinkan kode aplikasi untuk berkomunikasi dengan biji-bijian dan aliran yang dihosting dalam kluster. Ada dua cara untuk mendapatkan klien, tergantung di mana kode klien dihosting: dalam proses yang sama dengan silo, atau dalam proses terpisah. Artikel ini akan membahas kedua opsi, dimulai dengan opsi yang direkomendasikan: menghosting bersama kode klien dalam proses yang sama dengan kode biji-bijian.
Jika kode klien dihosting dalam proses yang sama dengan kode biji-bijian, maka klien dapat langsung diperoleh dari kontainer injeksi dependensi aplikasi hosting. Dalam hal ini, klien berkomunikasi langsung dengan silo yang dilampirkan dan dapat memanfaatkan pengetahuan tambahan yang dimiliki silo tentang kluster.
Ini memberikan beberapa manfaat, termasuk mengurangi overhead jaringan dan CPU serta mengurangi latensi dan meningkatkan throughput dan keandalan. Klien menggunakan pengetahuan silo tentang topologi dan status kluster dan tidak perlu menggunakan gateway terpisah. Ini menghindari hop jaringan dan serialisasi/deserialisasi pulang pergi. Oleh karena itu, ini juga meningkatkan keandalan, karena jumlah simpul yang diperlukan di antara klien dan biji-bijian diminimalkan. Jika biji-bijian adalah butir pekerja tanpa status atau kebetulan diaktifkan pada silo tempat klien dihosting, maka tidak ada serialisasi atau komunikasi jaringan yang perlu dilakukan sama sekali dan klien dapat menuai keuntungan performa dan keandalan tambahan. Klien hosting bersama dan kode biji-bijian juga menyederhanakan penyebaran dan topologi aplikasi dengan menghilangkan kebutuhan akan dua biner aplikasi yang berbeda untuk disebarkan dan dipantau.
Ada juga penolakan pendekatan ini, terutama bahwa kode biji-bijian tidak lagi terisolasi dari proses klien. Oleh karena itu, masalah dalam kode klien, seperti memblokir IO atau mengunci ketidakcocokan menyebabkan kelaparan utas dapat memengaruhi performa kode biji-bijian. Bahkan tanpa cacat kode seperti efek tetangga yang disebutkan di atas, bising dapat mengakibatkan hanya dengan menjalankan kode klien pada prosesor yang sama dengan kode biji-bijian, menempatkan strain tambahan pada cache CPU dan ketidakcocokan tambahan untuk sumber daya lokal secara umum. Selain itu, mengidentifikasi sumber masalah ini sekarang lebih sulit karena sistem pemantauan tidak dapat membedakan apa yang secara logis kode klien dari kode butir.
Terlepas dari penolakan ini, kode klien hosting bersama dengan kode biji-bijian adalah opsi populer dan pendekatan yang direkomendasikan untuk sebagian besar aplikasi. Untuk menguraikan, pendetraktor yang disebutkan di atas minimal dalam praktiknya karena alasan berikut:
Jika hosting menggunakan Host Generik .NET, klien akan tersedia dalam kontainer injeksi dependensi host secara otomatis dan dapat disuntikkan ke dalam layanan seperti pengontrol atau IHostedService implementasi ASP.NET.
Atau, antarmuka klien seperti IGrainFactory atau IClusterClient dapat diperoleh dari ISiloHost:
var client = host.Services.GetService<IClusterClient>();
await client.GetGrain<IMyGrain>(0).Ping();
Kode klien dapat berjalan di luar kluster tempat Orleans kode biji-bijian dihosting. Oleh karena itu, klien eksternal bertindak sebagai konektor atau saluran ke kluster dan semua butir aplikasi. Biasanya, klien digunakan pada server web frontend untuk terhubung ke Orleans kluster yang berfungsi sebagai tingkat menengah dengan biji-bijian yang mengeksekusi logika bisnis.
Dalam penyiapan umum, server web frontend:
Sebelum klien biji-bijian dapat digunakan untuk melakukan panggilan ke biji-bijian yang Orleans dihosting dalam kluster, klien perlu dikonfigurasi, diinisialisasi, dan terhubung ke kluster.
Konfigurasi disediakan melalui UseOrleansClient dan beberapa kelas opsi tambahan yang berisi hierarki properti konfigurasi untuk mengonfigurasi klien secara terprogram. Untuk informasi selengkapnya, lihat Konfigurasi klien.
Pertimbangkan contoh konfigurasi klien berikut:
// 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
Ketika dimulai, klien akan dikonfigurasi dan tersedia melalui instans penyedia layanan yang dibangun.
Konfigurasi disediakan melalui ClientBuilder dan beberapa kelas opsi tambahan yang berisi hierarki properti konfigurasi untuk mengonfigurasi klien secara terprogram. Untuk informasi selengkapnya, lihat Konfigurasi klien.
Contoh konfigurasi klien:
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();
Terakhir, Anda perlu memanggil Connect()
metode pada objek klien yang dibangun untuk membuatnya terhubung ke Orleans kluster. Ini adalah metode asinkron yang mengembalikan Task
. Jadi Anda perlu menunggu penyelesaiannya dengan await
atau .Wait()
.
await client.Connect();
Melakukan panggilan ke biji-bijian dari klien tidak berbeda dengan melakukan panggilan seperti itu dari dalam kode biji-bijian. Metode yang sama IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) , di mana T
adalah antarmuka biji-bijian target, digunakan dalam kedua kasus untuk mendapatkan referensi biji-bijian. Perbedaannya adalah dalam objek pabrik apa yang dipanggil IGrainFactory.GetGrain. Dalam kode klien, Anda melakukannya melalui objek klien yang terhubung seperti yang ditunjukkan contoh berikut:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Task joinGameTask = player.JoinGame(game)
await joinGameTask;
Panggilan ke metode biji-bijian Task mengembalikan atau Task<TResult> sebagaimana diperlukan oleh aturan antarmuka biji-bijian. Klien dapat menggunakan await
kata kunci untuk secara asinkron menunggu yang dikembalikan Task
tanpa memblokir utas atau dalam beberapa kasus metode untuk memblokir utas Wait()
eksekusi saat ini.
Perbedaan utama antara melakukan panggilan ke biji-bijian dari kode klien dan dari dalam biji-bijian lain adalah model eksekusi satu utas biji-bijian. Biji-bijian dibatasi untuk di-utas tunggal oleh Orleans runtime, sementara klien mungkin multi-utas. Orleans tidak memberikan jaminan seperti itu di sisi klien, sehingga terserah klien untuk mengelola konkurensinya menggunakan konstruksi sinkronisasi apa pun yang sesuai untuk lingkungannya—kunci, peristiwa, dan Tasks
.
Ada situasi di mana pola respons permintaan sederhana tidak cukup, dan klien perlu menerima pemberitahuan asinkron. Misalnya, pengguna mungkin ingin diberi tahu ketika pesan baru telah diterbitkan oleh seseorang yang diikutinya.
Penggunaan Observers adalah salah satu mekanisme seperti yang memungkinkan mengekspos objek sisi klien sebagai target seperti biji-bijian untuk dipanggil oleh biji-bijian. Panggilan ke pengamat tidak memberikan indikasi keberhasilan atau kegagalan, karena dikirim sebagai pesan upaya terbaik satu arah. Jadi adalah tanggung jawab kode aplikasi untuk membangun mekanisme keandalan tingkat yang lebih tinggi di atas pengamat jika perlu.
Mekanisme lain yang dapat digunakan untuk mengirimkan pesan asinkron kepada klien adalah Aliran. Aliran mengekspos indikasi keberhasilan atau kegagalan pengiriman pesan individu, dan karenanya memungkinkan komunikasi yang andal kembali ke klien.
Ada dua skenario di mana klien kluster dapat mengalami masalah konektivitas:
Dalam kasus pertama, klien akan mencoba untuk terhubung ke silo. Jika klien tidak dapat terhubung ke silo apa pun, klien akan melemparkan pengecualian untuk menunjukkan apa yang salah. Anda dapat mendaftarkan IClientConnectionRetryFilter untuk menangani pengecualian dan memutuskan apakah akan mencoba kembali atau tidak. Jika tidak ada filter coba lagi yang disediakan, atau jika filter coba lagi mengembalikan false
, klien menyerah untuk selamanya.
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;
}
}
Ada dua skenario di mana klien kluster dapat mengalami masalah konektivitas:
Dalam kasus pertama, Connect
metode akan melemparkan pengecualian untuk menunjukkan apa yang salah. Ini biasanya (tetapi belum tentu) .SiloUnavailableException Jika ini terjadi, instans klien kluster tidak dapat digunakan dan harus dibuang. Fungsi filter coba lagi dapat secara opsional disediakan untuk Connect
metode yang dapat, misalnya, menunggu durasi yang ditentukan sebelum melakukan upaya lain. Jika tidak ada filter coba lagi yang disediakan, atau jika filter coba lagi mengembalikan false
, klien menyerah untuk selamanya.
Jika Connect
berhasil dikembalikan, klien kluster dijamin dapat digunakan sampai dibuang. Ini berarti bahwa bahkan jika klien mengalami masalah koneksi, klien akan mencoba untuk pulih tanpa batas waktu. Perilaku pemulihan yang tepat dapat dikonfigurasi pada objek yang GatewayOptions disediakan oleh ClientBuilder, misalnya:
var client = new ClientBuilder()
// ...
.Configure<GatewayOptions>(
options => // Default is 1 min.
options.GatewayListRefreshPeriod = TimeSpan.FromMinutes(10))
.Build();
Dalam kasus kedua, di mana masalah koneksi terjadi selama panggilan biji-bijian, SiloUnavailableException akan dilemparkan di sisi klien. Ini dapat ditangani seperti:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
try
{
await player.JoinGame(game);
}
catch (SiloUnavailableException)
{
// Lost connection to the cluster...
}
Referensi biji-bijian tidak valid dalam situasi ini; panggilan dapat dicoba kembali pada referensi yang sama nanti ketika koneksi mungkin telah dibuat ulang.
Cara yang disarankan untuk membuat klien eksternal dalam program yang menggunakan Host Generik .NET adalah dengan menyuntikkan IClusterClient instans singleton melalui injeksi dependensi, yang kemudian dapat diterima sebagai parameter konstruktor dalam layanan yang dihosting, pengontrol ASP.NET, dan sebagainya.
Catatan
Ketika menghosting Orleans silo dalam proses yang sama yang akan terhubung ke silo, tidak perlu membuat klien secara manual; Orleans akan secara otomatis menyediakan satu dan mengelola masa pakainya dengan tepat.
Saat menyambungkan ke kluster dalam proses yang berbeda (pada komputer yang berbeda), pola umumnya adalah membuat layanan yang dihosting seperti ini:
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();
}
}
Layanan ini kemudian terdaftar seperti ini:
await Host.CreateDefaultBuilder(args)
.UseOrleansClient(builder =>
{
builder.UseLocalhostClustering();
})
.ConfigureServices(services =>
{
services.AddHostedService<ClusterClientHostedService>();
})
.RunConsoleAsync();
Berikut adalah versi yang diperluas dari contoh yang diberikan di atas dari aplikasi klien yang terhubung ke Orleans, menemukan akun pemain, berlangganan pembaruan untuk sesi permainan, pemain adalah bagian dari dengan pengamat, dan mencetak pemberitahuan sampai program dihentikan secara manual.
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);
}
}
Umpan balik .NET
.NET adalah proyek sumber terbuka. Pilih tautan untuk memberikan umpan balik:
Pelatihan
Modul
Bangun aplikasi Orleans pertama Anda dengan ASP.NET Core 8.0 - Training
Pelajari cara membangun aplikasi terdistribusi cloud-native dengan Orleans.