Konzepte verteilter .NET-Ablaufverfolgung
Verteilte Ablaufverfolgung ist eine Diagnosetechnik, die Entwicklern bei der Lokalisierung von Fehlern und Leistungsproblemen innerhalb von Anwendungen hilft, insbesondere bei solchen, die über mehrere Computer oder Prozesse verteilt sein können. Allgemeine Informationen dazu, wo verteilte Ablaufverfolgung sinnvoll ist, finden Sie in der Übersicht über verteilte Ablaufverfolgung.
Ablaufverfolgungen und Aktivitäten
Jedes Mal, wenn eine neue Anforderung von einer Anwendung empfangen wird, kann sie mit einer Ablaufverfolgung verknüpft werden. In Anwendungskomponenten, die in .NET geschrieben sind, werden Arbeitseinheiten in einer Ablaufverfolgung durch Instanzen von System.Diagnostics.Activity dargestellt und die Ablaufverfolgung als Ganzes bildet eine Baumstruktur dieser Aktivitäten, die sich möglicherweise über viele verschiedene Prozesse erstreckt. Die erste Aktivität, die für eine neue Anforderung erstellt wird, bildet den Stamm der Ablaufverfolgungsstruktur und verfolgt die Gesamtdauer und den Erfolg/das Scheitern der Verarbeitung der Anforderung. Optional können untergeordnete Aktivitäten erstellt werden, um die Arbeit in verschiedene Schritte zu unterteilen, die einzeln nachverfolgt werden können. Anhand einer Aktivität, die eine bestimmte eingehende HTTP-Anforderung in einem Webserver nachverfolgt, können beispielsweise untergeordnete Aktivitäten erstellt werden, um jede der Datenbankabfragen nachzuverfolgen, die zum Abschließen der Anforderung erforderlich waren. So können die Dauer und der Erfolg für jede Abfrage unabhängig voneinander aufgezeichnet werden. Aktivitäten können andere Informationen für jede Arbeitseinheit aufzeichnen, z. B. OperationName, Name-Wert-Paare, die als Tags bezeichnet werden, und Events. Der Name identifiziert die Art der ausgeführten Aufgabe, Tags können beschreibende Parameter der Aufgabe aufzeichnen, und Ereignisse sind ein einfacher Protokollierungsmechanismus zur Aufzeichnung von Diagnosemeldungen mit Zeitstempel.
Hinweis
Eine andere branchenübliche Bezeichnung für Arbeitseinheiten in einer verteilten Ablaufverfolgung sind „Spans“. .NET hat den Begriff „Aktivität“ schon vor vielen Jahren verwendet, bevor der Name „Span“ für dieses Konzept etabliert war.
Aktivitäts-IDs
Beziehungen zwischen übergeordneten und untergeordneten Elementen zwischen Aktivitäten in der verteilten Ablaufverfolgungsstruktur werden über eindeutige IDs hergestellt. Die .NET-Implementierung der verteilten Ablaufverfolgung unterstützt zwei ID-Schemas, den W3C-Standard TraceContext, der ab .NET 5 Standard ist, sowie eine ältere .NET-Konvention namens „Hierarchical“, die aus Gründen der Abwärtskompatibilität verfügbar ist. Activity.DefaultIdFormat steuert, welches ID-Schema verwendet wird. Im W3C-TraceContext-Standard wird jeder Ablaufverfolgung ein global eindeutiger 16-Byte-trace-id-Wert (Activity.TraceId) und jeder Aktivität innerhalb der Ablaufverfolgung ein eindeutiger 8-Byte-span-id-Wert (Activity.SpanId) zugewiesen. Jede Aktivität zeichnet die trace-id, die eigene span-id und die Span-id des übergeordneten Elements (Activity.ParentSpanId) auf. Da verteilte Ablaufverfolgungen die Arbeit über Prozessgrenzen hinweg verfolgen können, befinden sich übergeordnete und untergeordnete Aktivitäten möglicherweise nicht im selben Prozess. Die Kombination aus einer trace-id und einer übergeordneten span-id kann die übergeordnete Aktivität global eindeutig identifizieren, unabhängig davon, in welchem Prozess sie sich befindet.
Activity.DefaultIdFormat steuert, welches ID-Format zum Starten neuer Ablaufverfolgungen verwendet wird, aber standardmäßig wird beim Hinzufügen einer neuen Aktivität zu einer vorhandenen Ablaufverfolgung das Format übernommen, das die übergeordnete Aktivität verwendet. Wenn Sie Activity.ForceDefaultIdFormat auf TRUE festlegen, wird dieses Verhalten außer Kraft gesetzt, und alle neuen Aktivitäten werden mit dem DefaultIdFormat erstellt, auch wenn das übergeordnete Element ein anderes ID-Format verwendet.
Start- und Stoppaktivitäten
Jeder Thread in einem Prozess kann ein entsprechendes Activity-Objekt haben, das die an diesem Thread stattfindenden Aufgaben nachverfolgt. Auf dieses Objekt kann über Activity.Current zugegriffen werden. Die aktuelle Aktivität durchläuft automatisch alle synchronen Aufrufe für einen Thread und folgt asynchronen Aufrufen, die in verschiedenen Threads verarbeitet werden. Wenn Aktivität A die aktuelle Aktivität in einem Thread ist und der Code eine neue Aktivität B startet, wird B die neue aktuelle Aktivität in diesem Thread. Standardmäßig behandelt Aktivität B auch Aktivität A als übergeordnetes Element. Wenn Aktivität B später beendet wird, wird Aktivität A als aktuelle Aktivität im Thread wiederhergestellt. Wenn eine Aktivität gestartet wird, wird die aktuelle Uhrzeit als Activity.StartTimeUtc erfasst. Wenn sie beendet wird, wird Activity.Duration als Differenz zwischen der aktuellen Uhrzeit und der Startzeit berechnet.
Prozessgrenzenübergreifende Koordination
Übergeordnete Aktivitäts-IDs müssen über das Netzwerk übertragen werden, damit der empfangende Prozess Aktivitäten erstellen kann, die sich auf sie beziehen. So können Aufgaben über Prozessgrenzen hinweg verfolgt werden. Bei Verwendung des W3C-TraceContext-ID-Formats verwendet .NET auch die vom Standard empfohlenen HTTP-Header, um diese Informationen zu übertragen. Bei Verwendung des Hierarchical-ID-Formats verwendet .NET einen benutzerdefinierten request-id-HTTP-Header zum Übertragen der ID. Im Gegensatz zu vielen anderen Language Runtimes verstehen integrierte .NET-Bibliotheken wie der ASP.NET-Webserver und System.Net.Http nativ, wie Aktivitäts-IDs in HTTP-Nachrichten decodiert und codiert werden. Die Runtime versteht auch, wie die ID durch synchrone und asynchrone Aufrufe geleitet wird. Das bedeutet, dass .NET-Anwendungen, die HTTP-Nachrichten empfangen und ausgeben, automatisch am Fluss der verteilten Ablaufverfolgungs-IDs teilnehmen, ohne spezielle Codierung durch den App-Entwickler oder Abhängigkeiten von Bibliotheken von Drittanbietern. Bibliotheken von Drittanbietern können Unterstützung für die Übertragung von IDs über Nicht-HTTP-Nachrichtenprotokolle oder die Unterstützung benutzerdefinierter Codierungskonventionen für HTTP hinzufügen.
Nachverfolgungserfassung
Instrumentierter Code kann Activity-Objekte als Teil einer verteilten Ablaufverfolgung erstellen, aber die Informationen in diesen Objekten müssen übertragen und in einem zentralen persistenten Speicher serialisiert werden, damit die gesamte Ablaufverfolgung später sinnvoll überprüft werden kann. Es gibt verschiedene Telemetrieerfassungsbibliotheken, die diese Aufgabe übernehmen können, z. B. Application Insights, OpenTelemetry oder eine Bibliothek, die von einem Telemetrie- oder APM-Drittanbieter bereitgestellt wird. Alternativ können Entwickler mithilfe von System.Diagnostics.ActivityListener oder System.Diagnostics.DiagnosticListener eine eigene benutzerdefinierte Aktivitätstelemetrieerfassung erstellen. ActivityListener unterstützt die Beobachtung jeder beliebigen Aktivität, unabhängig davon, ob der*die Entwickler*in über Vorwissen über diese verfügt. Dies macht ActivityListener zu einer einfachen, flexiblen und universellen Lösung. Im Gegensatz dazu ist die Verwendung von DiagnosticListener ein komplexeres Szenario, bei dem der instrumentierte Code sich durch den Aufruf von DiagnosticSource.StartActivity anmelden und die Erfassungsbibliothek die genauen Namensinformationen kennen muss, die der instrumentierte Code beim Start verwendet hat. Die Verwendung von DiagnosticSource und DiagnosticListener ermöglicht es dem Ersteller und dem Listener, beliebige .NET-Objekte auszutauschen und angepasste Konventionen für die Informationsweitergabe festzulegen.
Stichproben
Um die Leistung in Anwendungen mit hohem Durchsatz zu verbessern, unterstützt die verteilte Ablaufverfolgung unter .NET das Abfragen nur einer Teilmenge von Ablaufverfolgungen, anstatt alle Ablaufverfolgungen aufzuzeichnen. Für Aktivitäten, die mit der empfohlenen ActivitySource.StartActivity-API erstellt wurden, können Telemetrieerfassungsbibliotheken die Erfassung mit dem ActivityListener.Sample-Rückruf steuern. Die Protokollierungsbibliothek kann die Aktivität wahlweise überhaupt nicht erstellen, sie mit minimalen Informationen erstellen, die für die Weitergabe von verteilten Ablaufverfolgungs-IDs erforderlich sind, oder sie mit vollständigen Diagnoseinformationen auffüllen. Diese Entscheidungen stellen einen Kompromiss zwischen dem zunehmenden Leistungsaufwand und der Erhöhung des Diagnosenutzens dar. Aktivitäten, die mit dem älteren Muster des Aufrufs von Activity.Activity und DiagnosticSource.StartActivity gestartet werden, können auch die DiagnosticListener-Erfassung unterstützen, indem sie zuerst DiagnosticSource.IsEnabled aufrufen. Selbst bei der Erfassung vollständiger Diagnoseinformationen ist die .NET-Implementierung auf Schnelligkeit ausgelegt – in Verbindung mit einem effizienten Erfassungsmechanismus kann eine Aktivität auf moderner Hardware in etwa einer Mikrosekunde erstellt, mit Daten aufgefüllt und übertragen werden. Durch Stichprobenentnahme können die Instrumentierungskosten für jede nicht aufgezeichnete Aktivität auf weniger als 100 Nanosekunden reduziert werden.
Nächste Schritte
Beispielcode für den Einstieg in verteilte Ablaufverfolgung in .NET-Anwendungen finden Sie unter Übersicht über verteilte Ablaufverfolgung.