Kolumne

EnterpriseTales: Tracing in Enterprise Java – Woher des Weges?

Arne Limburg

©s&s_media

Bereits bevor klar war, dass die Eclipse Foundation Java EE übernehmen würde, wurde unter der Eclipse-Haube begonnen, das sogenannte MicroProfile zu spezifizieren. Der Name deutet darauf hin, dass in den Initiatoren von Anfang an der Gedanke keimte, dass das MicroProfile eines Tages ein Profil des Enterprise-Java-Standards werden könnte.

Das einzige bisher existierende Profil ist das Web Profile. Die Übergabe inklusive Umbenennung in Jakarta EE ist fast abgeschlossen. Nun stellt sich die Frage, ob das MicroProfile tatsächlich direkt in den Jakarta-EE-Standard übernommen werden sollte. Wir werden im Rahmen dieser Kolumne in loser Abfolge einige Teile des MicroProfile vor diesem Hintergrund untersuchen, durch die Praxisbrille betrachten und auch Alternativen beleuchten. Den Anfang macht der Eclipse-MicroProfile-OpenTracing-Standard.

Bei Tracing handelt es sich um die Nachverfolgung des Programmflusses. Dabei geht es nicht nur darum, wie ein Request seinen Weg durch den Server nimmt. Ebenso spielt eine Rolle, wie lange er in welchem Bereich verweilt, also welche Berechnung länger dauert oder welcher Ressourcenzugriff teurer ist.

In einem Monolithen können diese Informationen sehr leicht über geschicktes Logging erreicht werden. Gängige Log-Frameworks halten dafür sogar ein eigenes Log-Level bereit. Über einen Filter, der nach besagtem Log-Level filtert, lassen sich im Nachhinein der Programmfluss und auch die Dauer einzelner Aufrufe sehr leicht nachvollziehen. Das kann im Fehlerfall durchaus hilfreich sein.

Komplizierter ist es in einer Landschaft mit verteilten Systemen, zum Beispiel in einer Microservices-Landschaft. Denn hier schreiben verschiedene Services nicht in dasselbe Logfile. Es ist also komplizierter, die Tracinginformationen an eine gemeinsame Stelle zu bekommen, um sie zu analysieren. Zwar gibt es mittlerweile auch gute Mechanismen zur Log-Aggregation. In der Praxis hat sich jedoch gezeigt, dass es sinnvoll ist, Logging und Tracing zu trennen. Und so sind verschiedene Distributed-Tracing-Lösungen wie Dapper, Zipkin, HTrace, Jaeger etc. entstanden.

Die Idee hinter diesen Lösungen: Ein Tracingclient sammelt in der Applikation Tracinginformationen und schickt sie in regelmäßigen Abständen zu einem Tracingserver. Auf diesem können die Informationen dann eingesehen und analysiert werden.

Die verschiedenen Tracingclients unterscheiden sich natürlich darin, welche Informationen automatisch gesammelt werden und in welcher Form das geschieht. Um sich in einer großen Service-Landschaft nicht zu sehr an einen Hersteller zu binden, ist also ein gemeinsamer Standard notwendig.

OpenTracing to the Rescue

Einen solchen Standard zu schaffen, hat sich die Open-Tracing-Initiative zur Aufgabe gemacht. Zunächst definieren wir hier die Bausteine von Tracing.

Abb. 1: Die Baumstruktur eines Tracings

Abb. 1: Die Baumstruktur eines Tracings

Ein Trace beschreibt eine Business-Transaction und besteht aus einem Baum von Spans. Ein Span repräsentiert eine Operation, die zu einem bestimmten Zeitpunkt innerhalb des Trace beginnt und auch endet. Ausgehend von der Operation können weitere Unteroperationen entstehen, die dann wieder durch einen Span dargestellt werden, wodurch sich die Baumstruktur manifestiert (Abb. 1). Spans können dabei mit weiteren beliebigen Informationen angereichert werden.

Neben der Definition dieser Begriffe spezifiziert das OpenTracing API auch Schnittstellen für diverse Programmiersprachen. Neben Java werden aktuell Go, Python, JavaScript, C#, Objective-C, C++, Ruby und PHP unterstützt. Das API bietet einerseits Methoden zum Starten und Beenden von Spans innerhalb des aktuellen Trace. Außerdem gibt es Methoden, diese Information auf das aktuelle Übertragungsmedium (zum Beispiel HttpServletRequest) zu schreiben (inject), bzw. davon zu lesen (extract).

Kommt ein Request bei einem Server an, müssen also zunächst die bereits existierenden Spans vom Request gelesen werden. Während des Request können weitere Spans hinzukommen. Wenn der Request weitere Requests auslöst, muss er die aktuellen Tracinginformationen in den Request schreiben. So kann der aufzurufende Server sie seinerseits wieder auslesen, um eigene Spans hinzuzufügen.

Es bleibt der Tracingimplementierung überlassen, wie diese Informationen auf das Übertragungsmedium gebracht werden (zum Beispiel, welche HTTP-Header-Felder befüllt werden). Und tatsächlich verwendet jede aktuelle Tracingimplementierung (wie zum Beispiel Zipkin und Jaeger) andere Headerfelder und auch in den Headerfeldern andere Formate. Also müssen alle Server einer Landschaft dieselbe Tracinglösung verwenden, um kompatibel zu sein. Das heißt jedoch nicht, dass in der gesamten Service-Landschaft tatsächlich derselbe Client verwendet werden muss. Der Jaeger-Client beispielsweise erkennt beim extract, dass nicht „seine“ Header befüllt sind, sondern die Zipkin-Header. Er schaltet in den Zipkin-Modus und befüllt beim inject ebenfalls wieder die Zipkin-Header. Auch der Sleuth-Client lässt sich so konfigurieren, dass er andere Header befüllt.

Standardisierung im Rahmen des MicroProfiles

Der OpenTracing-Teil des Eclipse MicroProfiles baut auf der Java-Version des OpenTracing API auf. Zwar definiert das OpenTracing API die Schnittstelle, mit der Spans und Traces erzeugt und auch auf das Übertragungsmedium (HttpRequest) gebracht werden können. Es fehlt jedoch jegliche Definition, wann ein Span erzeugt werden soll.

Diese Lücke schließt die Eclipse MicroProfile OpenTracing Specification. Sie definiert, dass aus jedem eingehenden JAX RS Request die Tracinginformationen automatisch extrahiert werden müssen und dass zu Beginn ein Span gestartet und am Ende des Request auch wieder beendet wird. Außerdem ist spezifiziert, dass bei Verwendung des JAX-RS-Clients die aktuellen Tracinginformationen in den ausgehenden Request injiziert werden und dass auch bei einem ausgehenden Request zu Beginn ein Span gestartet werden muss, der am Ende ebenfalls wieder beendet wird.

Zusätzlich ermöglicht die @Traced-Annotation die Definition weiterer Spans um Methodenaufrufe innerhalb des Requests herum. Zu guter Letzt kann der Tracer auch per Dependency Injection injiziert werden, um manuell Spans zu starten oder zu beenden und weitergehende Kontrolle über die aktuellen Tracinginformationen zu bekommen.

Fazit

Bei der Beurteilung der Standardisierungsansätze im Tracingbereich gilt es zunächst, zwischen der OpenTracing-Initiative und dem Eclipse-MicroProfile-Open-Tracing-Standard zu unterscheiden.

Einerseits erscheint die Idee der OpenTracing-Initiative sinnvoll, ein gemeinsames Verständnis und ein gemeinsames API für Tracing zu schaffen. Nachvollziehbar ist ebenfalls, dass es innerhalb einer solchen Initiative nicht gelingt, ein einheitliches Format zum Übertragen von Tracinginformationen von einem Service zum nächsten zu schaffen. Dazu ist das Interesse der Hersteller zu groß, ihr spezifisches Format „durchzudrücken“.

Andererseits hat das zur Folge, dass man sich innerhalb einer (Micro-)Service-Architektur auf eine Implementierung einigen muss. Wenn zwei (Micro-)Services miteinander via REST kommunizieren und dabei ihre Tracinginformationen in unterschiedlichen Headerfeldern mitschicken, kann ein gemeinsames Tracing nicht funktionieren. Das stellt den Sinn eines Tracingstandards grundsätzlich infrage. Wozu noch ein Standard, wenn jeder Service sowieso dieselbe Implementierung verwenden muss?

Da hilft es wenig, wenn die OpenTracing-Initiative es als Ziel ausgibt, durch das standardisierte API den Vendor Lock-in zu verhindern. Denn wie wahrscheinlich ist es, dass ich in einer Service-Landschaft global die Tracingimplementierung wechsle, nachdem ich mich einmal für eine entschieden habe? Das würde ein Laufzeitupdate aller Services gleichzeitig bedeuten: Eine Situation, die in Microservices-Architekturen grundsätzlich zu vermeiden ist.

Ich möchte damit nicht die Sinnhaftigkeit der Open-Tracing-Initiative in Frage stellen. Das gemeinsame Verständnis von Spans und Traces ist eine große Errungenschaft und auch das gemeinsame API ist zweckmäßig.

Die Frage, die allerdings erlaubt sein muss: Ist es sinnvoll, auf Basis dieses API eine eigene MicroProfile-Spezifikation aufzubauen? Das suggeriert nämlich: Wenn alle Microservices sich an die MicroProfile-Spezifikation halten, haben wir ein gemeinsames Tracing. Und das ist aus genannten Gründen leider nicht der Fall, wenn unterschiedliche Tracer Verwendung finden. Zwar definiert die Spec, dass sich die Tracingimplementierung von außerhalb konfigurieren lassen muss. Eine Nutzung dieses Features würde jedoch implizieren, dass jeder Microservice mehrere Tracingclients mitbringt, um diese zur Laufzeit umschalten zu können – ein unrealistisches Szenario.

Lesen Sie auch: MVC 1.0 Tutorial – Einführung in das aktionsbasierte Java Webframework

Einen einzigen Mehrwert bietet die MicroProfile-Spezifikation gegenüber der Spezifikation der OpenTracing-Initiative. Es gibt die Möglichkeit, sich das OpenTracing API injecten zu lassen und die Klarstellung, welche Spans innerhalb der Applikation automatisch erzeugt und weitergereicht werden müssen.

Insgesamt ist die MicroProfile OpenTracing Specification damit recht dünn. Hier wäre etwas mehr Weitsicht und Innovationsfreude wünschenswert. Wenn es schon nicht möglich ist, das Austauschformat (also die HTTPHeader) zu standardisieren, so hätte man zumindest innerhalb der Applikation den größeren Kontext sehen und das Ganze in einen globalen Monitoringstandard einbetten können. Über diesen könnten die Stellen, an denen jetzt Tracing vorgeschrieben ist, mit standardisierten Events versehen werden. So schlägt es beispielsweise Romain Manni-Bucau in seinem Blog vor.

Denn schließlich interessiert die Information, dass gerade ein Request hereinkommt, hinausgeht oder abgeschlossen ist, nicht nur den Tracingclient, sie ist auch für Logging, Monitoring und Health Check interessant. Diese könnten dann auf besagte Events hören. Die tatsächliche Integration des OpenTracing API könnte mit solchen Events innerhalb einer CDI Bean geschehen.

Aber noch ist der MicroProfile-Standard kein Bestandteil von Jakarta EE. Wir werden in kommenden Kolumnen weitere sich im Praxiseinsatz ergebende Schwächen von Teilen des MicroProfiles aufzeigen. Eventuell werden diese Schwächen ja in den nächsten MicroProfile-Versionen behoben, bevor das Profil in den Jakarta-EE-Standard einfließt.

In diesem Sinne: Stay tuned.

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: