次の方法で共有


Orleans 3.x から 7.0 に移行する

Orleans 7.0 では、ホスティング、カスタム シリアル化、不変性、グレイン抽象化の改善など、いくつかの役に立つ変更が導入されています。

移行

Orleansでグレインとストリームを識別する方法が変更されたため、アラーム、ストリーム、またはグレイン永続化を使用して既存のアプリケーションを Orleans 7.0 に移行することは、現在は簡単ではありません。

Orleans 7.0 へのローリング アップグレードを使用して、以前のOrleansバージョンを実行しているアプリケーションをスムーズにアップグレードすることはできません。 そのため、新しいクラスターのデプロイや前のクラスターの使用停止など、別のアップグレード戦略を使用します。 Orleans7.0 では、ネットワーク プロトコルが互換性を持たずに変更されます。つまり、クラスターには、以前のOrleans バージョンを実行している Orleans 7.0 ホストとホストを混在させることができません。

このような破壊的変更は、メジャー リリース全体でも、長年にわたって回避されてきました。 なぜ今あるのか。 主な理由は ID とシリアル化の 2 つです。 ID に関して、グレイン ID とストリーム ID は文字列で構成されるようになりました。 これにより、グレインはジェネリック型情報を適切にエンコードでき、アプリケーション ドメインへのストリームのマッピングが容易になります。 以前、Orleansは汎用的な穀物を表せない複雑なデータ構造を使用して穀物の種類を特定していたため、特殊なケースが発生しました。 ストリームは、 string 名前空間と Guid キーによって識別されました。これは効率的ですが、アプリケーション ドメインにマップすることは困難でした。 シリアル化はバージョン互換性を持つようになりました。 つまり、一連の規則に従って、特定の互換性のある方法で型を変更でき、シリアル化エラーなしでアプリケーションをアップグレードできます。 この機能は、アプリケーションの種類がストリームまたはグレイン ストレージに保持される場合に特に役立ちます。 次のセクションでは、主な変更について詳しく説明し、さらに詳しく説明します。

パッケージ化の変更

プロジェクトを Orleans 7.0 にアップグレードする場合は、次の操作を実行します。

  • すべてのクライアントで Microsoft.Orleans.Client を参照する必要があります。
  • すべてのサイロ (サーバー) で Microsoft.Orleans.Server を参照する必要があります。
  • 他のすべてのパッケージで、Microsoft.Orleans.Sdk を参照する必要があります。
    • "クライアント" と "サーバー" の両方のパッケージに、Microsoft..Sdk への参照が含まれます。Orleans
  • Microsoft.Orleans.CodeGenerator.MSBuildMicrosoft.Orleans.OrleansCodeGenerator.Build へのすべての参照を削除します。
    • KnownAssembly の使用を GenerateCodeForDeclaringAssemblyAttribute に置き換えます。
    • Microsoft.Orleans.Sdk パッケージでは、C# ソース ジェネレーター パッケージ (Microsoft.Orleans.CodeGenerator) が参照されています。
  • Microsoft.Orleans.OrleansRuntime へのすべての参照を削除します。
    • Microsoft.Orleans.Server パッケージでは、それの代わりの Microsoft.Orleans.Runtime が参照されています。
  • ConfigureApplicationParts の呼び出しを削除します。 アプリケーション パーツ が削除されました。 Orleans用の C# ソース ジェネレーターは、すべてのパッケージ (クライアントとサーバーを含む) に追加され、同等のアプリケーション パーツが自動的に生成されます。
  • Microsoft.Orleans.OrleansServiceBus への参照を Microsoft に置き換えます。Orleans.Streaming.EventHubs
  • リマインダーを使用している場合は、Orleans への参照を追加します。
  • ストリームを使用する場合は、Microsoft.Orleans への参照を追加します。ストリーミング

ヒント

Orleans のすべてのサンプルは Orleans 7.0 にアップグレードされており、変更内容の参照として使用できます。 詳しくは、各サンプルで行われた変更の一覧が示されている Orleans の問題 #8035 をご覧ください。

Orleans ディレクティブを使用するグローバル

すべての Orleans プロジェクトでは、Microsoft.Orleans.Sdk NuGet パッケージを直接または間接的に参照します。 Orleans プロジェクトが暗黙的な使用 (たとえば、) <ImplicitUsings>enable</ImplicitUsings>にするように構成されている場合、プロジェクトはOrleansOrleans.Hostingの両方の名前空間を暗黙的に使用します。 つまり、アプリ コードでは、これらの using ディレクティブは必要ありません。

詳細については、「ImplicitUsings」と「dotnet/orleans/src/Orleans.Sdk/build/Microsoft.Orleans.Sdk.targets」を参照してください。

ホスティング

ClientBuilder型は、UseOrleansClientIHostBuilder拡張メソッドに置き換えられます。 IHostBuilder 型は、Microsoft.Extensions.Hosting NuGet パッケージから取得されます。 つまり、別の依存関係挿入コンテナーを作成しなくても、 Orleans クライアントを既存のホストに追加できます。 クライアントは起動時にクラスターに接続します。 IHost.StartAsyncが完了すると、クライアントは自動的に接続します。 IHostBuilderに追加されたサービスは、登録の順序で開始されます。 たとえば、UseOrleansClientを呼び出す前にConfigureWebHostDefaultsを呼び出すと、ASP.NET Core が起動する前にOrleansが開始され、ASP.NET Core アプリケーションからクライアントにすぐにアクセスできるようになります。

以前の ClientBuilder 動作をエミュレートするには、別の HostBuilder を作成し、 Orleans クライアントで構成します。 IHostBuilderは、Orleans クライアントまたはOrleans サイロで構成できます。 すべてのサイロは、アプリケーションで使用できる IGrainFactoryIClusterClient のインスタンスを登録するため、クライアントを個別に構成することは不要であり、サポートされていません。

OnActivateAsyncOnDeactivateAsync のシグネチャの変更

Orleans では、アクティブ化と非アクティブ化の間にグレインでコードを実行できます。 この機能を使用して、ストレージからの状態の読み取りやライフサイクル メッセージのログ記録などのタスクを実行します。 Orleans 7.0 では、これらのライフサイクル メソッドのシグネチャが変更されました。

  • OnActivateAsync() は、CancellationToken パラメーターを受け取るようになりました。 CancellationTokenが取り消されたら、アクティブ化プロセスを中止します。
  • OnDeactivateAsync() は、DeactivationReason パラメーターと CancellationToken パラメーターを受け取るようになりました。 DeactivationReason は、アクティブ化が非アクティブ化される理由を示します。 この情報は、ログ記録と診断の目的で使用します。 CancellationTokenが取り消されたら、直ちに非アクティブ化プロセスを完了します。 ホストはいつでも失敗する可能性があるため、重要な状態の保持など、 OnDeactivateAsync に依存して重要なアクションを実行することはお勧めしません。

これらの新しいメソッドをオーバーライドするグレインの次の例を検討してください。

public sealed class PingGrain : Grain, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(ILogger<PingGrain> logger) =>
        _logger = logger;

    public override Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

POCO グレインと IGrainBase

Orleans のグレインは、Grain 基底クラスまたはその他のクラスから継承する必要がなくなりました。 この機能は、POCO グレイン と呼ばれます。 次のいずれかのような拡張メソッドにアクセスするには:

グレインは、 IGrainBase を実装するか、 Grainから継承する必要があります。 グレイン クラスに IGrainBase を実装する例を次に示します。

public sealed class PingGrain : IGrainBase, IPingGrain
{
    public PingGrain(IGrainContext context) => GrainContext = context;

    public IGrainContext GrainContext { get; }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

IGrainBase また、既定の実装で OnActivateAsyncOnDeactivateAsync を定義し、必要に応じてグレインがそのライフサイクルに参加できるようにします。

public sealed class PingGrain : IGrainBase, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(IGrainContext context, ILogger<PingGrain> logger)
    {
        _logger = logger;
        GrainContext = context;
    }

    public IGrainContext GrainContext { get; }

    public Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

シリアル化

Orleans 7.0 で最も負担の大きい変更は、バージョン トレラント シリアライザーの導入です。 この変更は、アプリケーションが進化する傾向があるために行われました。以前のシリアライザーでは既存の型へのプロパティの追加を許容できなかったため、開発者にとって大きな落とし穴が生じました。 一方、以前のシリアライザーは柔軟性があり、ジェネリック、ポリモーフィズム、参照追跡などの機能を含め、ほとんどの .NET 型を変更せずに表現できます。 交換はかなり遅れていましたが、型を忠実に表現することが依然として必要です。 そのため、 Orleans 7.0 では、.NET 型の忠実度の高い表現をサポートする代替シリアライザーが導入され、型の進化も可能になります。 新しいシリアライザーは、前のシリアライザーよりもはるかに効率的であり、結果として、エンド ツー エンドのスループット% 最大 170 になります。

詳しくは、Orleans 7.0 に関連する次の記事をご覧ください。

グレインの ID

グレインにはそれぞれ、グレインの型とそのキーで構成される一意の ID があります。 以前の Orleans バージョンでは、次のいずれかのグレイン キーをサポートするために、 GrainIdに複合型を使用しました。

この方法では、グレイン キーを扱うときに複雑さが伴います。 グレイン ID は、型とキーの 2 つのコンポーネントで構成されます。 型コンポーネントは、以前は数値型のコード、カテゴリ、3 バイトのジェネリック型情報で構成されていました。

グレイン ID は type/key形式になり、 typekey の両方が文字列になります。 最も一般的に使用されるグレイン キー インターフェイスは IGrainWithStringKeyです。 これにより、グレイン ID の動作が大幅に簡素化され、汎用グレイン型のサポートが向上します。

グレイン インターフェイスは、ハッシュ コードとジェネリック型パラメーターの文字列表現の組み合わせではなく、人間が判読できる名前を使用して表現されるようになりました。

新しいシステムはよりカスタマイズ可能であり、これらのカスタマイズは属性を使用して実行できます。

  • GrainTypeAttribute(String) のグレイン class は、そのグレイン ID の Type 部分を指定します。
  • DefaultGrainTypeAttribute(String)グレイン interfaceでは、グレイン参照を取得するときに既定で解決必要があるグレインのIGrainFactoryを指定します。 たとえば、IGrainFactory.GetGrain<IMyGrain>("my-key")を呼び出すときに、前述の属性が指定"my-type/my-key"場合、グレイン ファクトリはグレイン IMyGrainへの参照を返します。
  • GrainInterfaceTypeAttribute(String) ではインターフェイス名をオーバーライドできます。 このメカニズムを使用して明示的に名前を指定すると、既存のグレイン参照との互換性を損なうことなく、インターフェイス型の名前を変更できます。 ID がシリアル化される可能性があるため、この場合はインターフェイスにも AliasAttribute が必要であることに注意してください。 型エイリアスの指定について詳しくは、シリアル化に関するセクションをご覧ください。

前述のように、型の既定のグレイン クラスとインターフェイス名をオーバーライドすると、既存のデプロイとの互換性を損なうことなく、基になる型の名前を変更できます。

ストリームの ID

最初にリリースされたときの Orleans ストリームは、Guid を使うことによってのみ識別できました。 このアプローチはメモリ割り当ての点では効率的でしたが、意味のあるストリーム ID の作成が困難になりました。多くの場合、特定の目的に適したストリーム ID を特定するためにエンコードまたは間接参照が必要になります。

Orleans 7.0 では、ストリームは文字列を使用して識別されます。 Orleans.Runtime.StreamId structには、StreamId.NamespaceStreamId.KeyStreamId.FullKeyの 3 つのプロパティが含まれています。 これらのプロパティ値は、UTF-8 文字列でエンコードされます。 例については、「 StreamId.Create(String, String)」を参照してください。

BroadcastChannel での SimpleMessageStreams の置き換え

SimpleMessageStreams (SMS とも呼ばれます) は 7.0 で削除されます。 SMS は Orleans.Providers.Streams.PersistentStreamsと同じインターフェイスを持っていましたが、グレインからグレインへの直接呼び出しに依存しているため、その動作は非常に異なっていました。 混乱を避けるために、SMS が削除され、 Orleans.BroadcastChannel という新しい置換が導入されました。

BroadcastChannel は暗黙的なサブスクリプションのみをサポートしており、この場合は直接置き換えることができます。 明示的なサブスクリプションが必要な場合や、 PersistentStream インターフェイスを使用する必要がある場合 (たとえば、 EventHub が運用環境で使用されている間に SMS がテストで使用された場合)、 MemoryStream が最適な候補です。

BroadcastChannel は SMS と同じ動作をしますが、 MemoryStream は他のストリーム プロバイダーと同様に動作します。 次のブロードキャスト チャネルの使用例を検討してください。

// Configuration
builder.AddBroadcastChannel(
    "my-provider",
    options => options.FireAndForgetDelivery = false);

// Publishing
var grainKey = Guid.NewGuid().ToString("N");
var channelId = ChannelId.Create("some-namespace", grainKey);
var stream = provider.GetChannelWriter<int>(channelId);

await stream.Publish(1);
await stream.Publish(2);
await stream.Publish(3);

// Simple implicit subscriber example
[ImplicitChannelSubscription]
public sealed class SimpleSubscriberGrain : Grain, ISubscriberGrain, IOnBroadcastChannelSubscribed
{
    // Called when a subscription is added to the grain
    public Task OnSubscribed(IBroadcastChannelSubscription streamSubscription)
    {
        streamSubscription.Attach<int>(
          item => OnPublished(streamSubscription.ChannelId, item),
          ex => OnError(streamSubscription.ChannelId, ex));

        return Task.CompletedTask;

        // Called when an item is published to the channel
        static Task OnPublished(ChannelId id, int item)
        {
            // Do something
            return Task.CompletedTask;
        }

        // Called when an error occurs
        static Task OnError(ChannelId id, Exception ex)
        {
            // Do something
            return Task.CompletedTask;
        }
    }
}

構成のみを変更する必要があるため、 MemoryStream への移行が簡単です。 次のような MemoryStream の構成を検討してください。

builder.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(
    "in-mem-provider",
    _ =>
    {
        // Number of pulling agent to start.
        // DO NOT CHANGE this value once deployed, if you do rolling deployment
        _.ConfigurePartitioning(partitionCount: 8);
    });

OpenTelemetry

テレメトリ システムは Orleans 7.0 で更新され、以前のシステムは、メトリックの .NET メトリックやトレース用の ActivitySource などの標準化された .NET API を優先して削除されます。

この一環として、既存の Microsoft.Orleans.TelemetryConsumers.* パッケージが削除されます。 Orleansによって生成されたメトリックを、選択した監視ソリューションに統合することを効率化するために、新しいパッケージセットが検討されています。 いつものように、フィードバックと投稿をお寄せください。

dotnet-counters ツールは、アドホックな正常性監視と第 1 レベルのパフォーマンス調査のためのパフォーマンス監視機能を備えています。 Orleans カウンターの場合は、dotnet-counters ツールを使用して監視します。

dotnet counters monitor -n MyApp --counters Microsoft.Orleans

同様に、次のコードに示すように、 Microsoft.Orleans メーターを OpenTelemetry メトリックに追加します。

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddPrometheusExporter()
        .AddMeter("Microsoft.Orleans"));

分散トレースを有効にするには、次のコードに示すように OpenTelemetry を構成します。

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.SetResourceBuilder(ResourceBuilder.CreateDefault()
            .AddService(serviceName: "ExampleService", serviceVersion: "1.0"));

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.Orleans.Runtime");
        tracing.AddSource("Microsoft.Orleans.Application");

        tracing.AddZipkinExporter(options =>
        {
            options.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
        });
    });

前のコードでは、次のものを監視するように OpenTelemetry が構成されています。

  • Microsoft.Orleans.Runtime
  • Microsoft.Orleans.Application

アクティビティを伝達するには、AddActivityPropagation を呼び出します。

builder.Host.UseOrleans((_, clientBuilder) =>
{
    clientBuilder.AddActivityPropagation();
});

コア パッケージの機能を個別のパッケージにリファクタリングする

Orleans 7.0 では、拡張機能はOrleans.Coreに依存しない個別のパッケージに組み込まれています。 つまり、 Orleans.StreamingOrleans.Reminders、および Orleans.Transactions はコアから分離されました。 つまり、これらのパッケージは使用量に応じて支払いするもので、コア内にはこれらの機能専用のコードがありません。 このアプローチにより、コア API のサーフェスとアセンブリのサイズが縮小され、コアが簡略化され、パフォーマンスが向上します。 パフォーマンスに関して、 Orleans のトランザクションでは、以前は、潜在的なトランザクションを調整するために、すべてのメソッドに対していくつかのコードを実行する必要があります。 これで、その調整ロジックはメソッドごとに移動されます。

これはコンパイルに関する破壊的変更です。 Grain基底クラスで以前に定義されたメソッドを呼び出してアラームまたはストリームと対話する既存のコードは、拡張メソッドになったため中断する可能性があります。 拡張メソッドを修飾する必要があるため、 this ( GetReminders など) を指定しないこのような呼び出しを更新して、 this ( this.GetReminders() など) を含めます。 コンパイル エラーは、これらの呼び出しが更新されず、必要なコード変更が何が変更されたかを知らないと明らかでない場合に発生します。

トランザクション クライアント

Orleans 7.0 では、トランザクションを調整するための新しい抽象化 ( Orleans.ITransactionClient) が導入されています。 以前は、穀物のみが取引を調整することができました。 依存関係の挿入を介して使用できる ITransactionClientを使用すると、クライアントは中間グレインを必要とせずにトランザクションを調整することもできます。 次の例では、1 つのトランザクション内で、ある口座からクレジットを引き出し、別の口座に入金しています。 グレイン内、または依存性注入コンテナから ITransactionClient を取得した外部クライアントから、このコードを呼び出してください。

await transactionClient.RunTransaction(
  TransactionOption.Create,
  () => Task.WhenAll(from.Withdraw(100), to.Deposit(100)));

クライアントが調整するトランザクションの場合、クライアントは構成時に必要なサービスを追加する必要があります。

clientBuilder.UseTransactions();

BankAccount サンプルでは、ITransactionClient の使用方法が示されています。 詳細については、「Orleans トランザクション」を参照してください。

呼び出しチェーンの再入可能性

グレインはシングル スレッドであり、既定では要求を開始から完了まで 1 つずつ処理します。 つまり、グレインは既定では再入可能ではありません。 ReentrantAttributeをグレイン クラスに追加すると、グレインはシングル スレッドのまま、複数の要求をインターリーブ形式で同時に処理できます。 この機能は、内部状態を保持しないグレインや、HTTP 呼び出しの発行やデータベースへの書き込みなど、多くの非同期操作を実行する場合に役立ちます。 要求が相互に重複して発生する可能性がある場合、特に注意が必要です。非同期操作が完了し、メソッドが実行を再開するまでの間に、await ステートメントの前に観察されたグレインの状態が変化する可能性があります。

たとえば、次のグレインはカウンターを表します。 Reentrantとしてマークされており、複数の呼び出しを交差させることができます。 Increment() メソッドは、内部カウンターをインクリメントし、観察された値を返す必要があります。 ただし、Increment() メソッド本体では、await ポイントの前にグレインの状態が観察され、その後更新されるため、Increment()の複数のインターリーブ実行により、受信した_value呼び出しの合計数よりもIncrement()が少なくなる可能性があります。 これは、再入の不適切な使用によって発生したエラーです。

この問題を解決するには、 ReentrantAttribute を削除するだけで十分です。

[Reentrant]
public sealed class CounterGrain : Grain, ICounterGrain
{
    int _value;

    /// <summary>
    /// Increments the grain's value and returns the previous value.
    /// </summary>
    public Task<int> Increment()
    {
        // Do not copy this code, it contains an error.
        var currentVal = _value;
        await Task.Delay(TimeSpan.FromMilliseconds(1_000));
        _value = currentVal + 1;
        return currentValue;
    }
}

このようなエラーを防ぐため、グレインは既定では再入不可能になります。 欠点は、グレインが非同期操作の完了を待機している間に他の要求を処理できないため、実装で非同期操作を実行するグレインのスループットが低下することです。 これを軽減するため、Orleans には、特定のケースで再入を許可するいくつかのオプションが用意されています。

  • クラス全体の場合: グレインに ReentrantAttribute を配置すると、グレインへの要求が他の要求とインターリーブできるようになります。
  • メソッドのサブセットの場合: グレイン AlwaysInterleaveAttribute メソッドにを配置すると、そのメソッドへの要求が他の要求とインターリーブされ、他の要求がそのメソッドへの要求をインターリーブできるようになります。
  • メソッドのサブセットの場合: グレイン ReadOnlyAttribute メソッドにを配置すると、そのメソッドへの要求が他のReadOnly要求とインターリーブされ、他のReadOnly要求がそのメソッドに対する要求をインターリーブできるようになります。 この意味では、 AlwaysInterleaveのより制限された形式です。
  • 呼び出しチェーン内の要求の場合: RequestContext.AllowCallChainReentrancy()RequestContext.SuppressCallChainReentrancy() 、ダウンストリーム要求によるグレインの再入力を許可するオプトインとオプトアウトを許可します。 どちらの呼び出しも、要求を終了するときに破棄する 必要がある 値を返します。 そのため、次のように使用します。
public Task<int> OuterCall(IMyGrain other)
{
    // Allow call-chain reentrancy for this grain, for the duration of the method.
    using var _ = RequestContext.AllowCallChainReentrancy();
    await other.CallMeBack(this.AsReference<IMyGrain>());
}

public Task CallMeBack(IMyGrain grain)
{
    // Because OuterCall allowed reentrancy back into that grain, this method
    // will be able to call grain.InnerCall() without deadlocking.
    await grain.InnerCall();
}

public Task InnerCall() => Task.CompletedTask;

グレインごと、呼び出しチェーンごとに再入をオプトインします。 たとえば、A と B の 2 つのグレインについて考えてみましょう。グレイン A がグレイン B を呼び出す前に呼び出しチェーンの再入を有効にした場合、グレイン B はその呼び出しでグレイン A にコールバックできます。 ただし、グレイン B が呼び出しチェーンの再入を有効にしていない場合、グレイン A はグレイン B にコールバックすることはできません。 これは、グレイン単位、呼び出しチェーン単位で有効になります。

グレインでは、using var _ = RequestContext.SuppressCallChainReentrancy() を使って、呼び出しチェーンの再入情報が、呼び出しチェーンの下流に渡されるのを抑制することもできます。 これにより、後続の呼び出しが再入できなくなります。

ADO.NET 移行スクリプト

Orleans クラスタリング、永続化、および ADO.NET に依存するアラームとの前方互換性を確保するには、適切な SQL 移行スクリプトが必要です。

使われているデータベースのファイルを選び、順番に適用します。