Integração Jaeger com o aplicativo Spring Boot

(Himank Batra) (9 de setembro , 2020)

Integração Jaeger com aplicativo Spring Boot

Vamos primeiro entender o que é Jaeger

Jaeger é um software de código aberto para rastrear transações entre serviços distribuídos.

É usado para monitorar e solucionar problemas de ambientes de microsserviços complexos.

A empresa de compartilhamento de viagens Uber desenvolveu o Jaeger como um projeto de código aberto em 2015. Foi aceito como uma nuvem Projeto de incubação da Native Computing Foundation (CNCF) em 2017 e promovido à graduação em 2019.

O que é rastreamento distribuído?

Rastreamento distribuído é uma maneira de ver e entender o Toda a cadeia de eventos em uma interação complexa entre microsserviços.

O desenvolvimento moderno de software nativo da nuvem depende de microsserviços: serviços independentes em que cada um fornece uma função central diferente. Quando um usuário faz uma solicitação em um aplicativo, muitos serviços individuais respondem para produzir um resultado.

Uma única chamada em um aplicativo pode invocar dezenas de serviços diferentes que interagem entre si. Como os desenvolvedores e engenheiros podem isolar um problema quando algo dá errado ou uma solicitação está lenta? Precisamos de uma maneira de rastrear todas as conexões.

É aí que entra o rastreamento distribuído. Muitas vezes, é executado como parte de uma malha de serviço, que é uma maneira de gerenciar e observar microsserviços.

Jaeger usa rastreamento distribuído para seguir o caminho de uma solicitação por meio de microsserviços diferentes. Em vez de adivinhar, podemos ver uma representação visual dos fluxos de chamadas.

As informações organizadas sobre as transações são úteis para depuração e otimização. Jaeger inclui ferramentas para monitorar transações distribuídas, otimizar desempenho e latência e realizar análise de causa raiz (RCA), um método de solução de problemas.

Terminologia Jaeger e componentes

Jaeger apresenta solicitações de execução como rastreios . Um rastreamento mostra o caminho de dados / execução através de um sistema.

Um rastreamento é composto de um ou mais spans . Um período é uma unidade lógica de trabalho em Jaeger. Cada período inclui o nome da operação, hora de início e duração. Spans podem ser aninhados e ordenados.

Jaeger inclui vários componentes que trabalham juntos para coletar, armazenar e visualizar spans e rastreios.

Jaeger Client inclui linguagem -implementações específicas da API OpenTracing para rastreamento distribuído. Eles podem ser usados ​​manualmente ou com uma variedade de estruturas de código aberto.

Agente Jaeger é um daemon de rede que escuta spans enviados pelo protocolo de datagrama do usuário. O agente deve ser colocado no mesmo host que o aplicativo instrumentado. Isso geralmente é implementado por meio de um arquivo secundário em ambientes de contêiner, como Kubernetes.

O coletor Jaeger recebe spans e os coloca em uma fila para processamento.

Os coletores exigem um back-end de armazenamento persistente, então Jaeger também tem um mecanismo plugável para span storage.

Query é um serviço que recupera rastros de armazenamento.

Jaeger Console é uma interface de usuário que permite visualizar seus dados de rastreamento distribuídos.

Por que Jaeger?

Como os profissionais de microsserviços locais estão percebendo rapidamente, a maioria dos problemas operacionais que surgem ao mudar para uma arquitetura distribuída são basicamente baseados em duas áreas: rede e observabilidade. É simplesmente um problema de ordens de magnitude maior para a rede e depuração de um conjunto de serviços distribuídos entrelaçados versus um único aplicativo monolítico.

Jaeger em ação

Estaremos integrando o jaeger em aplicativos de boot de primavera.

Primeiro, vamos configurar rapidamente nossos aplicativos de boot de primavera.

A ideia aqui é gerar nomes concatenando nomes de cientistas famosos com nomes de animais.

Então, vamos construir 3 microsserviços usando bota de mola, isto é, serviço de nomes de animais , name-generator-service, e scientist-name-service.

Solicitação do cliente para um cientista e um animal nome concatenado de name-generator-service que chama internamente animal-name-service e scientist-name-service.

O mesmo é demonstrado no diagrama abaixo.

Exemplo de microsserviço

Vamos construir rapidamente nossos três microsserviços usando spring initialize r.

Adicionaremos a dependência spring-boot-starter-web enquanto geramos aplicativos de inicialização de primavera.

Agora temos 3 aplicativos de inicialização de primavera prontos. Vamos adicionar esses três microsserviços a uma pasta chamada opentracing-microservices-example.

E importar esta pasta em seu editor favorito. Eu uso o IntelliJ .

Como temos que chamar animal-name-service e scientist- name-service de name-generator-service.

estamos escolhendo fingir cliente para isso. Então, vamos adicionar a dependência spring-cloud-starter-openfeign: 2.2.3.RELEASE em name-generator-service.

Aqui está o código para todos os três microsserviços.

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:

Aqui, estou usando com.shekhargulati: strman: 0.4 .0 biblioteca para converter o nome do animal e do cientista em caso 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

Agora vamos executar todos os 3 aplicativos e ir para http: // localhost: 8080 / api / v1 / names / random em um navegador.

Obteremos alguns exemplos de nomes aleatórios: john-cockcroft-snapping-turtle

Então, agora nosso aplicativo a configuração está feita.

Agora vamos integrar o jaeger a esses aplicativos para que possamos traçar ce cada solicitação.

Só precisamos adicionar a dependência abaixo a todos os 3 pom.xml.


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

E precisamos adicionar as propriedades abaixo no arquivo application.properties para todos os 3 aplicativos.

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

Execute Jaeger no docker através do comando abaixo:

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

Agora reinicie o aplicativo. E vá para localhost: 9090 em um navegador. Obteremos uma página inicial do jaeger.

Além disso, vá para http: // localhost: 8080 / api / v1 / names / random em um navegador. Da mesma forma, você receberá algum nome aleatório.

Mas agora podemos rastrear a solicitação. Verifique no painel jaeger, escolha o serviço name-generator-service.

e clique em localizar rastros. obteremos rastreamentos conforme mostrado na imagem abaixo para o serviço gerador de nomes .

Jaeger Name Generator Service

Podemos ver claramente que existem 5 spans quando detalhamos isso.

serviço gerador de nomes (1 período)

serviço gerador de nomes-> serviço de nomes de animais (2 períodos)

serviço gerador de nomes-> cientista- serviço de nomes (2 spans)

Isso é mostrado na imagem abaixo.

Rastreamento de serviço do gerador de nomes Jaeger

Aqui, tudo é configurado automaticamente por opentracing-spring-jaeger-cloud-starter biblioteca que tem uma classe chamada TracingAspect basicamente fazendo mágica.

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

Eu adicionei Dockerfile, docker-compose e docker- setup.sh para facilitar a execução deste aplicativo. checkout code e execute docker-setup.sh diretamente.

Você pode encontrar o código em meu repositório Github link .

Isso é inspirado no Blog de Shekhar Gulati .

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *