https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
前文[《分布式调用链跟踪工具Jaeger?两分钟极速体验》]咱们体验了Jaeger的基本能力,今天就来编码实践,了解如何将让自己的应用集成Jaeger;
本文的目标:今天咱们要在一个分布式系统中部署和使用jaeger,使用方式包括两种:首先是SDK内置的span,例如web请求、mysql或redis的操作等,这些会自动上报,第二种就是自定义span;
总的来说,今天的实战步骤如下:
jaeger-service-consumer收到用户通过浏览器发来的http请求时,会调用jaeger-service-provider提供的web服务,而jaeger-service-provider又会操作一次redis,整个流程与典型的分布式系统类似
jaeger-service-consumer和jaeger-service-provider在响应服务的过程中,都会将本次服务相关的数据上报到jaeger,这样咱们在jaeger的web页面就能观察到客户的一次请求会经过那些应用,关键位置耗时多少,关键参数是哪些等等;
将所有应用制作成镜像,再编写docker-compose.yml文件集成它们
运行,验证
名称 | 链接 | 备注 |
---|---|---|
项目主页 | 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协议 |
<?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>
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; } }
# 指定基础镜像,这是分阶段构建的前期阶段 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"]
mvn clean package -U -DskipTests
docker build -t bolingcavalry/jaeger-service-provider:0.0.1 .
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; } }
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; } }
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
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
我是欣宸,期待与您一同畅游Java世界…
https://github.com/zq2599/blog_demos