Service Bus 메시징을 통한 분산 추적 및 상관관계

마이크로 서비스 개발의 일반적인 문제점 중 하나는 처리에 관련된 모든 서비스를 통해 클라이언트의 작업을 추적할 수 있다는 것입니다. 디버깅, 성능 분석, A/B 테스트 및 기타 일반적인 진단 시나리오에 유용합니다. 이 문제점의 한 부분은 논리적 작업 추적입니다. 메시지 처리 결과와 대기 시간 및 외부 종속성 호출이 포함됩니다. 다른 부분은 프로세스 경계를 벗어나는 이러한 진단 이벤트의 상관관계입니다.

생산자가 큐를 통해 메시지를 보내는 경우 일반적으로 다른 클라이언트 또는 서비스에서 시작된 다른 논리적 작업 범위에서 발생합니다. 소비자가 메시지를 수신하면 동일한 작업이 계속됩니다. 생산자와 소비자(작업을 처리하는 다른 서비스)가 둘 다 원격 분석 이벤트를 내보내 작업 흐름과 결과를 추적합니다. 이러한 이벤트와 추적 작업을 엔드투엔드에 상호 연결하려면 원격 분석을 보고하는 각 서비스가 추적 컨텍스트를 사용하여 모든 이벤트에 스탬프를 지정해야 합니다.

Microsoft Azure Service Bus 메시징에는 생산자와 소비자가 이러한 추적 컨텍스트를 전달하는 데 사용하는 페이로드 속성이 정의되어 있습니다. 프로토콜은 W3C 추적 컨텍스트를 기반으로 합니다.

속성 이름 설명
Diagnostic-Id 큐에 대한 생산자의 외부 호출 고유 식별자입니다. 형식은 W3C 추적 컨텍스트 추적 헤더를 참조하세요.

Service Bus .NET 클라이언트 자동 추적

.NET용 Azure Messaging Service Bus 클라이언트ServiceBusProcessor 클래스는 추적 시스템 또는 클라이언트 코드 조각에서 연결할 수 있는 추적 계측 지점을 제공합니다. 계측을 사용하면 클라이언트 쪽에서 모든 Service Bus 메시징 서비스 호출을 추적할 수 있습니다. 메시지 처리가 ServiceBusProcessor(메시지 처리기 패턴)의 ProcessMessageAsync를 사용하여 수행되는 경우 메시지 처리도 계측됩니다.

Azure Application Insights를 사용하여 추적

Microsoft Application Insights는 자동 요청 및 종속성 추적을 포함하여 다양한 성능 모니터링 기능을 제공합니다.

프로젝트 유형에 따라 Application Insights SDK를 설치합니다.

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 로그를 볼 때 시간이 더 오래 걸립니다.

Longer duration in Application Insights log

이는 메시지를 받는 시간이 지연된다는 의미가 아닙니다. 이 시나리오에서는 메시지가 SDK 코드에 대한 매개 변수로 전달되기 때문에 메시지는 이미 받았습니다. 그리고 App Insights 로그의 이름 태그(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.ActivitySystem.Diagnostics.DiagnosticSource를 사용하여 계측됩니다.

Activity는 추적 컨텍스트 역할을 하고, DiagnosticSource는 알림 메커니즘입니다.

DiagnosticSource 이벤트에 대한 수신기가 없는 경우 계측이 해제되어 계층 비용을 0으로 유지합니다. DiagnosticSource는 수신기에 모든 제어를 제공합니다.

  • 수신기가 수신 대기할 원본 및 이벤트를 제어함
  • 수신기가 이벤트율 및 샘플링을 제어함
  • 이벤트는 전체 컨텍스트를 제공하는 페이로드와 함께 전송되므로 이벤트 중 메시지 개체에 액세스하고 수정할 수 있음

구현을 계속 진행하기 전에 DiagnosticSource 사용자 가이드를 숙지하세요.

ASP.NET Core 앱에서 Microsoft.Extension.Logger로 로그를 기록하는 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 작업의 지속 기간, 결과, 고유 식별자 및 시작 시간을 기록합니다.

이벤트

모든 이벤트에는 개방형 원격 분석 사양(https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md)을 준수하는 다음과 같은 속성이 있습니다.

  • message_bus.destination – queue/topic/subscription 경로입니다.
  • peer.address – 정규화된 네임스페이스입니다.
  • kind – 생산자, 소비자 또는 클라이언트 중 하나입니다. 생산자는 메시지를 전송할 때 사용하고, 소비자는 수신할 때 사용하고, 클라이언트는 해결할 때 사용됩니다.
  • componentservicebus

모든 이벤트에는 EntityEndpoint 속성도 있습니다.

  • 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’ 이벤트만 기록하거나 이벤트의 샘플 비율을 기록할 수 있습니다. DiagnosticSourceIsEnabled 조건자로 이 작업을 수행하는 방법을 제공합니다. 자세한 내용은 DiagnosticSource의 컨텍스트 기반 필터링을 참조하세요.

성능 영향을 최소화하기 위해 단일 작업에 대해 IsEnabled를 여러 번 호출할 수 있습니다.

IsEnabled는 다음 순서로 호출됩니다.

  1. IsEnabled(<OperationName>, string entity, null)(예: IsEnabled("ServiceBusSender.Send", "MyQueue1")). 끝에 'Start' 또는 'Stop'이 없습니다. 특정 작업이나 큐를 필터링하는 데 사용합니다. 콜백 메서드가 false를 반환하면 작업에 대한 이벤트가 전송되지 않습니다.

    • ‘Process’ 및 ‘ProcessSession’ 작업의 경우 IsEnabled(<OperationName>, string entity, Activity activity) 콜백도 수신됩니다. activity.Id 또는 태그 속성을 기준으로 이벤트를 필터링하는 데 사용합니다.
  2. IsEnabled(<OperationName>.Start)(예: IsEnabled("ServiceBusSender.Send.Start")). ‘Start’ 이벤트를 실행할지 여부를 확인합니다. 결과는 'Start' 이벤트에만 영향을 주지만 추가 계측은 결과에 종속되지 않습니다.

'Stop' 이벤트에 대한 IsEnabled는 없습니다.

일부 작업 결과가 예외이면 IsEnabled("ServiceBusSender.Send.Exception")이 호출됩니다. ‘Exception’ 이벤트만 구독하고 나머지 계측을 차단할 수 있습니다. 이 경우에도 해당 예외를 처리해야 합니다. 다른 계측이 해제되었으므로 추적 컨텍스트가 메시지와 함께 소비자에서 생산자로 이동할 것으로 예상하면 안 됩니다.

IsEnabled를 사용하여 샘플링 전략을 구현할 수도 있습니다. Activity.Id 또는 Activity.RootId를 기준으로 샘플링하면 추적 시스템 또는 사용자 고유 코드를 통해 전파되는 한 모든 계층에서 일관된 샘플링이 보장됩니다.

동일한 원본에 대해 여러 개의 DiagnosticSource 수신기가 있는 경우 한 수신기만 이벤트를 수락하면 되므로 IsEnabled가 호출된다는 보장이 없습니다.

다음 단계