Service Bus メッセージングを介した分散トレースおよび相関付け
マイクロサービス開発に共通する問題の 1 つは、クライアントからの操作を、処理に関係するすべてのサービスにおいてトレースする機能です。 これは、デバッグ、パフォーマンス分析、A/B テスト、およびその他の一般的な診断シナリオで役立ちます。 この問題の 1 つとして、処理の論理的な部分の追跡があります。 これにはメッセージの処理結果、待機時間、外部依存関係呼び出しが含まれます。 もう 1 つの部分は、これらの診断イベントのプロセス境界を越えた相関付けです。
プロデューサーがキューを介してメッセージを送信すると、通常、そのメッセージは他の論理操作 (他のクライアントまたはサービスが開始した) のスコープ内で発生します。 メッセージを受信すると、同じ操作がコンシューマーによって続行されます。 プロデューサーとコンシューマーの両方 (および操作を処理する他のサービス) は、操作のフローと結果をトレースするためにテレメトリ イベントを出力すると推定されます。 このようなイベントを相関付けて、操作をエンドツーエンドでトレースするために、テレメトリをレポートする各サービスは、すべてのイベントにトレース コンテキストをスタンプする必要があります。 開発者が既定でこのテレメトリをすべて出力できるようにするライブラリの 1 つが NServiceBus です。
Microsoft Azure Service Bus メッセージングによってペイロード プロパティが定義され、プロデューサーとコンシューマーはこれを使用してトレース コンテキストを渡す必要があります。 プロトコルは W3C トレースコンテキストに基づいています。
プロパティ名 | 説明 |
---|---|
Diagnostic-Id | プロデューサーからキューへの外部呼び出しの一意識別子。 形式については、「W3C Trace-Context traceparent header」(W3C トレースコンテキスト traceparent ヘッダー) を参照してください |
Service Bus .NET クライアントの自動トレース
.NET 用 Azure Messaging Service Bus クライアントの ServiceBusProcessor
クラスにより、トレース インストルメンテーション ポイントが提供され、トレーシング システムまたはクライアント コードの一部でこれを受け取ることができます。 インストルメンテーションによって、すべての呼び出しをクライアント側から Service Bus メッセージング サービスまで追跡できるようになります。 メッセージの処理が ServiceBusProcessor
の ProcessMessageAsync
(メッセージ ハンドラー パターン) を使用して行われる場合は、メッセージの処理もインストルメント化されます。
Azure Application Insights で追跡する
Microsoft Application Insights では、自動的な要求と依存関係の追跡も含め、豊富なパフォーマンス監視機能が提供されます。
プロジェクト タイプに応じて次のいずれかの Application Insights SDK をインストールします。
- ASP.NET - バージョン 2.5-beta2 以上をインストールします
- ASP.NET Core - バージョン 2.2.0-beta2 以上をインストールします。 これらのリンクには、SDK のインストール、リソースの作成、SDK の構成 (必要な場合) に関する説明があります。 ASP.NET 以外のアプリケーションについては、コンソール アプリケーションのための Azure Application Insights に関する記事をご覧ください。
ServiceBusProcessor
の ProcessMessageAsync
(メッセージ ハンドラー パターン) を使用してメッセージを処理する場合、メッセージの処理もインストルメント化されます。 サービスによって実行されたすべての Service Bus の呼び出しは自動的に追跡され、他のテレメトリ項目と関連付けられます。 それ以外の場合は、手動でのメッセージ追跡について次の例をご覧ください。
メッセージ処理のトレース
async Task ProcessAsync(ProcessMessageEventArgs args)
{
ServiceBusReceivedMessage message = args.Message;
if (message.ApplicationProperties.TryGetValue("Diagnostic-Id", out var objectId) && objectId is string diagnosticId)
{
var activity = new Activity("ServiceBusProcessor.ProcessMessage");
activity.SetParentId(diagnosticId);
// If you're using Microsoft.ApplicationInsights package version 2.6-beta or higher, you should call StartOperation<RequestTelemetry>(activity) instead
using (var operation = telemetryClient.StartOperation<RequestTelemetry>("Process", activity.RootId, activity.ParentId))
{
telemetryClient.TrackTrace("Received message");
try
{
// process message
}
catch (Exception ex)
{
telemetryClient.TrackException(ex);
operation.Telemetry.Success = false;
throw;
}
telemetryClient.TrackTrace("Done");
}
}
}
この例では、処理されるメッセージごとに、タイムスタンプ、期間、結果 (成功) を含む要求テレメトリが報告されます。 テレメトリには、相関関係プロパティのセットも含まれます。 メッセージ処理中に報告された入れ子のトレースと例外にも、RequestTelemetry
の "子" であることを表す相関関係プロパティがスタンプされます。
サポートされる外部コンポーネントへの呼び出しをメッセージ処理中に行った場合は、それも自動的に追跡されて相関付けられます。 手動での追跡と相関付けについて詳しくは、「Application Insights .NET SDK でカスタム操作を追跡する」をご覧ください。
Application Insights SDK に加えて外部コードを実行している場合は、Application Insights ログを表示するときの所要時間が長くなることが予想されます。
これは、メッセージの受信で遅延があったことを意味するわけではありません。 このシナリオでは、メッセージは SDK コードにパラメーターとして渡されるため、メッセージは既に受信されています。 また、App Insights ログの name タグ (Process) は、メッセージが現在、外部イベント処理コードによって処理されていることを示しています。 この問題は、Azure に関連したものではありません。 メッセージが Service Bus から既に受信されていることを考えると、これらのメトリックは外部コードの効率を意味します。
OpenTelemetry を使用した追跡
Service Bus .NET クライアント ライブラリ バージョン 7.5.0 以降では、試験モードで OpenTelemetry がサポートされています。 詳しくは、.NET SDK での分散トレースに関するページをご覧ください。
トレース システムなしで追跡する
ご使用のトレーシング システムで Service Bus 呼び出しの自動追跡がサポートされない場合は、トレーシング システムまたはアプリケーションにそのようなサポートを追加することを検討してください。 このセクションでは、Service Bus .NET クライアントによって送信される診断イベントについて説明します。
Service Bus .NET クライアントは、.NET トレース プリミティブ System.Diagnostics.Activity および System.Diagnostics.DiagnosticSource を使用してインストルメント化されます。
Activity
はトレース コンテキストとして使用され、DiagnosticSource
は通知メカニズムです。
DiagnosticSource イベントのリスナーがない場合は、インストルメント化はオフになり、インストルメンテーション コストは 0 のままになります。 DiagnosticSource はすべてのコントロールをリスナーに与えます。
- リスナーは、リッスンするソースとイベントを制御します。
- リスナーは、イベント レートおよびサンプリングを制御します。
- イベントは、すべてのコンテキストを提供するペイロードと一緒に送信されるため、ユーザーがイベント中にメッセージ オブジェクトにアクセスして変更できます。
実装を進める前に、DiagnosticSource ユーザー ガイドを読むことをお勧めします。
Microsoft.Extension.Logger でログを書き込む ASP.NET Core アプリに、Service Bus イベントのリスナーを作成します。 これは System.Reactive.Core ライブラリを使用して DiagnosticSource をサブスクライブします (このライブラリなしでも簡単に DiagnosticSource をサブスクライブできます)。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory, IApplicationLifetime applicationLifetime)
{
// configuration...
var serviceBusLogger = factory.CreateLogger("Azure.Messaging.ServiceBus");
IDisposable innerSubscription = null;
IDisposable outerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
{
// subscribe to the Service Bus DiagnosticSource
if (listener.Name == "Azure.Messaging.ServiceBus")
{
// receive event from Service Bus DiagnosticSource
innerSubscription = listener.Subscribe(delegate (KeyValuePair<string, object> evnt)
{
// Log operation details once it's done
if (evnt.Key.EndsWith("Stop"))
{
Activity currentActivity = Activity.Current;
serviceBusLogger.LogInformation($"Operation {currentActivity.OperationName} is finished, Duration={currentActivity.Duration}, Id={currentActivity.Id}, StartTime={currentActivity.StartTimeUtc}");
}
});
}
});
applicationLifetime.ApplicationStopping.Register(() =>
{
outerSubscription?.Dispose();
innerSubscription?.Dispose();
});
}
この例では、リスナーが、Service Bus の各操作の期間、結果、一意識別子、開始時刻を記録します。
events
すべてのイベントには、公開されているテレメトリ仕様 (https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md ) に準拠する次のプロパティがあります。
message_bus.destination
– キュー/トピック/サブスクリプションのパスpeer.address
– 完全修飾名前空間kind
– producer、consumer、または client のいずれか。 メッセージを送信する場合は producer、受信する場合は consumer、設定する場合は client を使用します。component
–servicebus
すべてのイベントには、Entity
と Endpoint
プロパティもあります。
Entity
- エンティティ (キュー、トピックなど) の名前Endpoint
- Service Bus エンドポイント URL
インストルメント化された操作
インストルメント化された操作の全一覧を次に示します。
操作の名前 | 追跡される API |
---|---|
ServiceBusSender.Send | ServiceBusSender.SendMessageAsync ServiceBusSender.SendMessagesAsync |
ServiceBusSender.Schedule | ServiceBusSender.ScheduleMessageAsync ServiceBusSender.ScheduleMessagesAsync |
ServiceBusSender.Cancel | ServiceBusSender.CancelScheduledMessageAsync ServiceBusSender.CancelScheduledMessagesAsync |
ServiceBusReceiver.Receive | ServiceBusReceiver.ReceiveMessageAsync ServiceBusReceiver.ReceiveMessagesAsync |
ServiceBusReceiver.ReceiveDeferred | ServiceBusReceiver.ReceiveDeferredMessagesAsync |
ServiceBusReceiver.Peek | ServiceBusReceiver.PeekMessageAsync ServiceBusReceiver.PeekMessagesAsync |
ServiceBusReceiver.Abandon | ServiceBusReceiver.AbandonMessagesAsync |
ServiceBusReceiver.Complete | ServiceBusReceiver.CompleteMessagesAsync |
ServiceBusReceiver.DeadLetter | ServiceBusReceiver.DeadLetterMessagesAsync |
ServiceBusReceiver.Defer | ServiceBusReceiver.DeferMessagesAsync |
ServiceBusReceiver.RenewMessageLock | ServiceBusReceiver.RenewMessageLockAsync |
ServiceBusSessionReceiver.RenewSessionLock | ServiceBusSessionReceiver.RenewSessionLockAsync |
ServiceBusSessionReceiver.GetSessionState | ServiceBusSessionReceiver.GetSessionStateAsync |
ServiceBusSessionReceiver.SetSessionState | ServiceBusSessionReceiver.SetSessionStateAsync |
ServiceBusProcessor.ProcessMessage | ServiceBusProcessor で設定されるプロセッサ コールバック。 ProcessMessageAsync プロパティ |
ServiceBusSessionProcessor.ProcessSessionMessage | ServiceBusSessionProcessor で設定されるプロセッサ コールバック。 ProcessMessageAsync プロパティ |
フィルター処理およびサンプリング
場合によっては、パフォーマンスのオーバーヘッドや記憶域の消費量を抑えるためにイベントの一部分のみを記録する必要があります。 'Stop' イベントのみを記録するか (前の例と同様)、イベントの一定の割合をサンプリングします。
DiagnosticSource
では、IsEnabled
術後を使用してこれを実現する方法が提供されます。 詳しくは、DiagnosticSource でのコンテキストに基づくフィルター処理に関する記事をご覧ください。
IsEnabled
を 1 つの操作で複数回呼び出して、パフォーマンスへの影響を最小限にすることができます。
IsEnabled
は次のように呼び出します。
IsEnabled(<OperationName>, string entity, null)
を、たとえばIsEnabled("ServiceBusSender.Send", "MyQueue1")
と指定します。 最後に 'Start' も 'Stop' もないことに注意してください。 これを使用して、特定の操作またはキューをフィルター処理で除外します。 コールバック メソッドからfalse
が返されると、その操作のイベントは送信されません。- 'Process' 操作および 'ProcessSession' 操作では
IsEnabled(<OperationName>, string entity, Activity activity)
コールバックも受け取ります。 これを使用して、activity.Id
または Tags プロパティに基づいてイベントをフィルター処理します。
- 'Process' 操作および 'ProcessSession' 操作では
IsEnabled(<OperationName>.Start)
を、たとえばIsEnabled("ServiceBusSender.Send.Start")
と指定します。 'Start' イベントが起動されるかどうかを確認します。 この結果は 'Start' イベントのみに影響し、それ以降のインストルメンテーションがそれに依存することはありません。
'Stop' イベントに対する IsEnabled
はありません。
一部の操作の結果が例外になると、IsEnabled("ServiceBusSender.Send.Exception")
が呼び出されます。 'Exception' イベントをサブスクライブするだけで、その後のインストルメンテーションを防ぐことができます。 このケースでは、例外も処理する必要があります。 他のインストルメンテーションが無効になっているため、トレース コンテキストがメッセージと一緒にコンシューマーからプロデューサーに送られることを期待しないでください。
IsEnabled
を使用して、サンプリング戦略を実装することもできます。 Activity.Id
または Activity.RootId
に基づくサンプリングにより、すべての層で一貫性のあるサンプリングが保証されます (トレーシング システムまたはユーザー独自のコードによって伝達される場合)。
同じソースに対して複数の DiagnosticSource
リスナーが存在するとき、イベントを受け取るには 1 つのリスナーだけで十分です。したがって、IsEnabled
の呼び出しは保証されません。
次のステップ
- Application Insights の相関付け
- Application Insights による依存関係の監視: REST、SQL、その他の外部リソースによる処理速度の低下が発生しているかどうかを確認します。
- Application Insights .NET SDK でカスタム操作を追跡する