Jaeger-Integration mit Spring Boot-Anwendung

(Himank Batra) (9. September) , 2020)

Jaeger Integration in die Spring-Boot-Anwendung

Lassen Sie uns zunächst verstehen, was Jaeger ist.

Jaeger ist eine Open-Source-Software zur Verfolgung von Transaktionen zwischen verteilten Diensten.

Es wird zur Überwachung und Fehlerbehebung komplexer Microservices-Umgebungen verwendet.

Das Mitfahrunternehmen Uber hat Jaeger 2015 als Open Source-Projekt entwickelt. Es wurde als Cloud akzeptiert Inkubationsprojekt der Native Computing Foundation (CNCF) im Jahr 2017 und Aufstieg in den Abschlussstatus im Jahr 2019.

Was ist verteilte Ablaufverfolgung?

Verteilte Ablaufverfolgung ist eine Möglichkeit, dies zu sehen und zu verstehen Die gesamte Kette von Ereignissen in einer komplexen Interaktion zwischen Mikrodiensten.

Moderne, Cloud-native Softwareentwicklung basiert auf Mikrodiensten: unabhängige Dienste, die jeweils eine andere Kernfunktion bereitstellen. Wenn ein Benutzer eine Anfrage in einer App stellt, antworten viele einzelne Dienste, um ein Ergebnis zu erzielen.

Ein einzelner Anruf in einer App kann Dutzende verschiedener Dienste aufrufen, die miteinander interagieren. Wie können Entwickler und Ingenieure ein Problem eingrenzen, wenn etwas schief geht oder eine Anforderung langsam ausgeführt wird? Wir brauchen eine Möglichkeit, alle Verbindungen im Auge zu behalten.

Hier kommt die verteilte Ablaufverfolgung ins Spiel. Sie wird häufig als Teil eines Service-Mesh ausgeführt, mit dem Microservices verwaltet und beobachtet werden können.

Jaeger verwendet die verteilte Ablaufverfolgung, um den Pfad einer Anforderung durch verschiedene Mikrodienste zu verfolgen. Anstatt zu raten, können wir eine visuelle Darstellung der Anrufverläufe sehen.

Organisierte Informationen zu Transaktionen sind nützlich für das Debuggen und Optimieren. Jaeger enthält Tools zur Überwachung verteilter Transaktionen, zur Optimierung der Leistung und Latenz sowie zur Durchführung einer Ursachenanalyse (RCA), einer Methode zur Problemlösung.

Jaeger-Terminologie und Komponenten

Jaeger präsentiert Ausführungsanforderungen als Traces . Eine Ablaufverfolgung zeigt den Daten- / Ausführungspfad durch ein System.

Eine Ablaufverfolgung besteht aus einem oder mehreren Bereichen . Eine Spanne ist in Jaeger eine logische Arbeitseinheit. Jeder Bereich enthält den Operationsnamen, die Startzeit und die Dauer. Bereiche können verschachtelt und geordnet sein.

Jaeger enthält mehrere Komponenten, die zusammenarbeiten, um Bereiche und Traces zu sammeln, zu speichern und zu visualisieren.

Jaeger Client enthält Sprache -spezifische Implementierungen der OpenTracing-API für die verteilte Ablaufverfolgung. Diese können manuell oder mit einer Vielzahl von Open-Source-Frameworks verwendet werden.

Jaeger Agent ist ein Netzwerkdämon, der auf Bereiche wartet, die über das User Datagram Protocol gesendet werden. Der Agent soll auf demselben Host wie die instrumentierte Anwendung platziert werden. Dies wird normalerweise über einen Beiwagen in Containerumgebungen wie Kubernetes implementiert.

Jaeger Collector empfängt Bereiche und stellt sie zur Verarbeitung in eine Warteschlange.

Collectors benötigen a Persistent Storage Backend, daher verfügt Jaeger auch über einen steckbaren Mechanismus für span storage.

Query ist ein Dienst, der Traces aus dem Speicher abruft.

Jaeger Console ist eine Benutzeroberfläche, mit der Sie Ihre verteilten Ablaufverfolgungsdaten visualisieren können.

Warum Jaeger?

Da Mikroservice-Anwender vor Ort schnell erkennen, sind die meisten Betriebsprobleme, die beim Wechsel zu einer verteilten Architektur auftreten, letztendlich auf zwei Bereiche zurückzuführen: Vernetzung und Beobachtbarkeit. Es ist einfach ein um Größenordnungen größeres Problem, eine Reihe miteinander verflochtener verteilter Dienste im Vergleich zu einer einzelnen monolithischen Anwendung zu vernetzen und zu debuggen.

Jaeger in Aktion

Wir werden Jaeger in eine Spring-Boot-Anwendung integrieren.

Lassen Sie uns zunächst schnell unsere Spring-Boot-Anwendungen einrichten.

Die Idee hier ist, Namen zu generieren, indem berühmte Wissenschaftlernamen mit Tiernamen verknüpft werden.

Wir werden also 3 Microservices mit Spring Boot erstellen, dh Animal-Name-Service , name-generator-service, und scientist-name-service.

Client-Anfrage für einen mit Wissenschaftlern und Tieren verketteten Namen von name-generator-service , das intern animal-name-service und scientist-name-service aufruft.

Dasselbe wird in der folgenden Abbildung gezeigt.

Microservice-Beispiel

Lassen Sie uns schnell unsere drei Microservices mit spring initialize r erstellen.

Beim Generieren von Spring-Boot-Anwendungen wird die Abhängigkeit Spring-Boot-Starter-Web hinzugefügt.

Jetzt stehen 3 Spring-Boot-Anwendungen bereit. Fügen Sie diese 3 Microservices einem Ordner mit dem Namen opentracing-microservices-example hinzu.

Und importieren Sie diesen Ordner in Ihren bevorzugten Editor. Ich verwende IntelliJ .

Wie wir Tiernamensdienst und Wissenschaftler nennen müssen- name-service von name-generator-service.

Wir entscheiden uns dafür, den Client dafür vorzutäuschen. Fügen wir also spring-cloud-Starter-openfeign hinzu: 2.2.3.RELEASE Abhängigkeit in name-generator-service.

Hier ist der Code für alle 3 Microservices.

AnimalNameService :

package com.example.ans;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.core.io.ClassPathResource;import org.springframework.http.HttpHeaders;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.List;import java.util.Random;import java.util.stream.Collectors;@SpringBootApplication
public class AnimalNameService {public static void main(String[] args) {SpringApplication.run(AnimalNameService.class, args);}}@RestController
@RequestMapping("/api/v1/animals")
class AnimalNameResource {private final List animalNames;private Random random;public AnimalNameResource() throws IOException {InputStream inputStream = new ClassPathResource("/animals.txt").getInputStream();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {animalNames = reader.lines().collect(Collectors.toList());}random = new Random();}@GetMapping(path = "/random")
public String name(@RequestHeader HttpHeaders headers) {String name = animalNames.get(random.nextInt(animalNames.size()));return name;}}

application.properties:

server.port=9000

NameGeneratorService:

Hier verwende ich com.shekhargulati: strman: 0.4 .0 Bibliothek zum Konvertieren des Tier- und Wissenschaftlernamens in einen Kebab-Fall.

package com.example.ngs;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import static strman.Strman.toKebabCase;@SpringBootApplication
@EnableFeignClients
public class NameGeneratorService {public static void main(String[] args) {SpringApplication.run(NameGeneratorService.class, args);}}@FeignClient(name = "scientist-service-client", url = "${scientist.service.prefix.url}")
interface ScientistServiceClient {@GetMapping("/api/v1/scientists/random")
String randomScientistName();}@FeignClient(name = "animal-service-client", url = "${animal.service.prefix.url}")
interface AnimalServiceClient {@GetMapping("/api/v1/animals/random")
String randomAnimalName();}@RestController
@RequestMapping("/api/v1/names")
class NameResource {@Autowired
private AnimalServiceClient animalServiceClient;@Autowired
private ScientistServiceClient scientistServiceClient;@GetMapping(path = "/random")
public String name() throws Exception {String animal = animalServiceClient.randomAnimalName();String scientist = scientistServiceClient.randomScientistName();String name = toKebabCase(scientist) + "-" + toKebabCase(animal);return name;}}

application.properties:

server.port=8080scientist.service.prefix.url=http://localhost:8090animal.service.prefix.url=http://localhost:9000

ScientistNameService:

package com.example.sns;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.core.io.ClassPathResource;import org.springframework.http.HttpHeaders;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.List;import java.util.Random;import java.util.stream.Collectors;@SpringBootApplication
public class ScientistNameService {public static void main(String[] args) {SpringApplication.run(ScientistNameService.class, args);}}@RestController
@RequestMapping("/api/v1/scientists")
class ScientistNameResource {private final List scientistsNames;private Random random;public ScientistNameResource() throws IOException {InputStream inputStream = new ClassPathResource("/scientists.txt").getInputStream();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {scientistsNames = reader.lines().collect(Collectors.toList());}random = new Random();}@GetMapping(path = "/random")
public String name(@RequestHeader HttpHeaders headers) {String name = scientistsNames.get(random.nextInt(scientistsNames.size()));return name;}}

application.properties :

server.port=8090

Jetzt führen wir alle drei Anwendungen aus und gehen zu http: // localhost: 8080 / api / v1 / names / random in einem Browser.

Wir erhalten ein Beispiel für einen zufälligen Namen: john-cockcroft-snapping-turtle

Also jetzt unsere Anwendung Das Setup ist abgeschlossen.

Nun integrieren wir Jaeger in diese Anwendungen, damit wir tra ce jede Anfrage.

Wir müssen nur die folgende Abhängigkeit zu allen 3 pom.xml hinzufügen.


io.opentracing.contrib
opentracing-spring-jaeger-cloud-starter3.1.2

Und Wir müssen die folgenden Eigenschaften in der Datei application.properties für alle 3 Anwendungen hinzufügen.

spring.application.name= // example : name-generator-service (this will be displayed in jaeger for respective service)opentracing.jaeger.udp-sender.host=localhost //udp host for sender. By default Jaeger libraries use a UDP sender to report finished spans to the jaeger-agent daemonopentracing.jaeger.udp-sender.port=6831 // udp portopentracing.jaeger.log-spans=true // logs the spans in console

Führen Sie Jaeger im Docker über den folgenden Befehl aus:

docker run -p 9090:16686 — name jaeger -d jaegertracing/all-in-one:1.17

Starten Sie nun die Anwendung neu. Und gehen Sie in einem Browser zu localhost: 9090. Wir erhalten eine Jaeger-Homepage.

Gehen Sie auch in einem Browser zu http: // localhost: 8080 / api / v1 / names / random . Ebenso erhalten Sie einen zufälligen Namen.

Jetzt können wir die Anfrage verfolgen. Im Jaeger-Dashboard einchecken, wählen Sie service name-generator-service.

und klicken Sie dann auf Traces suchen. Wir erhalten Traces wie im folgenden Bild für den Name-Generator-Service gezeigt.

Jaeger Name Generator Service

Wir können deutlich sehen, dass es 5 Bereiche gibt, wenn wir einen Drilldown durchführen.

Name-Generator-Service (1 Spanne)

Name-Generator-Service-> Tier-Name-Service (2 Spans)

Name-Generator-Service-> Wissenschaftler- Name-Service (2 Bereiche)

Dies ist in der folgenden Abbildung dargestellt.

Jaeger Name Generator Service Trace

Hier wird alles automatisch von opentracing-spring-jaeger-cloud-Starter Bibliothek mit einer Klasse namens TracingAspect, die im Grunde genommen Magie ausführt.

@Aspect
class TracingAspect {
TracingAspect() {
}

@Around("execution (* feign.Client.*(..)) && !within(is(FinalType))")
public Object feignClientWasCalled(ProceedingJoinPoint pjp) throws Throwable {
Object bean = pjp.getTarget();
if (!(bean instanceof TracingClient)) {
Object[] args = pjp.getArgs();
return (new TracingClientBuilder((Client)bean, FeignTracingAutoConfiguration.this.tracer)).withFeignSpanDecorators(FeignTracingAutoConfiguration.this.spanDecorators).build().execute((Request)args[0], (Options)args[1]);
} else {
return pjp.proceed();
}
}
}

Ich habe Dockerfile, Docker-Compose und Docker- hinzugefügt. setup.sh, um das Ausführen dieser Anwendung zu vereinfachen. Checkout-Code und führen Sie docker-setup.sh direkt aus.

Sie finden den Code in meinem Github-Repository Link .

Dies ist inspiriert von Shekhar Gulatis Blog .

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.