SpringBoot集成TraceId的方案詳解(追蹤ID)
一、TraceId 是什么?
- TraceId 就是給每一條請(qǐng)求鏈路分配的全局唯一 “身份證號(hào)” —— 不管是單體項(xiàng)目里的一次接口請(qǐng)求,還是分布式微服務(wù)里跨多個(gè)服務(wù)的調(diào)用流程,只要屬于同一條請(qǐng)求鏈路,全程共用同一個(gè) TraceId。
- 你可以把它理解成:用戶在 APP 點(diǎn)了 “下單” 按鈕,這個(gè)請(qǐng)求從前端→網(wǎng)關(guān)→訂單服務(wù)→用戶服務(wù)→支付服務(wù),整個(gè)流程會(huì)被分配一個(gè)唯一的 TraceId,所有環(huán)節(jié)的日志、調(diào)用記錄都帶上這個(gè) ID。排查問題時(shí),只要搜這個(gè) TraceId,就能把整條鏈路的日志 / 調(diào)用記錄都揪出來,不用在海量日志里大海撈針。
場(chǎng)景一:無SkyWalking的最優(yōu)實(shí)現(xiàn)
核心方案
MDC(日志上下文) + Filter(入口生成/讀取TraceId) + TaskDecorator(異步線程傳遞) + Feign/RestTemplate攔截器(微服務(wù)跨服務(wù)傳遞)
適用場(chǎng)景
- 中小型單體/微服務(wù)項(xiàng)目;
- 無可視化鏈路分析需求,僅需日志攜帶TraceId定位問題;
- 不愿引入額外中間件(SkyWalking OAP)增加運(yùn)維成本。
1. 依賴配置(拆分SpringBoot/SpringCloud)
1.1 SpringBoot單體項(xiàng)目依賴
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version> <!-- 3.x可替換為3.2.0 -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot-traceid-no-skywalking</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- SpringBoot Web核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 工具類:判空/字符串處理 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<!-- 可選:Hutool(雪花ID生成) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<!-- 測(cè)試依賴(非核心) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.15</version>
</plugin>
</plugins>
</build>
</project>1.2 SpringCloud微服務(wù)項(xiàng)目依賴
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version> <!-- 對(duì)應(yīng)Cloud 2021.0.x -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>cloud-traceid-no-skywalking</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- 基礎(chǔ)依賴(同SpringBoot) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<!-- 微服務(wù)核心:OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 測(cè)試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Cloud版本管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.15</version>
</plugin>
</plugins>
</build>
</project>2. 核心代碼實(shí)現(xiàn)(無SkyWalking專屬)
2.1 TraceId工具類(自定義生成/傳遞)
package com.example.traceid.util;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import java.util.UUID;
/**
* 無SkyWalking時(shí):自定義TraceId生成/管理工具類
*/
public class TraceIdUtils {
// MDC中TraceId的Key(日志配置用%X{traceId}讀?。?
public static final String TRACE_ID_MDC_KEY = "traceId";
// HTTP頭傳遞TraceId的Key
public static final String TRACE_ID_HEADER_KEY = "X-Trace-Id";
// 雪花ID生成器(高并發(fā)用,單機(jī)/低并發(fā)可只用UUID)
private static final Snowflake SNOWFLAKE = IdUtil.createSnowflake(
NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()) % 32, 1L
);
/**
* 生成TraceId:低并發(fā)用UUID,高并發(fā)用雪花ID
*/
public static String generateTraceId() {
// 方案1:UUID(默認(rèn),無需額外依賴)
return UUID.randomUUID().toString().replace("-", "");
// 方案2:雪花ID(高并發(fā)切換)
// return SNOWFLAKE.nextIdStr();
}
/**
* 從MDC獲取TraceId,兜底返回UNKNOWN
*/
public static String getTraceId() {
String traceId = MDC.get(TRACE_ID_MDC_KEY);
return StringUtils.isBlank(traceId) ? "UNKNOWN" : traceId;
}
/**
* 手動(dòng)設(shè)置TraceId到MDC
*/
public static void setTraceIdToMdc(String traceId) {
if (StringUtils.isNotBlank(traceId)) {
MDC.put(TRACE_ID_MDC_KEY, traceId);
}
}
/**
* 清除MDC中的TraceId
*/
public static void clearTraceIdFromMdc() {
MDC.remove(TRACE_ID_MDC_KEY);
}
}2.2 TraceId過濾器(請(qǐng)求入口生成/讀取TraceId)
package com.example.traceid.filter;
import com.example.traceid.util.TraceIdUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 無SkyWalking時(shí):請(qǐng)求入口生成/讀取TraceId,放入MDC
* 優(yōu)先級(jí)最高,保證所有邏輯執(zhí)行前已有TraceId
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TraceIdMdcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 1. 優(yōu)先讀取上游傳遞的TraceId(網(wǎng)關(guān)/其他服務(wù))
String traceId = httpRequest.getHeader(TraceIdUtils.TRACE_ID_HEADER_KEY);
// 2. 上游無則生成新的
if (StringUtils.isBlank(traceId)) {
traceId = TraceIdUtils.generateTraceId();
}
// 3. 放入MDC,供日志讀取
MDC.put(TraceIdUtils.TRACE_ID_MDC_KEY, traceId);
// 4. 執(zhí)行后續(xù)邏輯
chain.doFilter(request, response);
} finally {
// 核心:請(qǐng)求結(jié)束清除MDC,避免線程池復(fù)用導(dǎo)致TraceId錯(cuò)亂
MDC.clear();
}
}
}2.3 異步線程池配置(解決異步TraceId丟失)
package com.example.traceid.config;
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 無SkyWalking時(shí):異步線程池配置,傳遞MDC上下文
*/
@Configuration
@EnableAsync
public class AsyncTraceConfig {
// 自定義TaskDecorator:復(fù)制父線程MDC到子線程
private static class MdcTraceDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> parentMdc = MDC.getCopyOfContextMap();
return () -> {
try {
if (parentMdc != null) {
MDC.setContextMap(parentMdc);
}
runnable.run();
} finally {
MDC.clear(); // 子線程執(zhí)行完清除MDC
}
};
}
}
// 全局異步線程池
@Bean("asyncTraceExecutor")
public Executor asyncTraceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int core = Runtime.getRuntime().availableProcessors() * 2;
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-trace-");
executor.setTaskDecorator(new MdcTraceDecorator()); // 綁定裝飾器
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
}2.4 微服務(wù)跨服務(wù)傳遞(Feign/RestTemplate攔截器)
2.4.1 Feign攔截器(微服務(wù)專屬)
package com.example.traceid.interceptor;
import com.example.traceid.util.TraceIdUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
/**
* 無SkyWalking時(shí):Feign調(diào)用傳遞TraceId
*/
@Component
public class FeignTraceInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String traceId = TraceIdUtils.getTraceId();
if (!"UNKNOWN".equals(traceId)) {
template.header(TraceIdUtils.TRACE_ID_HEADER_KEY, traceId);
}
}
}2.4.2 RestTemplate攔截器(微服務(wù)專屬)
package com.example.traceid.config;
import com.example.traceid.util.TraceIdUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
/**
* 無SkyWalking時(shí):RestTemplate傳遞TraceId
*/
@Configuration
public class RestTemplateTraceConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
String traceId = TraceIdUtils.getTraceId();
if (!"UNKNOWN".equals(traceId)) {
request.getHeaders().add(TraceIdUtils.TRACE_ID_HEADER_KEY, traceId);
}
return execution.execute(request, body);
}));
return restTemplate;
}
}2.5 日志配置(logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<contextName>traceid-no-skywalking</contextName>
<!-- 控制臺(tái)輸出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<!-- 核心:%X{traceId}讀取MDC中的TraceId -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件輸出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>3. 無SkyWalking的核心驗(yàn)證
- 同步接口:日志中
[%X{traceId}]顯示自定義生成的TraceId; - 異步任務(wù):子線程日志TraceId與父線程一致;
- 微服務(wù)調(diào)用:下游服務(wù)日志TraceId與上游一致;
- 無任何額外中間件依賴,僅依賴Spring原生能力。
場(chǎng)景二:有SkyWalking的最優(yōu)實(shí)現(xiàn)
核心方案
復(fù)用SkyWalking TraceId + MDC綁定 + 上下文傳遞(異步/跨服務(wù))
核心差異
- 無需自定義生成TraceId,直接復(fù)用SkyWalking的全局TraceId;
- 日志中同時(shí)綁定SkyWalking TraceId,實(shí)現(xiàn)「日志TraceId=鏈路TraceId」;
- 異步/跨服務(wù)場(chǎng)景需傳遞SkyWalking上下文,保證鏈路不中斷。
1. 依賴配置(拆分SpringBoot/SpringCloud)
1.1 SpringBoot單體項(xiàng)目(新增SkyWalking依賴)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot-traceid-with-skywalking</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- 基礎(chǔ)依賴(同無SkyWalking) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<!-- SkyWalking核心依賴 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-spring-boot-starter</artifactId>
<version>8.16.0</version> <!-- 與SkyWalking OAP版本一致 -->
</dependency>
<!-- SkyWalking日志增強(qiáng):自動(dòng)綁定TraceId到MDC -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.16.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.15</version>
</plugin>
</plugins>
</build>
</project>1.2 SpringCloud微服務(wù)項(xiàng)目(新增SkyWalking依賴)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>cloud-traceid-with-skywalking</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- 基礎(chǔ)依賴(同無SkyWalking) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SkyWalking核心依賴 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-spring-boot-starter</artifactId>
<version>8.16.0</version>
</dependency>
<!-- SkyWalking Feign增強(qiáng):跨服務(wù)鏈路追蹤 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-feign</artifactId>
<version>8.16.0</version>
</dependency>
<!-- SkyWalking日志增強(qiáng) -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.16.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.15</version>
</plugin>
</plugins>
</build>
</project>2. SkyWalking極簡(jiǎn)部署(Docker,中小型項(xiàng)目首選)
# 1. 拉取OAP和UI鏡像(版本與項(xiàng)目依賴一致) docker pull apache/skywalking-oap-server:8.16.0 docker pull apache/skywalking-ui:8.16.0 # 2. 啟動(dòng)OAP(單節(jié)點(diǎn),H2存儲(chǔ),無需額外數(shù)據(jù)庫(kù)) docker run -d \ --name skywalking-oap \ -p 11800:11800 \ # Agent上報(bào)端口 -p 12800:12800 \ # UI訪問OAP端口 -e SW_STORAGE=h2 \ apache/skywalking-oap-server:8.16.0 # 3. 啟動(dòng)UI(可視化界面,訪問http://localhost:8080) docker run -d \ --name skywalking-ui \ -p 8080:8080 \ --link skywalking-oap:oap \ -e SW_OAP_ADDRESS=http://oap:12800 \ apache/skywalking-ui:8.16.0
3. 核心代碼實(shí)現(xiàn)(有SkyWalking專屬)
3.1 TraceId工具類(復(fù)用SkyWalking TraceId)
package com.example.traceid.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
/**
* 有SkyWalking時(shí):復(fù)用其TraceId,無需自定義生成
*/
public class TraceIdUtils {
// 復(fù)用SkyWalking的MDC Key(SW_TRACE_ID),也可自定義
public static final String TRACE_ID_MDC_KEY = "SW_TRACE_ID";
public static final String TRACE_ID_HEADER_KEY = "X-Trace-Id";
/**
* 獲取TraceId:優(yōu)先SkyWalking,兜底UNKNOWN
*/
public static String getTraceId() {
// SkyWalking原生TraceId
String skyWalkingTraceId = TraceContext.traceId();
if (StringUtils.isNotBlank(skyWalkingTraceId) && !"N/A".equals(skyWalkingTraceId)) {
return skyWalkingTraceId;
}
// 兜底讀取MDC
String mdcTraceId = MDC.get(TRACE_ID_MDC_KEY);
return StringUtils.isBlank(mdcTraceId) ? "UNKNOWN" : mdcTraceId;
}
/**
* 綁定SkyWalking TraceId到MDC(僅特殊場(chǎng)景用)
*/
public static void bindSkyWalkingTraceIdToMdc() {
String traceId = TraceContext.traceId();
if (StringUtils.isNotBlank(traceId) && !"N/A".equals(traceId)) {
MDC.put(TRACE_ID_MDC_KEY, traceId);
}
}
}3.2 TraceId過濾器(簡(jiǎn)化:僅綁定SkyWalking TraceId到MDC)
package com.example.traceid.filter;
import com.example.traceid.util.TraceIdUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
/**
* 有SkyWalking時(shí):過濾器僅綁定其TraceId到MDC,無需生成
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TraceIdMdcFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 綁定SkyWalking TraceId到MDC,供日志讀取
TraceIdUtils.bindSkyWalkingTraceIdToMdc();
chain.doFilter(request, response);
} finally {
MDC.clear();
// 釋放SkyWalking上下文(避免內(nèi)存泄漏)
TraceContext.release();
}
}
}3.3 異步線程池配置(傳遞SkyWalking上下文)
package com.example.traceid.config;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 有SkyWalking時(shí):異步線程池傳遞MDC+SkyWalking上下文
*/
@Configuration
@EnableAsync
public class AsyncTraceConfig {
private static class MdcTraceDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 復(fù)制父線程MDC和SkyWalking TraceId
Map<String, String> parentMdc = MDC.getCopyOfContextMap();
String parentSkyWalkingTraceId = TraceContext.traceId();
return () -> {
try {
// 子線程綁定MDC
if (parentMdc != null) {
MDC.setContextMap(parentMdc);
}
// 子線程綁定SkyWalking TraceId(核心:保證鏈路不中斷)
if (StringUtils.isNotBlank(parentSkyWalkingTraceId) && !"N/A".equals(parentSkyWalkingTraceId)) {
TraceContext.continueTrace(parentSkyWalkingTraceId);
}
runnable.run();
} finally {
MDC.clear();
TraceContext.release(); // 釋放SkyWalking上下文
}
};
}
}
@Bean("asyncTraceExecutor")
public Executor asyncTraceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int core = Runtime.getRuntime().availableProcessors() * 2;
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-trace-");
executor.setTaskDecorator(new MdcTraceDecorator());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}3.4 日志配置(綁定SkyWalking TraceId)
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 引入SkyWalking默認(rèn)配置 -->
<include resource="org/apache/skywalking/apm/toolkit/log/logback/default.xml"/>
<contextName>traceid-with-skywalking</contextName>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<!-- %X{SW_TRACE_ID}:SkyWalking原生TraceId -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{SW_TRACE_ID}] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{SW_TRACE_ID}] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>3.5 項(xiàng)目掛載SkyWalking Agent(核心步驟)
啟動(dòng)項(xiàng)目時(shí)添加JVM參數(shù)(本地/生產(chǎn)通用):
# 本地IDEA VM Options -javaagent:/path/to/skywalking-agent/skywalking-agent.jar -DSW_AGENT_NAME=boot-traceid-demo # 服務(wù)名(UI中顯示) -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800 # OAP地址 # 生產(chǎn)Jar啟動(dòng) java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \ -DSW_AGENT_NAME=cloud-traceid-demo \ -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.1.100:11800 \ -jar app.jar
4. 有SkyWalking的核心驗(yàn)證
- 日志中
[%X{SW_TRACE_ID}]顯示SkyWalking的全局TraceId; - 異步任務(wù):子線程TraceId與父線程一致,SkyWalking UI可看到異步鏈路;
- 微服務(wù)調(diào)用:上下游TraceId一致,SkyWalking UI可看到跨服務(wù)完整鏈路;
- 訪問SkyWalking UI(http://localhost:8080),可通過TraceId搜索整條鏈路。
核心差異總結(jié)
| 維度 | 無SkyWalking(純自研) | 有SkyWalking(復(fù)用TraceId) |
|---|---|---|
| TraceId生成 | 自定義(UUID/雪花ID) | 復(fù)用SkyWalking全局TraceId(無需自定義) |
| 核心目標(biāo) | 日志攜帶TraceId,定位問題 | 日志+可視化鏈路統(tǒng)一,支持跨服務(wù)/異步鏈路分析 |
| 運(yùn)維成本 | 0(僅代碼) | 低(單節(jié)點(diǎn)Docker部署OAP+UI) |
| 異步/跨服務(wù)處理 | 僅傳遞MDC上下文 | 傳遞MDC+SkyWalking上下文(保證鏈路不中斷) |
| 日志配置 | 讀取自定義MDC Key(traceId) | 讀取SkyWalking MDC Key(SW_TRACE_ID) |
到此這篇關(guān)于SpringBoot集成TraceId的方案詳解(追蹤ID)的文章就介紹到這了,更多相關(guān)SpringBoot集成TraceId內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于swagger參數(shù)與實(shí)體中參數(shù)不一致的原因分析
這篇文章主要介紹了基于swagger參數(shù)與實(shí)體中參數(shù)不一致的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Windows下Java+MyBatis框架+MySQL的開發(fā)環(huán)境搭建教程
這篇文章主要介紹了Windows下Java+MyBatis框架+MySQL的開發(fā)環(huán)境搭建教程,Mybatis對(duì)普通SQL語句的支持非常好,需要的朋友可以參考下2016-04-04
JAVA?SSE接口開發(fā)中的Spring?Security與異步線程池配置方法
本文詳細(xì)描述了在使用SpringSecurity和異步線程池進(jìn)行SSE接口開發(fā)時(shí)遇到的兩個(gè)問題:AccessDeniedException異常和SimpleAsyncTaskExecutor警告,文章還介紹了線程池參數(shù)的科學(xué)估算方法,并適用于SpringBoot/SpringMVC的SSE接口生產(chǎn)環(huán)境配置,感興趣的朋友一起看看吧2025-12-12
簡(jiǎn)單介紹區(qū)分applet和application的方法
applet和application都是Java語言編寫出來的應(yīng)用程序,本文簡(jiǎn)單介紹了二者的不同之處,需要的朋友可以參考下2017-09-09
Java?InheritableThreadLocal使用示例詳解
InheritableThreadLocal繼承了ThreadLocal,此類擴(kuò)展了ThreadLocal以提供從父線程到子線程的值的繼承:當(dāng)創(chuàng)建子線程時(shí),子線程接收父線程具有的所有可繼承線程局部變量的初始值。?通常子線程的值與父線程的值是一致的2022-09-09
一文盤點(diǎn)Java中常見內(nèi)存泄漏場(chǎng)景與解決方法
內(nèi)存泄漏 是指對(duì)象 已經(jīng)不再被程序使用,但因?yàn)槟承┰?nbsp;無法被垃圾回收器回收,長(zhǎng)期占用內(nèi)存,最終可能引發(fā)?OOM,本文會(huì)介紹常見的幾類內(nèi)存泄漏場(chǎng)景,大家可以避免一下2025-12-12

