• Jaeger开发入门(java版)


    欢迎访问我的GitHub

    https://github.com/zq2599/blog_demos

    内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

    本篇概览

    • 前文《分布式调用链跟踪工具Jaeger?两分钟极速体验》咱们体验了Jaeger的基本能力,今天就来编码实践,了解如何将让自己的应用集成Jaeger;

    • 本文的目标:今天咱们要在一个分布式系统中部署和使用jaeger,使用方式包括两种:首先是SDK内置的span,例如web请求、mysql或redis的操作等,这些会自动上报,第二种就是自定义span;

    • 总的来说,今天的实战步骤如下:

    1. 今天咱们要从零开发一个迷你的分布式系统,该系统架构如下图所示,可见有两个web应用:服务提供方jaeger-service-provider和服务调用方jaeger-service-consumer,再加一个redis:

    在这里插入图片描述

    1. jaeger-service-consumer收到用户通过浏览器发来的http请求时,会调用jaeger-service-provider提供的web服务,而jaeger-service-provider又会操作一次redis,整个流程与典型的分布式系统类似

    2. jaeger-service-consumerjaeger-service-provider在响应服务的过程中,都会将本次服务相关的数据上报到jaeger,这样咱们在jaeger的web页面就能观察到客户的一次请求会经过那些应用,关键位置耗时多少,关键参数是哪些等等;

    3. 将所有应用制作成镜像,再编写docker-compose.yml文件集成它们

    4. 运行,验证

    参考文章

    • 本文中会将springboot应用制作成docker镜像,如果您想了解详细的制作过程,可以参考以下两篇文章:
    1. 《体验SpringBoot(2.3)应用制作Docker镜像(官方方案)》
    2. 《详解SpringBoot(2.3)应用制作Docker镜像(官方方案)》

    jaeger接入套路

    • 先提前总结Spring Cloud应用接入jaeger的套路,以方便您的使用:
    1. 添加依赖库opentracing-spring-jaeger-cloud-starter,我这里是3.3.1版本
    2. 配置jaeger远程端口
    3. 创建配置类,向spring环境注册TracerBuilderCustomizer实例
    4. 在需要使用自定义span的代码中,用@Autowired注解引入Trace,使用它的API定制span
    5. 可以创建span,还可以基于已有span创建子span
    6. 除了指定span的名字,还能借助Trace的API给span增加标签(tag)和日志(log),这些都会在jaeger的web页面展示出来
    • 以上六步就是常规接入套路,接下来的实战就是按照此套路进行的

    源码下载

    名称 链接 备注
    项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
    git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
    git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
    • 这个git项目中有多个文件夹,本篇的源码在spring-cloud-tutorials文件夹下,如下图红框所示:

    在这里插入图片描述

    • spring-cloud-tutorials文件夹下有多个子工程,本篇的代码是jaeger-service-consumerjaeger-service-provider,如下图红框所示:

    在这里插入图片描述

    创建web工程之一:jaeger-service-provider

    • 为了方便管理依赖库版本,jaeger-service-provider工程是作为spring-cloud-tutorials的子工程创建的,其pom.xml如下:
    <?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>
    
    • 配置文件application.yml,注意由于后面会用到docker-compose,因此redis和jaeger的地址都无需填写具体的IP,只要填写它们的容器名即可:
    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
    
    • 配置类:
    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());
        }
    }
    
    • 另外,由于本篇的重点是jaeger,因此redis相关代码就不贴出来了,有需要的读者请在此查看:RedisConfig.javaRedisUtils.java

    • 接下来看看如何使用Trace的实例来定制span,下面是定了span及其子span的web接口类,请注意trace的API的使用,代码中已有详细注释,就不多赘述了:

    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;
        }
    }
    
    • 编码已经结束,接下来要将此工程制作成docker镜像了,新建Dockerfile文件,和pom.xml在同一个目录下:
    # 指定基础镜像,这是分阶段构建的前期阶段
    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"]
    
    • 先在父工程spring-cloud-tutorials的pom.xml所在目录执行以下命令完成编译构建:
    mvn clean package -U -DskipTests
    
    • 再在Dockerfile所在目录执行以下命令制作docker镜像:
    docker build -t bolingcavalry/jaeger-service-provider:0.0.1 .
    
    • 至此,jaeger-service-provider相关开发已经完成

    创建web工程之二:jaeger-service-consumer

    • jaeger-service-consumer工程的创建过程和jaeger-service-provider如出一辙,甚至还要更简单一些(不操作redis),所以描述其开发过程的内容尽量简化,以节省篇幅

    • pom.xml相比jaeger-service-provider的,少了redis依赖,其他可以照抄

    • application.yml也少了redis:

    spring:
      application:
        name: jaeger-service-consumer
    opentracing:
      jaeger:
        enabled: true
        udp-sender:
          host: jaeger
          port: 6831
    
    • 配置类JaegerConfig.java可以照抄jaeger-service-provider的

    • 由于要远程调用jaeger-service-provider的web接口,因此新增restTemplate的配置类:

    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;
        }
    }
    
    • 关键代码是web接口的实现,会通过restTemplate调用jaeger-service-provider的接口:
    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;
        }
    }
    
    • 接下来是编译构建制作docker镜像,和前面的jaeger-service-provider一样;

    docker-compose.yml文件编写

    • 现在咱们要将所有服务都运行起来了,先盘点一共有哪些服务要在docker-compose中启动的,如下所示,共计四个:
    1. jaeger
    2. redis
    3. jaeger-service-provider
    4. jaeger-service-consumer
    • 完整的docker-compose.yml内容如下:
    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
    
    • 至此,开发工作已全部完成,开始验证

    验证

    • 在docker-compose.yml所在目录执行命令docker-compose up -d,即可启动所有容器:
    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
    

    在这里插入图片描述

    在这里插入图片描述

    • 再去jaeger上可以看到上述访问的追踪详情:

    在这里插入图片描述

    • 点击上图红框3,可以展开此trace的所有span详情,如下图,红框中是咱们程序中自定义的span,绿框中的全是SDK自带的span,而蓝框中是redis的span的tag,该tag的值就是本次写redis操作的key,借助tag可以在定位问题的时候提供关键线索:

    在这里插入图片描述

    • 点开上图红框中的自定义span,如下图所示,tag和log都和代码对应上了:

    在这里插入图片描述

    • 至此,Spring Cloud应用接入和使用Jaeger的基本操作就全部完成了,希望如果您正在接入Jaeger,希望本文能给您一些参考,接下来的文章,咱们会继续深入学习Jaeger,了解它的更多特性;

    你不孤单,欣宸原创一路相伴

    1. Java系列
    2. Spring系列
    3. Docker系列
    4. kubernetes系列
    5. 数据库+中间件系列
    6. DevOps系列

    欢迎关注公众号:程序员欣宸

    微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...
    https://github.com/zq2599/blog_demos

  • 相关阅读:
    Leetcode(680) ;验证回文字符串 Ⅱ
    mysql常用操作语句
    组合索引问题
    php生成一维码以及保存-转载
    php后台实现页面跳转的方法-转载
    php操作表格(写)
    虚拟机复制后上网冲突的问题
    centos下安装nginx(转载)
    虚拟机与宿主机可以互相ping通,但是外网不能
    防火墙设置:虚拟机ping不通主机,但是主机可以ping通虚拟机(转载)
  • 原文地址:https://www.cnblogs.com/bolingcavalry/p/15700712.html
Copyright © 2020-2023  润新知