Jaeger開發入門(java版)

程序員欣宸 2021-09-19 22:08:02 阅读数:969

jaeger java

歡迎訪問我的GitHub

這裏分類和匯總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 前文《分布式調用鏈跟踪工具Jaeger?兩分鐘極速體驗》咱們體驗了Jaeger的基本能力,今天就來編碼實踐,了解如何將讓自己的應用集成Jaeger;
  • 本文的目標:今天咱們要在一個分布式系統中部署和使用jaeger,使用方式包括兩種:首先是SDK內置的span,例如web請求、mysql或redis的操作等,這些會自動上報,第二種就是自定義span;
  • 總的來說,今天的實戰步驟如下:
  1. 今天咱們要從零開發一個迷你的分布式系統,該系統架構如下圖所示,可見有兩個web應用:服務提供方jaeger-service-provider和服務調用方jaeger-service-consumer,再加一個redis:
    在這裏插入圖片描述
  2. jaeger-service-consumer收到用戶通過瀏覽器發來的http請求時,會調用jaeger-service-provider提供的web服務,而jaeger-service-provider又會操作一次redis,整個流程與典型的分布式系統類似
  3. jaeger-service-consumerjaeger-service-provider在響應服務的過程中,都會將本次服務相關的數據上報到jaeger,這樣咱們在jaeger的web頁面就能觀察到客戶的一次請求會經過那些應用,關鍵比特置耗時多少,關鍵參數是哪些等等;
  4. 將所有應用制作成鏡像,再編寫docker-compose.yml文件集成它們
  5. 運行,驗證

參考文章

  • 本文中會將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頁面展示出來
  • 以上六步就是常規接入套路,接下來的實戰就是按照此套路進行的

源碼下載

  • 本篇實戰中的完整源碼可在GitHub下載到,地址和鏈接信息如下錶所示(https://github.com/zq2599/blog_demos):
名稱 鏈接 備注
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]: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
  • 瀏覽器打開http://localhost:16686/search,熟悉的jaeger頁面:
    在這裏插入圖片描述
  • 調用jaeger-service-consumer的web服務,瀏覽器訪問http://localhost:18080/hello
    在這裏插入圖片描述
  • 再去jaeger上可以看到上述訪問的追踪詳情:
    在這裏插入圖片描述
  • 點擊上圖紅框3,可以展開此trace的所有span詳情,如下圖,紅框中是咱們程序中自定義的span,綠框中的全是SDK自帶的span,而藍框中是redis的span的tag,該tag的值就是本次寫redis操作的key,借助tag可以在定比特問題的時候提供關鍵線索:
    在這裏插入圖片描述
  • 點開上圖紅框中的自定義span,如下圖所示,tag和log都和代碼對應上了:
    在這裏插入圖片描述
  • 至此,Spring Cloud應用接入和使用Jaeger的基本操作就全部完成了,希望如果您正在接入Jaeger,希望本文能給您一些參考,接下來的文章,咱們會繼續深入學習Jaeger,了解它的更多特性;
版权声明:本文为[程序員欣宸]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210919220802124A.html