Optimierung des Kafka-Tracings mit OpenTelemetry: Mehr Visibility und Performance

Entdecken Sie, wie OpenTelemetry Ihr Kafka-Tracing verbessern kann, um betriebliche Einblicke und die Anwendungs-Performance zu verbessern.

Veröffentlicht 8 Minuten Lesedauer

Idealerweise sollten Sie Distributed Tracing verwenden, um Anfragen durch Ihr System zu verfolgen, aber Kafka entkoppelt Producer und Consumer, was bedeutet, dass es keine direkten Transaktionen gibt, die zwischen ihnen verfolgt werden müssen. Kafka verwendet auch asynchrone Prozesse, die implizite, nicht explizite Abhängigkeiten haben. Daher ist es schwierig zu verstehen, wie Ihre Microservices zusammenarbeiten.

Es ist jedoch möglich, Ihre Kafka-Cluster mit Distributed Tracing und OpenTelemetry zu überwachen. Anschließend können Sie Ihre Traces in einem quelloffenen Distributed-Tracing-Tool wie Jaeger oder einer vollständigen Observability-Plattform wie New Relic analysieren und visualisieren. In diesem Beitrag zeige ich Ihnen anhand einer einfachen Anwendung, wie das geht.

Designüberlegungen und Richtlinien

OpenTelemetry gibt es normalerweise in zwei Varianten:

Varianten

Wenn ich über diese Varianten spreche, verwende ich normalerweise die obige Analogie. Sie können entweder einen fertigen Kuchen kaufen und genießen oder die einzelnen Zutaten erwerben und den Kuchen selbst backen. Bei OpenTelemetry ist der Ansatz sehr ähnlich und die Varianten sind:

  • Zero-Code-Instrumentierung: Bei diesem Ansatz verwenden Sie einen OpenTelemetry-Agent und hängen ihn beim Start an Ihre Anwendung an. Dieser Agent wird dann seine Magie entfalten und automatisch (ohne Änderungen am Quellcode) viele Telemetriesignale (Metriken, Traces und Logs) und Einblicke in Ihre Anwendung bereitstellen.
    • Vorteile:
      • Schneller Einstieg
      • Keine Quellcodeänderungen
    • Nachteile:
      • Eingeschränkte Anpassung
      • Die Transparenz Ihrer Anwendung ist möglicherweise eingeschränkt
         
  • Manuelle Instrumentierung: Bei dieser Option müssen Sie Ihrem Quellcode einige Abhängigkeiten und Pakete hinzufügen, die Sie im Rahmen Ihres regulären Software-Dev-Lifecycle (SDLC) verwalten müssen. Allerdings haben Sie dadurch auch die Möglichkeit, Ihre Instrumentierung spezifischer und individueller zu gestalten. Sie können Ihrer Telemetrie problemlos benutzerdefinierte Metriken, Traces und Attribute hinzufügen.
    • Vorteile:
      • Deutlich mehr Flexibilität durch die Anpassung der Telemetrie
      • Einfaches Hinzufügen, Entfernen und Optimieren der Tiefe Ihrer Instrumentierung
    • Nachteile:
      • Abhängigkeiten in Ihrem Quellcode
      • Mehr Aufwand bei der Implementierung

Beispielanwendung

Die Beispielanwendung (verfügbar in diesem öffentlichen GitHub-Repository), die ich in diesem Blog verwende, basiert auf dieser High-Level-Architektur:

 

Beispielanwendung

Es enthält diese Komponenten:

  • kafka-java-producer: eine Java-Spring-Boot-Anwendung, die als Producer Nachrichten an ein Kafka-Topic sendet
  • Kafka-Broker
  • kafka-java-consumer: eine Java-Spring-Boot-Anwendung, die ein Kafka-Topic abonniert und Nachrichten daraus liest. Diese Komponente ruft auch einen externen REST-API-Service auf (der nicht in unserer Kontrolle liegt).
  • kafka-java-service: eine nachgelagerte Java-Spring-Boot-Anwendung, die vom Consumer-Service aufgerufen wird

Zero-Code-Instrumentierung

Beginnen wir mit der Zero-Code-Instrumentierung, auch bekannt als automatische Instrumentierung.

Konfiguration

Jeder der verschiedenen Services enthält ein `run.sh`-Skript, um den Service zum Laufen zu bringen. Das Skript sieht folgendermaßen aus:

Konfiguration

Wichtig ist dabei die erste Zeile. Hier definieren wir die JAVA_TOOL_OPTIONS und konfigurieren den `-javaagent`, um auf den Speicherort des OpenTelemetry-Java-Agent zu verweisen. 

Die nächsten drei Zeilen legen fest, wie wir mit den verschiedenen Telemetriesignalen umgehen möchten. In unserem Fall definiere ich, dass die Traces, Metriken und Logs über das OpenTelemetry Line Protocol (OTLP) exportiert werden sollen.

Es gibt drei weitere Umgebungsvariablen, die zu konfigurieren recht wichtig sind:

  • OTEL_EXPORTER_OTLP_ENDPOINT: das Zielsystem, in das wir die Daten exportieren möchten, d. h. unser Telemetrie-Backend. In meinem Fall ist das definitiv New Relic, deshalb konfiguriere ich den nativen OTLP-Endpunkt von New Relic.
  • OTEL_EXPORTER_OTLP_HEADERS: Der obige Exporter-Endpunkt ist eine offene API, daher müssen wir einen API-Schlüssel konfigurieren. Im Fall von New Relic ist dies ein New Relic Lizenzschlüssel.
  • OTEL_SERVICE_NAME: Idealerweise möchten wir dem Service einen aussagekräftigen Namen geben, damit New Relic daraus eine entsprechende Entity erstellen kann.

Dies ist im Grunde alles, was wir konfigurieren müssen. Alles andere erledigt der OpenTelemetry-Java-Agent. Es ist nicht nötig, etwas an unserem Quellcode zu ändern.

Observability

Sehen wir uns an, welchen Grad an Visibility in die Services wir durch Zero-Code-Instrumentierung erreichen können.

Wenn ich zu meinem New Relic Konto navigiere, kann ich sehen, dass das Reporting aller Services in separate Entities erfolgt.

Observability

Beginnen wir mit der Erkundung des kafka-java-producer-Service.

Die Übersicht zeigt mir alle wichtigen Telemetrie- und Metrikdaten, auf die ich mich konzentrieren sollte.

Fazit

Im Rahmen dieses Blogs interessiere ich mich vor allem für den Abschnitt Distributed Tracing, also sehen wir diesen Bereich genauer an.

Distributed Tracing

Durch die Betrachtung eines einzelnen Trace kann ich detaillierte Informationen dazu sehen, wie lange die Ausführung dieses bestimmten Trace gedauert hat und wofür die Zeit aufgewendet wurde.

erstellen bestellen veröffentlichen

Wir erstellen außerdem automatisch eine Entity Map aller verschiedenen Services, die an einem bestimmten Trace beteiligt sind.

Entity Map

Der Bereich, auf den ich Ihre Aufmerksamkeit lenken möchte, liegt in der Trace- und Span- Aufschlüsselung. Sie können sehen, wie der Trace beim Producer initiiert wird, der Consumer dann die Nachricht aufnimmt und wie der Consumer dann auch zwei separate Aufrufe an den nachgelagerten Service tätigt.

Uninstrumentierte Zeit

Interessant ist hier der Span mit der Bezeichnung „Uninstrumented time“. Dies ist Code im Consumer, bei dem der Agent nicht in der Lage war, detailliertere Informationen darüber zu erfassen, was in seinen internen Methoden vor sich geht.

Dies zeigt bereits die Grenzen der Zero-Code-Instrumentierung. Der Agent instrumentiert standardmäßig nicht alle verschiedenen Methoden und Quellcodes, sondern stoppt absichtlich auf einer bestimmten Ebene, um eine bessere Einsicht in Ihren Code zu erhalten.

Manuelle Instrumentierung

Im vorherigen Abschnitt haben Sie gesehen, dass die Zero-Code-Instrumentierung hinsichtlich der Visibility in Ihre Anwendung gewisse Einschränkungen aufweist. Genau hier kommt die manuelle Instrumentierung ins Spiel.

Konfiguration

Ich habe dieselbe Anwendung konfiguriert, aber dieses Mal wird beim Starten der Anwendung überhaupt kein Agent konfiguriert.

Konfigurieren

Ich verwende einfach den Maven-Wrapper, um die Anwendung auszuführen.

Die weiteren Konfigurationsdetails sind dann Teil meiner application.properties:

application.properties

Diese Eigenschaften werden dann in meinem Spring-Boot-Anwendungscode verwendet, um die Konfiguration für OpenTelemetry für Traces, Metriken und Logs zu definieren.

Metriken und Logs

Observability

Bevor ich die Details meiner Implementierung einer manuellen Instrumentierung betrachte, schauen wir uns zunächst das Ergebnis an.

manuelle Instrumentierung

Fällt Ihnen auf, dass der Span, der zuvor mit „Uninstrumented time“ bezeichnet wurde, nun viel detailliertere Informationen anzeigt? Ich kann jetzt diese zusätzlichen Spans sehen:

  • ExecuteLongRunningTask
  • WhyTheHeckDoWeSleepHere
  • SomeTinyTask
  • AnotherShortRunningTask

„WhyTheHeckDoWeSleepHere“ scheint am längsten zu dauern. Kein Wunder, der Name verrät es ja schon 😉.

Werfen wir einen Blick auf den Quellcode, um die manuelle Instrumentierung zu zeigen, die ich vorgenommen habe.

RunningTask

In der Methode mit dem Namen ExecuteLongRunningTask habe ich mithilfe der Methode spanBuilder() einen neuen Span auf dem aktuellen Tracer erstellt.

Darüber hinaus fällt Ihnen vielleicht auch auf, dass ich nur zum Spaß einen weiteren Span mit dem Namen „WhyTheHeckDoWeSleepHere“ erstellt habe, der eine künstliche Arbeitseinheit oder besser gesagt eine Sleep-Anweisung für den aktuellen Thread enthält.

Diese Konzepte zur Nutzung des OpenTelemetry-SDK ermöglichen es mir, wesentlich spezifischere Einblicke und Details zu meiner Anwendung und meinem Quellcode zu erhalten. Wie Sie sich vorstellen können, besteht jedoch auch der Vorbehalt, dass in meinem Quellcode einige Abhängigkeiten und benutzerdefinierter Code verfügbar sein müssen.

Fazit

Ich hoffe, ich konnte Ihnen zeigen, wie einfach es sein kann, OpenTelemetry zu nutzen, um Einblicke in Ihre Anwendungen und Services zu erhalten. Wir haben uns mit Zero-Code-Instrumentierung befasst, um ohne Codeänderungen beginnen zu können, der Detaillierungsgrad ist jedoch möglicherweise begrenzt. Anschließend haben wir uns auch mit der manuellen Instrumentierung beschäftigt. Dadurch konnten wir spezifischer vorgehen und die Instrumentierung individuell anpassen, allerdings ist der Einstiegsaufwand etwas höher.

Ich empfehle Ihnen, einen Blick auf OpenTelemetry und seine faszinierenden Fähigkeiten zu werfen. Teilen Sie mir Ihre Meinung mit und kontaktieren Sie mich bitte, wenn Sie Fragen haben oder weitere Informationen benötigen.

Viel Spaß beim Programmieren!

New Relic Now Demo der neuen Agentic-Integrationen – heute!
Jetzt ansehen.