1

Welcome to my GitHub

https://github.com/zq2599/blog_demos

Content: Classification and summary of all original articles and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;

Overview of this article

  • Previous article "Distributed call chain tracking tool Jaeger? Two minutes of speed experience" We have experienced the basic capabilities of Jaeger, and today we will coding practice to learn how to integrate Jaeger into our applications;
  • The goal of this article: Today we want to deploy and use jaeger in a distributed system. There are two ways to use it: First, the built-in span of the SDK, such as web requests, mysql or redis operations, etc., which will be automatically reported, and the second Is a custom span;
  • In general, today’s actual combat steps are as follows:
  1. Today we are going to develop a mini distributed system from scratch. The system architecture is shown in the figure below. It can be seen that there are two web applications: service provider <font color="blue">jaeger-service-provider</font> and service The caller <font color="blue">jaeger-service-consumer</font>, add a redis:

在这里插入图片描述

  1. When jaeger-service-consumer receives an http request from a user through a browser, it will call the web service provided by <font color="blue">jaeger-service-provider</font>, and <font color="blue" >jaeger-service-provider</font> will operate redis again, the whole process is similar to a typical distributed system
  2. <font color="blue">jaeger-service-consumer</font> and <font color="blue">jaeger-service-provider</font> in the process of responding to the service, will include the data related to this service Report to jaeger, so that we can observe on jaeger's web page that a customer's request will pass through those applications, how long is the key location, what are the key parameters, etc.;
  3. Make all applications into mirror images, and then write the docker-compose.yml file to integrate them
  4. Run, verify

Reference article

jaeger access routine

  • First summarize the Spring Cloud application access jaeger routines in advance to facilitate your use:
  • Add dependency library <font color="blue">opentracing-spring-jaeger-cloud-starter</font>, my version is 3.3.1
  • Configure jaeger remote port
  • Create a configuration class and register an instance of TracerBuilderCustomizer with the spring environment
  • In the code that needs to use a custom span, use the @Autowired annotation to introduce Trace, and use its API to customize the span
  • You can create a span, or create a sub-span based on an existing span
  • In addition to specifying the name of the span, you can also use the Trace API to add tags and logs to the span, which will be displayed on the jaeger web page
  • The above six steps are conventional access routines, and the next actual combat is carried out according to this routine

Source download

nameLinkRemark
Project homepagehttps://github.com/zq2599/blog_demosThe project's homepage on GitHub
git warehouse address (https)https://github.com/zq2599/blog_demos.gitThe warehouse address of the source code of the project, https protocol
git warehouse address (ssh)git@github.com:zq2599/blog_demos.gitThe warehouse address of the source code of the project, ssh protocol
  • There are multiple folders in this git project. The source code of this article is under the <font color="blue">spring-cloud-tutorials</font> folder, as shown in the red box in the following figure:

在这里插入图片描述

  • There are multiple sub-projects under the <font color="blue">spring-cloud-tutorials</font> folder. The code for this article is <font color="red">jaeger-service-consumer</font> and <font color="red">jaeger-service-provider</font>, as shown in the red box in the following figure:

在这里插入图片描述

Create one of the web projects: jaeger-service-provider

  • In order to facilitate the management of dependent library versions, the <font color="blue">jaeger-service-provider</font> project is created as a sub-project of spring-cloud-tutorials, and its pom.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-tutorials</artifactId>
        <groupId>com.bolingcavalry</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>jaeger-service-provider</artifactId>

    <dependencies>

        <dependency>
            <groupId>com.bolingcavalry</groupId>
            <artifactId>common</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>io.opentracing.contrib</groupId>
            <artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layers>
                        <enabled>true</enabled>
                    </layers>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • The configuration file application.yml, note that since docker-compose will be used later, neither redis nor jaeger addresses need to fill in the specific IP, just fill in their container name:
spring:
  application:
    name: jaeger-service-provider
  redis:
    database: 0
    # Redis服务器地址 写你的ip
    host: redis
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password:
    # 连接池最大连接数(使用负值表示没有限制  类似于mysql的连接池
    jedis:
      pool:
        max-active: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 表示连接池的链接拿完了 现在去申请需要等待的时间
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒) 去链接redis服务端
    timeout: 6000

opentracing:
  jaeger:
    enabled: true
    udp-sender:
      host: jaeger
      port: 6831
  • Configuration class:
package com.bolingcavalry.jaeger.provider.config;

import io.jaegertracing.internal.MDCScopeManager;
import io.opentracing.contrib.java.spring.jaeger.starter.TracerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JaegerConfig {
    @Bean
    public TracerBuilderCustomizer mdcBuilderCustomizer() {
        // 1.8新特性,函数式接口
        return builder -> builder.withScopeManager(new MDCScopeManager.Builder().build());
    }
}
  • In addition, since the focus of this article is jaeger, redis related code will not be posted, readers who need it, please check here: RedisConfig.java , RedisUtils.java
  • Next, let’s take a look at how to use the Trace instance to customize the span. The following is the web interface class that defines the span and its sub-spans. Please pay attention to the use of the trace API. There are detailed comments in the code, so I won’t go into details:
package com.bolingcavalry.jaeger.provider.controller;

import com.bolingcavalry.common.Constants;
import com.bolingcavalry.jaeger.provider.util.RedisUtils;
import io.opentracing.Span;
import io.opentracing.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
@Slf4j
public class HelloController {

    @Autowired
    private Tracer tracer;

    @Autowired
    private RedisUtils redisUtils;

    private String dateStr(){
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
    }

    /**
     * 模拟业务执行,耗时100毫秒
     * @param parentSpan
     */
    private void mockBiz(Span parentSpan) {
        // 基于指定span,创建其子span
        Span span = tracer.buildSpan("mockBizChild").asChildOf(parentSpan).start();

        log.info("hello");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        span.finish();
    }

    /**
     * 返回字符串类型
     * @return
     */
    @GetMapping("/hello")
    public String hello() {
        long startTime = System.currentTimeMillis();

        // 生成当前时间
        String timeStr = dateStr();

        // 创建一个span,在创建的时候就添加一个tag
        Span span = tracer.buildSpan("mockBiz")
                    .withTag("time-str", timeStr)
                    .start();

        // span日志
        span.log("normal span log");

        // 模拟一个耗时100毫秒的业务
        mockBiz(span);

        // 增加一个tag
        span.setTag("tiem-used", System.currentTimeMillis()-startTime);

        // span结束
        span.finish();

        // 写入redis
        redisUtils.set("Hello",  timeStr);
        // 返回
        return Constants.HELLO_PREFIX + ", " + timeStr;
    }
}
  • The coding is over. Next, we need to make this project into a docker image, create a new Dockerfile file, and pom.xml in the same directory:
# 指定基础镜像,这是分阶段构建的前期阶段
FROM openjdk:8-jdk-alpine as builder

# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

# 执行工作目录
WORKDIR application
# 配置参数
ARG JAR_FILE=target/*.jar
# 将编译构建得到的jar文件复制到镜像空间中
COPY ${JAR_FILE} application.jar
# 通过工具spring-boot-jarmode-layertools从application.jar中提取拆分后的构建结果
RUN java -Djarmode=layertools -jar application.jar extract

# 正式构建镜像
FROM openjdk:8-jdk-alpine
WORKDIR application
# 前一阶段从jar中提取除了多个文件,这里分别执行COPY命令复制到镜像空间中,每次COPY都是一个layer
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
  • First execute the following command in the directory where the pom.xml of the parent project <font color="blue">spring-cloud-tutorials</font> is located to complete the compilation and construction:
mvn clean package -U -DskipTests
  • Then execute the following command in the directory where the Dockerfile is located to make a docker image:
docker build -t bolingcavalry/jaeger-service-provider:0.0.1 .
  • So far, the development of <font color="blue">jaeger-service-provider</font> has been completed

Create web project two: jaeger-service-consumer

  • The creation process of the jaeger-service-consumer project is the same as that of the jaeger-service-provider, and even simpler (does not operate redis), so the content describing the development process should be simplified as much as possible to save space
  • Compared with jaeger-service-provider, pom.xml has less redis dependency, others can be copied
  • Application.yml also lacks redis:
spring:
  application:
    name: jaeger-service-consumer
opentracing:
  jaeger:
    enabled: true
    udp-sender:
      host: jaeger
      port: 6831
  • The configuration class JaegerConfig.java can be copied from jaeger-service-provider
  • Since the web interface of jaeger-service-provider needs to be called remotely, a new configuration class for restTemplate is added:
package com.bolingcavalry.jaeger.consumer.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);
        factory.setConnectTimeout(15000);
        return factory;
    }
}
  • The key code is the implementation of the web interface, which will call the interface of jaeger-service-provider through restTemplate:
package com.bolingcavalry.jaeger.consumer.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class HelloConsumerController {

    @Autowired
    RestTemplate restTemplate;

    /**
     * 返回字符串类型
     * @return
     */
    @GetMapping("/hello")
    public String hello() {
        String url = "http://jaeger-service-provider:8080/hello";
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
        StringBuffer sb = new StringBuffer();
        HttpStatus statusCode = responseEntity.getStatusCode();
        String body = responseEntity.getBody();

        // 返回
        return "response from jaeger-service-provider \nstatus : " + statusCode + "\nbody : " + body;
    }
}
  • The next step is to compile and build the docker image, which is the same as the previous jaeger-service-provider;

docker-compose.yml file writing

  • Now we need to run all the services. First, check which services should be started in docker-compose, as shown below, there are four in total:
  • jaeger
  • redis
  • jaeger-service-provider
  • jaeger-service-consumer
  • The complete docker-compose.yml content is as follows:
version: '3.0'

networks:
  jaeger-tutorials-net:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.1.0/24
          gateway: 192.168.1.1

services:
  jaeger:
    image: jaegertracing/all-in-one:1.26
    container_name: jaeger
    # 处理时钟漂移带来的计算出负数的问题
    command: ["--query.max-clock-skew-adjustment=100ms"]
    #选择网络
    networks:
      - jaeger-tutorials-net
    #选择端口
    ports:
      - 16686:16686/tcp
    restart: always
  redis:
    image: redis:6.2.5
    container_name: redis
    #选择网络
    networks:
      - jaeger-tutorials-net
    restart: always
  jaeger-service-provider:
    image: bolingcavalry/jaeger-service-provider:0.0.1
    container_name: jaeger-service-provider
    #选择网络
    networks:
      - jaeger-tutorials-net
    restart: always
  jaeger-service-consumer:
    image: bolingcavalry/jaeger-service-consumer:0.0.1
    container_name: jaeger-consumer-provider
    #选择端口
    ports:
      - 18080:8080/tcp
    #选择网络
    networks:
      - jaeger-tutorials-net
    restart: always
  • At this point, the development work has been completed, and the verification begins

verify

  • Execute the command <font color="blue">docker-compose up -d</font> in the directory where docker-compose.yml is located to start all containers:
will$ docker-compose up -d
Creating network "jaeger-service-provider_jaeger-tutorials-net" with driver "bridge"
Creating jaeger-service-provider  ... done
Creating jaeger                   ... done
Creating redis                    ... done
Creating jaeger-consumer-provider ... done

在这里插入图片描述

  • Call the web service of <font color="blue">jaeger-service-consumer</font>, and the browser visits <font color="blue"> http://localhost:18080/hello </font>:

在这里插入图片描述

  • Go to jaeger again to see the tracking details of the above visit:

在这里插入图片描述

  • Click the red box 3 in the above figure to expand all the span details of this trace. As shown in the figure below, the red box is the custom span in our program, the green box is the span that comes with the SDK, and the blue box is redis The tag of the span. The value of the tag is the key of this redis operation. With the help of the tag, it can provide key clues when locating the problem:

在这里插入图片描述

  • Click on the custom span in the red box of the figure above, as shown in the figure below, the tag and log are corresponding to the code:

在这里插入图片描述

  • At this point, the basic operations of Spring Cloud application access and use of Jaeger are all completed. I hope that if you are accessing Jaeger, I hope this article can give you some reference. In the next article, we will continue to learn Jaeger in depth and understand its more Multi-characteristics

You are not lonely, Xinchen is with you all the way

  1. Java series
  2. Spring series
  3. Docker series
  4. kubernetes series
  5. database + middleware series
  6. DevOps series

Welcome to pay attention to the public account: programmer Xin Chen

Search "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos

程序员欣宸
147 声望24 粉丝

热爱Java和Docker