Integración de Jaeger con la aplicación Spring Boot

(Himank Batra) (9 de septiembre , 2020)

Integración de Jaeger con la aplicación Spring Boot

Primero entendamos qué es Jaeger

Jaeger es un software de código abierto para rastrear transacciones entre servicios distribuidos.

Se utiliza para monitorear y solucionar problemas de entornos de microservicios complejos.

La empresa de viajes compartidos Uber desarrolló Jaeger como un proyecto de código abierto en 2015. Fue aceptado como una nube Proyecto de incubación de Native Computing Foundation (CNCF) en 2017 y ascendido al estado de graduado en 2019.

¿Qué es el rastreo distribuido?

El rastreo distribuido es una forma de ver y comprender Toda la cadena de eventos en una interacción compleja entre microservicios.

El desarrollo de software moderno y nativo de la nube se basa en microservicios: servicios independientes que cada uno proporciona una función central diferente. Cuando un usuario realiza una solicitud en una aplicación, muchos servicios individuales responden para producir un resultado.

Una sola llamada en una aplicación puede invocar docenas de servicios diferentes que interactúan entre sí. ¿Cómo pueden los desarrolladores e ingenieros aislar un problema cuando algo sale mal o una solicitud se está ejecutando lentamente? Necesitamos una forma de realizar un seguimiento de todas las conexiones.

Ahí es donde entra el seguimiento distribuido. A menudo se ejecuta como parte de una malla de servicios, que es una forma de administrar y observar microservicios.

Jaeger usa el rastreo distribuido para seguir la ruta de una solicitud a través de diferentes microservicios. En lugar de adivinar, podemos ver una representación visual de los flujos de llamadas.

La información organizada sobre las transacciones es útil para depurar y optimizar. Jaeger incluye herramientas para monitorear transacciones distribuidas, optimizar el rendimiento y la latencia, y realizar análisis de causa raíz (RCA), un método de resolución de problemas.

Terminología de Jaeger y componentes

Jaeger presenta las solicitudes de ejecución como trazas . Una traza muestra la ruta de datos / ejecución a través de un sistema.

Una traza se compone de uno o más tramos . Un tramo es una unidad lógica de trabajo en Jaeger. Cada intervalo incluye el nombre de la operación, la hora de inicio y la duración. Los intervalos se pueden anidar y ordenar.

Jaeger incluye varios componentes que trabajan juntos para recopilar, almacenar y visualizar intervalos y trazas.

Cliente Jaeger incluye idioma -implementaciones específicas de la API de OpenTracing para el seguimiento distribuido. Estos se pueden usar manualmente o con una variedad de marcos de código abierto.

Jaeger Agent es un demonio de red que escucha los intervalos enviados a través del Protocolo de datagramas de usuario. El agente debe colocarse en el mismo host que la aplicación instrumentada. Esto generalmente se implementa a través de un sidecar en entornos de contenedor como Kubernetes.

Jaeger Collector recibe intervalos y los coloca en una cola para su procesamiento.

Los recolectores requieren un backend de almacenamiento persistente, por lo que Jaeger también tiene un mecanismo conectable para span storage.

Query es un servicio que recupera trazas del almacenamiento.

Jaeger Console es una interfaz de usuario que le permite visualizar sus datos de seguimiento distribuidos.

¿Por qué Jaeger?

Como los profesionales de microservicios en el terreno se están dando cuenta rápidamente, la mayoría de los problemas operativos que surgen cuando se cambia a una arquitectura distribuida se basan en última instancia en dos áreas: redes y observabilidad. Es simplemente un problema de órdenes de magnitud mayor para conectar en red y depurar un conjunto de servicios distribuidos entrelazados en comparación con una sola aplicación monolítica.

Jaeger en acción

Integraremos jaeger en aplicaciones de arranque de primavera.

Primero, configuremos rápidamente nuestras aplicaciones de arranque de primavera.

La idea aquí es generar nombres concatenando nombres de científicos famosos con nombres de animales.

Entonces, construiremos 3 microservicios usando Spring Boot, es decir, animal-name-service , name-generator-service, y scientist-name-service.

Solicitud del cliente para un nombre concatenado de científico y animal de name-generator-service que llama internamente a animal-name-service y scientist-name-service.

Lo mismo se demuestra en el siguiente diagrama.

Ejemplo de microservicio

Construyamos rápidamente nuestros tres microservicios usando spring initialize r.

Agregaremos la dependencia spring-boot-starter-web mientras generamos aplicaciones de arranque de primavera.

Ahora tenemos 3 aplicaciones de arranque de primavera listas. Agreguemos estos 3 microservicios a una carpeta llamada opentracing-microservices-example.

E importe esta carpeta en su editor favorito. Yo uso IntelliJ .

Como tenemos que llamar animal-name-service y scientist- name-service de name-generator-service.

Estamos eligiendo fingir cliente para esto. Así que agreguemos la dependencia spring-cloud-starter-openfeign: 2.2.3.RELEASE en name-generator-service.

Aquí está el código para los 3 microservicios.

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:

Aquí, estoy usando com.shekhargulati: strman: 0.4 .0 biblioteca para convertir el nombre del animal y del científico en mayúsculas y minúsculas.

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

Ahora ejecutaremos las 3 aplicaciones e iremos a http: // localhost: 8080 / api / v1 / names / random en un navegador.

Obtendremos un ejemplo de nombre aleatorio: john-cockcroft-snapping-turtle

Así que ahora nuestra aplicación la configuración está lista.

Ahora integremos jaeger a estas aplicaciones para que podamos ce cada solicitud.

Solo necesitamos agregar la siguiente dependencia a los 3 pom.xml.


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

Y debemos agregar las propiedades siguientes en el archivo application.properties para las 3 aplicaciones.

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

Ejecute Jaeger en la ventana acoplable mediante el siguiente comando:

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

Ahora reinicie la aplicación. Y vaya a localhost: 9090 en un navegador. Obtendremos una página de inicio de jaeger.

Además, vaya a http: // localhost: 8080 / api / v1 / names / random en un navegador. Del mismo modo, obtendrá un nombre aleatorio.

Pero ahora podemos rastrear la solicitud. Verifique en el tablero de Jaeger, elija el servicio name-generator-service.

luego haga clic en buscar rastros. obtendremos trazas como se muestra en la siguiente imagen para el servicio-generador-de-nombres .

Jaeger Name Generator Service

Podemos ver claramente que hay 5 intervalos cuando profundizamos en esto.

servicio-generador-de-nombres (1 tramo)

servicio-generador-de-nombres-> servicio-de-nombre-animal (2 tramos)

servicio-generador-de-nombres-> científico- name-service (2 intervalos)

Esto se muestra en la siguiente imagen.

Rastreo del servicio del generador de nombres Jaeger

Aquí, todo está configurado automáticamente por opentracing-spring-jaeger-cloud-starter biblioteca que tiene una clase llamada TracingAspect básicamente haciendo magia.

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

He agregado Dockerfile, docker-compose y docker- setup.sh para facilitar la ejecución de esta aplicación. Checkout Code y ejecute docker-setup.sh directamente.

Puede encontrar el código en mi enlace de Github.

Esto está inspirado en el Blog de Shekhar Gulati .

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *