Integracja Jaeger z aplikacją Spring Boot

(Himank Batra) (9 września , 2020)

Integracja Jaeger z aplikacją do rozruchu wiosennego

Najpierw zrozumiemy, czym jest Jaeger

Jaeger to oprogramowanie typu open source do śledzenia transakcji między usługami rozproszonymi.

Służy do monitorowania i rozwiązywania problemów ze złożonymi środowiskami mikrousług.

Firma Ridesharing Uber opracowała Jaeger jako projekt open source w 2015 r. Został zaakceptowany jako chmura Projekt inkubacji Native Computing Foundation (CNCF) w 2017 r., Aw 2019 r. Uzyskał status absolwenta.

Co to jest śledzenie rozproszone?

Śledzenie rozproszone to sposób na zobaczenie i zrozumienie Cały łańcuch zdarzeń w złożonej interakcji między mikrousługami.

Nowoczesne, natywne dla chmury tworzenie oprogramowania opiera się na mikrousługach: niezależnych usługach, z których każda zapewnia inną podstawową funkcję. Gdy użytkownik wysyła żądanie w aplikacji, odpowiada wiele usług, które generują wynik.

Pojedyncze połączenie w aplikacji może wywołać dziesiątki różnych usług, które współdziałają ze sobą. W jaki sposób programiści i inżynierowie mogą zidentyfikować problem, gdy coś idzie nie tak lub żądanie działa wolno? Potrzebujemy sposobu na śledzenie wszystkich połączeń.

W tym miejscu pojawia się śledzenie rozproszone. Często jest uruchamiane jako część siatki usług, która jest sposobem zarządzania i obserwowania mikrousług.

Jaeger używa śledzenia rozproszonego, aby śledzić ścieżkę żądania za pośrednictwem różnych mikrousług. Zamiast zgadywać, możemy zobaczyć wizualną reprezentację przepływów połączeń.

Uporządkowane informacje o transakcjach są przydatne do debugowania i optymalizacji. Jaeger zawiera narzędzia do monitorowania transakcji rozproszonych, optymalizacji wydajności i opóźnień oraz przeprowadzania analizy przyczyn źródłowych (RCA), metody rozwiązywania problemów.

Terminologia Jaeger i komponenty

Jaeger przedstawia żądania wykonania jako ślady . Ślad przedstawia ścieżkę danych / wykonywania w systemie.

Ślad składa się z jednego lub więcej zakresów . Rozpiętość to logiczna jednostka pracy w Jaeger. Każdy zakres zawiera nazwę operacji, godzinę rozpoczęcia i czas trwania. Rozpiętości mogą być zagnieżdżane i porządkowane.

Jaeger zawiera kilka komponentów, które współpracują ze sobą w celu gromadzenia, przechowywania i wizualizacji rozpiętości i śladów.

Klient Jaeger zawiera język – specyficzne implementacje API OpenTracing do śledzenia rozproszonego. Mogą być używane ręcznie lub z różnymi frameworkami open source.

Jaeger Agent to demon sieciowy, który nasłuchuje rozpiętości przesyłanych przez User Datagram Protocol. Agent ma być umieszczony na tym samym hoście, co instrumentowana aplikacja. Zwykle jest to realizowane za pomocą wózka bocznego w środowiskach kontenerowych, takich jak Kubernetes.

Jaeger Collector odbiera rozpiętości i umieszcza je w kolejce do przetwarzania.

Kolektory wymagają trwałe zaplecze pamięci masowej, więc Jaeger ma również podłączany mechanizm span storage.

Zapytanie to usługa, która pobiera ślady z pamięci.

Jaeger Console to interfejs użytkownika, który umożliwia wizualizację rozproszonych danych śledzenia.

Dlaczego Jaeger?

Praktycy mikrousług w terenie szybko zdają sobie sprawę, że większość problemów operacyjnych, które pojawiają się podczas przechodzenia na architekturę rozproszoną, jest ostatecznie uziemiona w dwóch obszarach: sieci i obserwowalność. Jest to po prostu o rząd wielkości większy problem dotyczący sieci i debugowania zestawu splecionych ze sobą usług rozproszonych w porównaniu z pojedynczą monolityczną aplikacją.

Jaeger w działaniu

Będziemy integrować jaeger w aplikacjach rozruchowych wiosennych.

Najpierw szybko skonfigurujmy nasze aplikacje do rozruchu wiosennego.

Pomysł polega na generowaniu nazw poprzez łączenie nazwisk znanych naukowców z imionami zwierząt.

Zbudujemy więc 3 mikrousługi przy użyciu rozruchu sprężynowego, tj. usługa-nazw-zwierząt , name-generator-service, i scientist-name-service.

Żądanie klienta dotyczące połączonej nazwy naukowca i zwierzęcia z name-generator-service który wewnętrznie wywołuje usługę-imion-zwierząt i usługę -nazwa-naukowca.

To samo pokazano na poniższym diagramie.

Przykład mikrousługi

Szybko zbudujmy nasze trzy mikrousługi za pomocą inicjalizacji wiosennej r.

Dodamy zależność spring-boot-starter-web podczas generowania aplikacji do rozruchu sprężynowego.

Teraz mamy gotowe 3 aplikacje do rozruchu sprężynowego. Dodajmy te 3 mikrousługi do folderu o nazwie opentracing-microservices-example.

I zaimportujmy ten folder do swojego ulubionego edytora. Używam IntelliJ .

Ponieważ musimy zadzwonić do usługi-nazw-zwierząt i naukowca- name-service z name-generator-service.

wybieramy udawać klienta. Dodajmy więc spring-cloud-starter-openfeign: 2.2.3.RELEASE zależność w name-generator-service.

Oto kod dla wszystkie 3 mikrousługi.

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:

Tutaj używam com.shekhargulati: strman: 0.4 .0 biblioteka do konwersji nazw zwierząt i naukowców na kebab.

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

Teraz uruchomimy wszystkie 3 aplikacje i przejdziemy do http: // localhost: 8080 / api / v1 / names / random w przeglądarce.

Otrzymamy przykład losowej nazwy: john-cockcroft-snapping-turtle

Więc teraz nasza aplikacja konfiguracja jest zakończona.

Zintegrujmy teraz jaegera z tymi aplikacjami, abyśmy mogli tra ce każdego żądania.

Musimy tylko dodać poniższą zależność do wszystkich 3 pom.xml.


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

I musimy dodać poniższe właściwości w pliku application.properties dla wszystkich 3 aplikacji.

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

Uruchom Jaeger w dockerze za pomocą poniższego polecenia:

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

Teraz uruchom ponownie aplikację. I przejdź do localhost: 9090 w przeglądarce. Otrzymamy stronę domową jaegera.

Ponadto przejdź do http: // localhost: 8080 / api / v1 / names / random w przeglądarce. Podobnie, otrzymasz losową nazwę.

Ale teraz możemy prześledzić żądanie. Sprawdź w panelu jaeger, wybierz usługę nazwa-generator-service.

a następnie kliknij znajdź ślady. otrzymamy ślady, jak pokazano na poniższym obrazku dla name-generator-service .

Usługa generatora nazw Jaeger

Po przeanalizowaniu tego możemy wyraźnie zobaczyć, że istnieje 5 zakresów.

nazwa-generator-usługa (1 zakres)

nazwa-generator-usługa-> nazwa-usługi-zwierzęcia (2 zakresy)

nazwa-generator-usługa-> naukowiec- name-service (2 span)

Jest to pokazane na poniższym obrazku.

Śledzenie usługi generatora nazw Jaeger

Tutaj wszystko jest automatycznie konfigurowane przez opentracing-spring-jaeger-cloud-starter bibliotekę, która ma klasę o nazwie TracingAspect w zasadzie robiący magię.

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

Dodałem Dockerfile, docker-compose i docker- setup.sh, aby ułatwić uruchamianie tej aplikacji. pobierz kod i uruchom bezpośrednio docker-setup.sh.

Możesz znaleźć kod w moim repozytorium Github link .

Inspiracją do tego jest Blog Shekhara Gulatiego .

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *