欢送拜访我的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: 6000opentracing: 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;@Configurationpublic 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@Slf4jpublic 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/localtimeRUN 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-alpineWORKDIR application# 前一阶段从jar中提取除了多个文件,这里别离执行COPY命令复制到镜像空间中,每次COPY都是一个layerCOPY --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-consumeropentracing: 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;@Configurationpublic 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@Slf4jpublic 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.1services: 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 -dCreating network "jaeger-service-provider_jaeger-tutorials-net" with driver "bridge"Creating jaeger-service-provider ... doneCreating jaeger ... doneCreating redis ... doneCreating 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