欢送拜访我的 GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;
本篇概览
- 前文《分布式调用链跟踪工具 Jaeger?两分钟极速体验》咱们体验了 Jaeger 的根本能力,明天就来编码实际,理解如何将让本人的利用集成 Jaeger;
- 本文的指标:明天咱们要在一个分布式系统中部署和应用 jaeger,应用形式包含两种:首先是 SDK 内置的 span,例如 web 申请、mysql 或 redis 的操作等,这些会主动上报,第二种就是自定义 span;
- 总的来说,明天的实战步骤如下:
- 明天咱们要从零开发一个迷你的分布式系统,该零碎架构如下图所示,可见有两个 web 利用:服务提供方 <font color=”blue”>jaeger-service-provider</font> 和服务调用方 <font color=”blue”>jaeger-service-consumer</font>,再加一个 redis:
- jaeger-service-consumer 收到用户通过浏览器发来的 http 申请时,会调用 <font color=”blue”>jaeger-service-provider</font> 提供的 web 服务,而 <font color=”blue”>jaeger-service-provider</font> 又会操作一次 redis,整个流程与典型的分布式系统相似
- <font color=”blue”>jaeger-service-consumer</font> 和 <font color=”blue”>jaeger-service-provider</font> 在响应服务的过程中,都会将本次服务相干的数据上报到 jaeger,这样咱们在 jaeger 的 web 页面就能察看到客户的一次申请会通过那些利用,要害地位耗时多少,要害参数是哪些等等;
- 将所有利用制作成镜像,再编写 docker-compose.yml 文件集成它们
- 运行,验证
参考文章
- 本文中会将 springboot 利用制作成 docker 镜像,如果您想理解具体的制作过程,能够参考以下两篇文章:
- 《体验 SpringBoot(2.3)利用制作 Docker 镜像(官网计划)》
- 《详解 SpringBoot(2.3)利用制作 Docker 镜像(官网计划)》
jaeger 接入套路
- 先提前总结 Spring Cloud 利用接入 jaeger 的套路,以不便您的应用:
- 增加依赖库 <font color=”blue”>opentracing-spring-jaeger-cloud-starter</font>,我这里是 3.3.1 版本
- 配置 jaeger 近程端口
- 创立配置类,向 spring 环境注册 TracerBuilderCustomizer 实例
- 在须要应用自定义 span 的代码中,用 @Autowired 注解引入 Trace,应用它的 API 定制 span
- 能够创立 span,还能够基于已有 span 创立子 span
- 除了指定 span 的名字,还能借助 Trace 的 API 给 span 减少标签 (tag) 和日志(log),这些都会在 jaeger 的 web 页面展现进去
- 以上六步就是惯例接入套路,接下来的实战就是依照此套路进行的
源码下载
- 本篇实战中的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo…):
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blo… | 该我的项目在 GitHub 上的主页 |
git 仓库地址(https) | https://github.com/zq2599/blo… | 该我的项目源码的仓库地址,https 协定 |
git 仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh 协定 |
- 这个 git 我的项目中有多个文件夹,本篇的源码在 <font color=”blue”>spring-cloud-tutorials</font> 文件夹下,如下图红框所示:
- <font color=”blue”>spring-cloud-tutorials</font> 文件夹下有多个子工程,本篇的代码是 <font color=”red”>jaeger-service-consumer</font> 和 <font color=”red”>jaeger-service-provider</font>,如下图红框所示:
创立 web 工程之一:jaeger-service-provider
- 为了方便管理依赖库版本,<font color=”blue”>jaeger-service-provider</font> 工程是作为 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.java、RedisUtils.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"]
- 先在父工程 <font color=”blue”>spring-cloud-tutorials</font> 的 pom.xml 所在目录执行以下命令实现编译构建:
mvn clean package -U -DskipTests
- 再在 Dockerfile 所在目录执行以下命令制作 docker 镜像:
docker build -t bolingcavalry/jaeger-service-provider:0.0.1 .
- 至此,<font color=”blue”>jaeger-service-provider</font> 相干开发曾经实现
创立 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 中启动的,如下所示,共计四个:
- jaeger
- redis
- jaeger-service-provider
- 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 所在目录执行命令 <font color=”blue”>docker-compose up -d</font>,即可启动所有容器:
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
- 浏览器关上 <font color=”blue”>http://localhost:16686/search</font>,相熟的 jaeger 页面:
- 调用 <font color=”blue”>jaeger-service-consumer</font> 的 web 服务,浏览器拜访 <font color=”blue”>http://localhost:18080/hello</font>:
- 再去 jaeger 上能够看到上述拜访的追踪详情:
- 点击上图红框 3,能够开展此 trace 的所有 span 详情,如下图,红框中是咱们程序中自定义的 span,绿框中的全是 SDK 自带的 span,而蓝框中是 redis 的 span 的 tag,该 tag 的值就是本次写 redis 操作的 key,借助 tag 能够在定位问题的时候提供要害线索:
- 点开上图红框中的自定义 span,如下图所示,tag 和 log 都和代码对应上了:
- 至此,Spring Cloud 利用接入和应用 Jaeger 的基本操作就全副实现了,心愿如果您正在接入 Jaeger,心愿本文能给您一些参考,接下来的文章,咱们会持续深刻学习 Jaeger,理解它的更多个性;
你不孤独,欣宸原创一路相伴
- Java 系列
- Spring 系列
- Docker 系列
- kubernetes 系列
- 数据库 + 中间件系列
- DevOps 系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos