Verteilte Ablaufverfolgung und Korrelation über Service Bus-Messaging

Eine der häufigsten Herausforderungen bei der Microservice-Entwicklung besteht darin, Abläufe von einem Client aus über alle an der Verarbeitung beteiligten Dienste zu verfolgen. Eine solche Fähigkeit ist nützlich für das Debuggen, die Leistungsanalyse, A/B-Tests und andere gängige Diagnoseszenarien. Ein Teil des Problems besteht darin, die logischen Arbeitsschritte nachzuverfolgen. Dies umfasst neben der Auswertung der Nachrichtenverarbeitung auch die Latenz und externe Abhängigkeitsaufrufe. Der andere Teil ist die Korrelation dieser Diagnoseereignisse über Prozessgrenzen hinweg.

Wenn ein Producer eine Nachricht über eine Warteschlange sendet, geschieht dies in der Regel im Rahmen eines anderen logischen Vorgangs, der von einem anderen Client oder Dienst initiiert wird. Derselbe Vorgang wird vom Consumer fortgesetzt, sobald dieser eine Nachricht empfängt. Meistens gibt sowohl der Producer als auch der Consumer (sowie andere an der Verarbeitung beteiligten Dienste) Telemetrieereignisse aus, die zur Nachverfolgung des Vorgangsflusses und des Ergebnisses herangezogen werden. Um diese Ereignisse zu korrelieren und den Vorgang bis zum Ende zu verfolgen, muss jeder Dienst, der Telemetriedaten meldet, die einzelnen Ereignisse mit einem Ablaufverfolgungskontext kennzeichnen.

Microsoft Azure Service Bus-Messaging verfügt über definierte Nutzlasteigenschaften, die sowohl von Producern als auch von Consumern zur Übergabe dieser Ablaufverfolgungskontexte verwendet werden müssen. Das Protokoll basiert auf dem W3C-Ablaufverfolgungskontext.

Eigenschaftenname BESCHREIBUNG
Diagnostic-Id Ein eindeutiger Bezeichner eines externen Aufrufs, der vom Producer an die Warteschlange gesendet wird. Informationen zum Format finden Sie unter traceparent-Header des Ablaufverfolgungskontexts.

Automatische Ablaufverfolgung im Service Bus-Client für .NET

Die Klasse ServiceBusProcessor des Azure Messaging-Service Bus-Clients für .NET stellt Instrumentierungspunkte für die Ablaufverfolgung bereit, die von Ablaufverfolgungssystemen oder von Clientcode verwendet werden können. Die Instrumentierung ermöglicht die Nachverfolgung aller Aufrufe, die von Clientseite an den Service Bus-Messagingdienst gesendet werden. Wenn die Nachrichtenverarbeitung über ProcessMessageAsync von ServiceBusProcessor (Meldungshandlermuster) erfolgt, wird die Nachrichtenverarbeitung ebenfalls instrumentiert.

Nachverfolgung mithilfe von Azure Application Insights

Microsoft Application Insights bietet umfassende Funktionen zur Leistungsüberwachung, darunter die automatische Nachverfolgung von Anforderungen und Abhängigkeiten.

Installieren Sie je nach Projekttyp ggf. das Application Insights SDK:

  • ASP.NET: Installieren Sie Version 2.5-beta2 oder höher.
  • ASP.NET Core: Installieren Sie Version 2.2.0-beta2 oder höher. Über diese Links können Sie Details zur Erstellung von Ressourcen sowie zur Installation und Konfiguration des SDKs (falls erforderlich) abrufen. Weitere Informationen zu Nicht-ASP.NET-Anwendungen finden Sie im Artikel Azure Application Insights für Konsolenanwendungen.

Wenn Sie ProcessMessageAsync von ServiceBusProcessor (Meldungshandlermuster) zur Verarbeitung von Nachrichten verwenden, wird die Nachrichtenverarbeitung ebenfalls instrumentiert. Alle von Ihrem Dienst durchgeführten Service Bus-Aufrufe werden automatisch nachverfolgt und mit anderen Telemetrieelementen korreliert. Andernfalls finden Sie im folgenden Beispiel Informationen zur manuellen Nachverfolgung der Nachrichtenverarbeitung.

Nachrichtenverarbeitung nachverfolgen

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");
        }
    }
}

In diesem Beispiel wird für jede verarbeitete Nachricht Anforderungstelemetrie einschließlich Zeitstempel, Dauer und Ergebnis (Erfolg) gemeldet. Zur Telemetrie gehört auch eine Gruppe von Korrelationseigenschaften. Geschachtelte Ablaufverfolgungen und Ausnahmen, die während der Nachrichtenverarbeitung gemeldet werden, werden ebenfalls mit Korrelationseigenschaften gekennzeichnet, um sie als "untergeordnete Elemente" von RequestTelemetry auszuweisen.

Falls Sie während der Nachrichtenverarbeitung unterstützte externe Komponenten aufrufen, werden diese ebenfalls automatisch nachverfolgt und korreliert. Weitere Informationen zur manuellen Nachverfolgung und Korrelation finden Sie unter Nachverfolgen benutzerdefinierter Vorgänge mit dem Application Insights .NET SDK.

Wenn Sie zusätzlich zum Application Insights SDK externen Code ausführen, sollten Sie bei der Anzeige von Application Insights-Protokollen von einer längeren Dauer ausgehen.

Longer duration in Application Insights log

Dies bedeutet nicht, dass eine Verzögerung beim Empfangen der Nachricht aufgetreten ist. In diesem Szenario wurde die Nachricht bereits empfangen, weil die Nachricht als Parameter an den SDK-Code übergeben wird. Außerdem gibt das Tag Name in App Insights-Protokollen (Process) an, dass die Nachricht nun von Ihrem externen Ereignisverarbeitungscode verarbeitet wird. Dieses Problem bezieht sich nicht auf Azure. Stattdessen verweisen diese Metriken auf die Effizienz Ihres externen Codes, wenn die Nachricht bereits von Service Bus empfangen wurde.

Nachverfolgung mit OpenTelemetry

Version 7.5.0 der .NET-Clientbibliothek für Service Bus unterstützt OpenTelemetry im experimentellen Modus. Weitere Informationen finden Sie unter Distributed tracing (Verteilte Ablaufverfolgung in .NET SDK).

Nachverfolgung ohne Ablaufverfolgungssystem

Falls die automatische Nachverfolgung von Service Bus-Aufrufen von Ihrem Ablaufverfolgungssystem nicht unterstützt wird, empfiehlt es sich, das Ablaufverfolgungssystem oder die Anwendung entsprechend zu erweitern. In diesem Abschnitt werden die Diagnoseereignisse beschrieben, die vom Service Bus .NET-Client gesendet werden.

Der Service Bus .NET-Client verwendet zwei primitive Typen, um die .NET-Ablaufverfolgung zu instrumentieren: System.Diagnostics.Activity und System.Diagnostics.DiagnosticSource.

Activity dient als Ablaufverfolgungskontext, während DiagnosticSource ein Benachrichtigungsmechanismus ist.

Falls kein Listener für die DiagnosticSource-Ereignisse vorhanden ist, wird die Instrumentierung deaktiviert. In diesem Fall liegen die Instrumentierungskosten bei null. DiagnosticSource übergibt die Kontrolle vollständig an den Listener:

  • Der Listener steuert, welche Datenquellen und Ereignisse überwacht werden.
  • Der Listener steuert die Ereignisrate und den Stichprobenumfang.
  • Ereignisse werden mit einer Nutzlast gesendet, die vollständigen Kontext bereitstellt. Auf diese Weise können Sie das Message-Objekt während des Ereignisses abrufen und ändern.

Machen Sie sich mit dem DiagnosticSource-Benutzerleitfaden vertraut, bevor Sie mit der Implementierung fortfahren.

Als Nächstes erstellen wir einen Listener für Service Bus-Ereignisse in der ASP.NET Core-App, die die Protokollierung mithilfe von Microsoft.Extension.Logger durchführt. Dabei wird die System.Reactive.Core-Bibliothek verwendet, um DiagnosticSource zu abonnieren (aber auch ohne Bibliothek kann DiagnosticSource ganz einfach abonniert werden).

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();
    });
}

In diesem Beispiel werden die Dauer, das Ergebnis, der eindeutige Bezeichner und die Startzeit der einzelnen Service Bus-Vorgänge vom Listener protokolliert.

Events

Alle Ereignisse verfügen über die folgenden Eigenschaften, die der Spezifikation für offene Telemetrie entsprechen: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md.

  • message_bus.destination: Pfad zu Warteschlange/Thema/Abonnement.
  • peer.address: Vollqualifizierter Namespace.
  • kind: Producer, Consumer oder Client. Der Producer wird beim Senden, der Consumer beim Empfangen und der Client beim Speichern von Nachrichten verwendet.
  • componentservicebus

Alle Ereignisse haben auch die Eigenschaften Entity und Endpoint.

  • Entity – der Name der Entität (z. B. Warteschlange oder Thema)
  • Endpoint – die URL des Service Bus-Endpunkts

Instrumentierte Vorgänge

Im Folgenden finden Sie eine vollständige Liste der instrumentierten Vorgänge:

Vorgangsname Nachverfolgte 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 Der für ServiceBusProcessor festgelegte Prozessorrückruf. Eigenschaft „ProcessMessageAsync“
ServiceBusSessionProcessor.ProcessSessionMessage Der für ServiceBusSessionProcessor festgelegte Prozessorrückruf. Eigenschaft „ProcessMessageAsync“

Filtern und Stichprobenumfang

In einigen Fällen ist es wünschenswert, nur einen Teil der Ereignisse zu protokollieren, um den zusätzlichen Leistungsaufwand oder die Speicherauslastung zu reduzieren. Beispielsweise können Sie nur Stop-Ereignisse (wie im vorangehenden Beispiel) oder einen Stichprobenprozentsatz protokollieren. DiagnosticSource bietet zu diesem Zweck das IsEnabled-Prädikat. Weitere Informationen finden Sie unter Kontextbasierte Filterung in DiagnosticSource.

IsEnabled kann mehrmals für einen einzelnen Vorgang aufgerufen werden, um Leistungseinbußen zu minimieren.

IsEnabled wird in der folgenden Abfolge aufgerufen:

  1. IsEnabled(<OperationName>, string entity, null) entspricht IsEnabled("ServiceBusSender.Send", "MyQueue1"). Hinweis: Das Start- und das Stop-Ereignis am Ende fehlen. Verwenden Sie das Element, um bestimmte Vorgänge oder Warteschlangen herauszufiltern. Wenn durch die Rückrufmethode false zurückgegeben wird, werden keine Ereignisse für den Vorgang gesendet.

    • Für den Process-Vorgang und den ProcessSession-Vorgang wird ebenfalls ein IsEnabled(<OperationName>, string entity, Activity activity)-Rückruf empfangen. Verwenden Sie das Element, um Ereignisse auf der Basis von activity.Id-Eigenschaften oder Tags-Eigenschaften zu filtern.
  2. IsEnabled(<OperationName>.Start) entspricht IsEnabled("ServiceBusSender.Send.Start"). Durch diesen Code wird überprüft, ob das Start-Ereignis ausgelöst werden soll. Das Ergebnis wirkt sich nur auf das Start-Ereignis, nicht aber auf die weitere Instrumentierung aus.

Für das Stop-Ereignis ist keine IsEnabled-Eigenschaft vorhanden.

Wenn ein Vorgang eine Ausnahme zurückgibt, wird IsEnabled("ServiceBusSender.Send.Exception") aufgerufen. Sie können auch nur Exception-Ereignisse abonnieren und die übrigen Instrumentierungsschritte auslassen. In diesem Fall müssen solche Ausnahmen trotzdem behandelt werden. Da die weitere Instrumentierung deaktiviert ist, werden die Nachrichten zwischen Consumer und Producer ohne Ablaufverfolgungskontext weitergeleitet.

Sie können IsEnabled auch zur Implementierung von Stichprobenstrategien verwenden. Durch die Stichprobenentnahme auf der Grundlage von Activity.Id oder Activity.RootId wird über alle Ebenen eine konsistente Stichprobenentnahme sichergestellt (sofern die Weiterleitung über das Ablaufverfolgungssystem oder den eigenen Code erfolgt).

Falls mehrere DiagnosticSource-Listener für dieselbe Quelle vorhanden sind, muss nur ein Listener das Ereignis akzeptieren. Es kann also sein, dass IsEnabled nicht aufgerufen wird.

Nächste Schritte