個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Orleans の概要と基本構成のデプロイまで(目次)

Orleans は何を解決するために生まれ、どのようなフレームワークなのか

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の 構成と重要なプリミティブ(構成要素)

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の構成要素:フロントエンド

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の構成要素:グレイン

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の構成要素:サイロ

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の基本的な構成を App Service にデプロイする(1)構成の確認

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の基本的な構成を App Service にデプロイする(2)VNet の設定

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の基本的な構成を App Service にデプロイする(3)App Service の プライベートポート

記事はこちらです。
hiro128.hatenablog.jp
 

Orleans の基本的な構成を App Service にデプロイする(4)動作確認

記事はこちらです。
hiro128.hatenablog.jp
 
 

Xamarin から .NET へのアップグレード

公式でも、Xamarin から .NET へのアップグレードが案内されるようになりました。いよいよ Xamarin が終焉が近くなってきました。
 
learn.microsoft.com
 
Xamarin.Forms から MAUI への移行は .NET Upgrade Assistant が利用できます。
learn.microsoft.com
 
Xamarin.Android、Xamarin.iOS、Xamarin.Mac といった Xamarin プロジェクトは現状、「手動」でのアップグレードになってしまいます。
learn.microsoft.com
 
 

Orleans の構成要素:サイロ

目次

 
前の記事はこちらです。
hiro128.hatenablog.jp
 

サイロの役割

 

 

  • サイロは複数の仮想アクターつまりグレインをホストするコンテナです。サイロは単一ではその効力を発揮できません。複数のサイロによって構成されるグループを一緒に実行しクラスターを形成することでスケーラビリティや耐障害性を提供する役割を果たします。
  • サイロのクラスターにより、アプリケーションの状態は物理的に暗黙的・透過的に分割され、負荷に応じて並列に処理が実行されますが、開発者はそれを意識することはなく Orleans を利用するだけでスケーラビリティを確保することができます。
  • サイロのクラスターにより、分割されたアプリケーションの状態のバックアップを使用することで、回復力が向上し、障害からの回復性が高まります。

 
 

サイロの実装例

 
サイロの実装では以下を行います。

  • IP、ポート番号などネットワーク関連の設定
  • クラスタの設定、ロギングの設定

 
サイロのアプリケーションコードはデプロイするインスタンスが単一であっても複数(N個)であってもコード自体は同一になります。
(スケールアウト、スケールインへの対応)

 

using System.Net;
using Orleans.Configuration;
using OrleansPoc;

var siloHost = await StartSiloAsync();

// Silo 動作確認用
var app = WebApplication.Create();
app.MapGet("/", () => $"Silo is activated {DateTime.Now}");
await app.RunAsync();

static async Task<IHost> StartSiloAsync()
{
    var builder = Host.CreateDefaultBuilder()
        .UseOrleans(
            (context, builder) =>
            {
                var siloIPAddress = IPAddress.Parse(context.Configuration["WEBSITE_PRIVATE_IP"] ?? "");

                // WEBSITE_PRIVATE_PORTS は再起動で変更されるのでWEBSITE_PRIVATE_PORTS として取得し Parse すること
                var strPorts = (context.Configuration["WEBSITE_PRIVATE_PORTS"] ?? "").Split(',');
                if (strPorts.Length < 2)
                    throw new Exception("Insufficient private ports configured.");
                var (siloPort, gatewayPort) = (int.Parse(strPorts[0]), int.Parse(strPorts[1]));
                var connectionString =
                    context.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"];

                builder
                    .ConfigureEndpoints(siloIPAddress, siloPort, gatewayPort)
                    .Configure<ClusterOptions>(
                        options =>
                        {
                            options.ClusterId = "PoCCluster";
                            options.ServiceId = "OrleansPoC";
                        })
                    .UseAzureStorageClustering(
                    options => options.ConfigureTableServiceClient(connectionString))
                    .AddAzureTableGrainStorage(
                        "PocStore",
                     options => options.ConfigureTableServiceClient(connectionString)
                     )
                    .ConfigureLogging(logging => logging.AddConsole());
            });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

 
 
次の記事はこちらです。
hiro128.hatenablog.jp
 
 

Orleans の構成要素:グレイン

グレインの役割

 

 
グレインはユーザー定義のID(identity)、業務ロジック(behavior)、およびインメモリの状態(state)をカプセル化し、他のグレインとメッセージを送受信したり、クライアントからの要求に対するレスポンスを返します。
 
 

グレインの取得

 
グレインの物理的な場所に関して開発者は意識不要です。
同一インターフェースで指定したグレインのプロキシオブジェクトを同一の GrainID で取得すると、どのフロントエンド(クライアント)から呼び出したとしても同一のグレインの実体が呼び出されます。
 

 
 

グレインの実装例

 

インターフェース

using System.Threading.Tasks;
using Orleans;

namespace OrleansPoc
{
    public interface IWriteLargeData : Orleans.IGrainWithGuidKey
    {
        Task<string> WriteLargeData();
    }
}
|cs|<
 
** 実装
>|cs|
using Grains.Status;
using Microsoft.Extensions.Logging;
using OrleansPoc.Sevices;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Orleans.Runtime;

namespace OrleansPoc
{
    public class WriteLargeDataGrain : Orleans.Grain, IWriteLargeData
    {
        private readonly ILogger _logger;
        private readonly IPersistentState<WriteLargeDataState> _largeData;

        public WriteLargeDataGrain(
            [PersistentState("largeData", "PocStore")] IPersistentState<WriteLargeDataState> largeData,
            ILogger<HelloGrain> logger
            )
        {
            _largeData = largeData;
            _logger = logger;
        }

        async Task<string> IWriteLargeData.WriteLargeData()
        {
            _logger.LogInformation($"\n{DateTime.Now} WriteLargeData recieved.");

            var largeData = "";
            largeData = Enumerable.Range(0, 30000).Select(i => "ABCDEFGHIJKLMNOPQRSTUVWXYZ").Aggregate((x, y) => $"{x},{y}");

            _largeData.State.LargeData = largeData;
            await _largeData.WriteStateAsync();

            return "Grain Status の書き込みが終了しました。";
        }

    }
}

 
 

グレインのその他の特長

  • グレインはシングルスレッド実行が保証され、ロックや一貫性の問題の考慮が不要です。
  • 個々のグレインは Orleans のランタイムによって、開発者は意識することなく、必要に応じてページイン(アクティブ化)・ページアウト(非アクティブ化)されるため、メモリの使用量やグレインのロードバランシングは動的に最適化されます。
  • GrainID によって呼び出されるグレインの参照(プロキシオブジェクト)は、グレインの論理的なアイデンティティのみを含んでいます。アプリケーションコードはグレインの物理的な位置を意識することなくグレインと通信できます。グレインの物理的な位置は、Orleans のランタイムによって、障害やリソース管理、または呼び出された時点での当該のグレインが非アクティブになっているなどの理由で、時間の経過とともに変化する可能性がありますが、それを開発者がアプリケーションコードによって管理する必要はなく、意識する必要もありません。

 
 
次の記事はこちらです。
hiro128.hatenablog.jp
 
 

Orleans の構成要素:フロントエンド

フロントエンドの役割

 

フロントエンドは、www と Orleans の世界のゲートウェイの役割を果たします。
( REST API と Orleans 独自の世界を相互変換する境界)

公開されている REST API への HTTP リクエストを受信して、リクエストを処理すべき Grain に対してメソッドをコールし、 Grain からレスポンスを受け取り、 HTTP リクエストにレスポンスを返します。

なお、 フロントエンドは Orleans クラスターから見ればクライアントでもあります。
 
 

フロントエンドの実装例

 
Minimum なフロントエンドの実装としては、ASP.NET Core Minimal Web API のプロジェクトで REST API を定義し、Generic Host で Orleans クラアントを起動し、Grain プロキシオブジェクトのメソッドをコールし、戻り値を返します。
 

using Orleans.Configuration;
using OrleansPoc;

var host = await StartClientAsync();
var client = host.Services.GetRequiredService<IClusterClient>();
var hello = client.GetGrain<IHello>(Guid.NewGuid());
var writeLargeData = client.GetGrain<IWriteLargeData>(Guid.NewGuid());

var app = WebApplication.Create();
app.MapGet("/", async () => await hello.Call());
app.MapGet("/hello", async () => await hello.SayHello($"Konnichiwa!!"));
app.MapGet("/deactivate", async () => await hello.Deactivate());
app.MapGet("/writelargedata", async () => await writeLargeData.WriteLargeData());
await app.RunAsync();

static async Task<IHost> StartClientAsync()
{
    var connectionString = Environment.GetEnvironmentVariable("ORLEANS_AZURE_STORAGE_CONNECTION_STRING");

    var builder = new HostBuilder()
        .UseOrleansClient(client =>
        {
            client
                .UseAzureStorageClustering(
                    options => options.ConfigureTableServiceClient(connectionString))
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "PoCCluster";
                    options.ServiceId = "OrleansPoC";
                });
        })
        .ConfigureLogging(logging => logging.AddConsole());

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

 
 
次の記事はこちらです。
hiro128.hatenablog.jp
 
 

Orleans の基本的な構成を App Service にデプロイする(4)動作確認

目次

 
前の記事はこちらです。
hiro128.hatenablog.jp
 

Web App

 
フロントエンドにアクセスできて、サイロ01、サイロ02にアクセスできなければデプロイ成功です。

フロントエンド パブリックアクセス可
サイロ01 パブリックアクセス不可
サイロ02 パブリックアクセス不可

 
 

Azure Table

サイロ01、サイロ02のインスタンスのステータスが Active になっていればデプロイ成功です。