Jaeger와 Spring Boot 애플리케이션 통합

(Himank Batra) (9 월 9 일 , 2020)

Jaeger와 스프링 부트 애플리케이션 통합

Jaeger가 무엇인지 먼저 이해하겠습니다.

Jaeger는 트랜잭션 추적을위한 오픈 소스 소프트웨어입니다.

복잡한 마이크로 서비스 환경을 모니터링하고 문제를 해결하는 데 사용됩니다.

Ridesharing 회사 Uber는 2015 년에 Jaeger를 오픈 소스 프로젝트로 개발했습니다. 클라우드로 채택되었습니다. 2017 년에 CNCF (Native Computing Foundation) 인큐베이션 프로젝트로 2019 년에 졸업 상태로 승격되었습니다.

분산 추적이란 무엇입니까?

분산 추적은 e 마이크로 서비스 간의 복잡한 상호 작용에서 발생하는 전체 이벤트 체인.

현대적인 클라우드 네이티브 소프트웨어 개발은 ​​각각 다른 핵심 기능을 제공하는 독립 서비스 인 마이크로 서비스에 의존합니다. 사용자가 앱에서 요청을하면 많은 개별 서비스가 결과를 생성하기 위해 응답합니다.

앱에서 한 번의 호출로 서로 상호 작용하는 수십 개의 서로 다른 서비스를 호출 할 수 있습니다. 문제가 발생하거나 요청이 느리게 실행되는 경우 개발자와 엔지니어가 문제를 어떻게 분리 할 수 ​​있습니까? 모든 연결을 추적 할 수있는 방법이 필요합니다.

분산 추적이 시작되는 곳입니다. 종종 마이크로 서비스를 관리하고 관찰하는 방법 인 서비스 메시의 일부로 실행됩니다.

Jaeger는 분산 추적을 사용하여 다양한 마이크로 서비스를 통해 요청 경로를 따릅니다. 추측하는 대신 호출 흐름을 시각적으로 볼 수 있습니다.

트랜잭션에 대한 조직화 된 정보는 디버깅 및 최적화에 유용합니다. Jaeger에는 분산 트랜잭션을 모니터링하고 성능과 지연 시간을 최적화하며 문제 해결 방법 인 근본 원인 분석 (RCA)을 수행하는 도구가 포함되어 있습니다.

Jaeger 용어 및 구성 요소

Jaeger는 실행 요청을 추적 으로 표시합니다. 추적은 시스템을 통한 데이터 / 실행 경로를 보여줍니다.

추적은 하나 이상의 스팬 으로 구성됩니다. 범위는 Jaeger의 논리적 작업 단위입니다. 각 범위에는 작업 이름, 시작 시간 및 기간이 포함됩니다. 스팬은 중첩되고 정렬 될 수 있습니다.

Jaeger에는 스팬 및 추적을 수집, 저장 및 시각화하기 위해 함께 작동하는 여러 구성 요소가 포함되어 있습니다.

Jaeger 클라이언트 에는 언어가 포함됩니다. 분산 추적을위한 OpenTracing API의 특정 구현. 수동으로 또는 다양한 오픈 소스 프레임 워크와 함께 사용할 수 있습니다.

Jaeger Agent 는 사용자 데이터 그램 프로토콜을 통해 전송 된 스팬을 수신하는 네트워크 데몬입니다. 에이전트는 계측 된 응용 프로그램과 동일한 호스트에 배치됩니다. 이는 일반적으로 Kubernetes와 같은 컨테이너 환경의 사이드카를 통해 구현됩니다.

Jaeger Collector 는 스팬을 수신하여 처리를 위해 대기열에 넣습니다.

수집기에는 영구 저장소 백엔드이므로 Jaeger에는 스팬 저장 에 대한 플러그 형 메커니즘도 있습니다.

Query 는 저장소에서 추적을 검색하는 서비스입니다.

Jaeger Console 은 분산 추적 데이터를 시각화 할 수있는 사용자 인터페이스입니다.

Jaeger를 사용해야하는 이유

현장 마이크로 서비스 실무자들이 빠르게 인식함에 따라 분산 아키텍처로 전환 할 때 발생하는 대부분의 운영 문제는 궁극적으로 네트워킹과 관찰 가능성. 단일 모 놀리 식 애플리케이션과 비교하여 서로 연결된 분산 서비스 세트를 네트워크화하고 디버깅하는 것은 단순히 훨씬 더 큰 문제입니다.

Jaeger in Action

스프링 부트 애플리케이션에 jaeger를 통합 할 것입니다.

먼저, 스프링 부트 애플리케이션을 빠르게 설정하겠습니다.

여기서 아이디어는 유명한 과학자 이름을 동물 이름과 연결하여 이름을 생성하는 것입니다.

그래서 스프링 부트를 사용하여 3 개의 마이크로 서비스, 즉 animal-name-service , name-generator-service, scientist-name-service .

name-generator-service에서 과학자 및 동물 연결 이름에 대한 클라이언트 요청 내부적으로 animal-name-service scientist-name-service 를 호출합니다.

아래 다이어그램에서 동일하게 설명합니다.

마이크로 서비스 예

봄 초기화 r를 사용하여 세 가지 마이크로 서비스를 빠르게 빌드 해 보겠습니다.

스프링 부트 애플리케이션을 생성하는 동안 spring-boot-starter-web 종속성을 추가 할 것입니다.

이제 3 개의 스프링 부트 애플리케이션이 준비되었습니다. 이 3 개의 마이크로 서비스를 opentracing-microservices-example 이라는 폴더에 추가하겠습니다.

그리고이 폴더를 원하는 편집기로 가져옵니다. IntelliJ 를 사용합니다.

animal-name-service scientist- name-generator-service 의 name-service .

우리는이를 위해 클라이언트를 가장하기로 선택했습니다. 이제 name-generator-service spring-cloud-starter-openfeign : 2.2.3.RELEASE 종속성을 추가하겠습니다.

다음은 마이크로 서비스 3 개 모두.

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 :

여기에서는 com.shekhargulati : strman : 0.4를 사용하고 있습니다. .0 라이브러리로 동물 및 과학자 이름을 케밥 케이스로 변환합니다.

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

이제 3 개의 애플리케이션을 모두 실행하고 http : // localhost : 8080 / api / v1 / names로 이동합니다. / random 브라우저에서.

무작위 이름 예제를 얻을 것입니다. john-cockcroft-snapping-turtle

이제 애플리케이션 설정이 완료되었습니다.

이제 jaeger를 이러한 애플리케이션에 통합하여 각 요청을 처리합니다.

3 개의 pom.xml 모두에 아래 종속성을 추가하면됩니다.


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

그리고 3 개 애플리케이션 모두에 대한 application.properties 파일에 아래 속성을 추가해야합니다.

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

아래 명령을 통해 docker에서 Jaeger를 실행합니다.

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

이제 응용 프로그램을 다시 시작하십시오. 그리고 브라우저에서 localhost : 9090으로 이동합니다. jaeger 홈페이지가 표시됩니다.

또한 브라우저에서 http : // localhost : 8080 / api / v1 / names / random 으로 이동하세요. 마찬가지로 임의의 이름을 얻게됩니다.

하지만 이제 요청을 추적 할 수 있습니다. jaeger 대시 보드에서 서비스 name-generator-service

를 선택한 다음 추적 찾기를 클릭합니다. name-generator-service 에 대한 아래 이미지와 같이 추적을 가져옵니다.

Jaeger 이름 생성기 서비스

이 부분을 드릴 다운하면 5 개의 스팬이 있음을 분명히 알 수 있습니다.

name-generator-service (1 스팬)

name-generator-service-> animal-name-service (2 스팬)

name-generator-service-> scientist- name-service (2 spans)

아래 이미지에 나와 있습니다.

Jaeger 이름 생성기 서비스 추적

여기에서 모든 것이 opentracing-spring-jaeger-cloud-starter 라이브러리는 TracingAspect라는 클래스가 기본적으로 마법을 수행합니다.

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

Dockerfile, docker-compose 및 docker- setup.sh를 사용하면이 애플리케이션을 더 쉽게 실행할 수 있습니다. 코드를 체크 아웃하고 docker-setup.sh를 직접 실행하세요.

코드는 내 Github 저장소 링크 에서 찾을 수 있습니다.

Shekhar Gulati의 블로그 에서 영감을 얻었습니다.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다