項目地址:https://github.com/nemoob/atlas-log 開箱即用。
前言
在微服務架構中,一個用戶請求往往會經過多個服務的協作處理。本章將實現一個輕量級的鏈路追蹤系統,讓日志具備分布式追蹤能力。
分布式鏈路追蹤基礎概念
鏈路追蹤的核心價值
追蹤的核心元素:
- 🎯 TraceID: 標識一次完整的請求鏈路
- 🔗 SpanID: 標識鏈路中的一個操作節點
- 👤 UserID: 標識發起請求的用戶
- 📝 RequestID: 標識單次HTTP請求
TraceContext - 追蹤上下文設計
package com.simpleflow.log.context;import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 追蹤上下文 - 存儲分布式鏈路追蹤相關的信息*/
public class TraceContext {private String requestId; // 請求IDprivate String traceId; // 追蹤IDprivate String spanId; // 跨度IDprivate String userId; // 用戶IDprivate String sessionId; // 會話IDprivate LocalDateTime startTime;private final Map<String, String> extraInfo; // 額外信息public TraceContext() {this.extraInfo = new ConcurrentHashMap<>();this.startTime = LocalDateTime.now();}public TraceContext(String requestId, String traceId) {this();this.requestId = requestId;this.traceId = traceId;}/*** 添加額外信息*/public TraceContext addExtra(String key, String value) {if (key != null && value != null) {this.extraInfo.put(key, value);}return this;}/*** 創建子上下文(用于子線程或子操作)*/public TraceContext createChild() {TraceContext child = new TraceContext();child.requestId = this.requestId;child.traceId = this.traceId;child.spanId = generateChildSpanId(this.spanId);child.userId = this.userId;child.sessionId = this.sessionId;child.startTime = LocalDateTime.now();child.extraInfo.putAll(this.extraInfo);return child;}/*** 繼承上下文(用于InheritableThreadLocal)*/public TraceContext inherit() {TraceContext inherited = new TraceContext();inherited.requestId = this.requestId;inherited.traceId = this.traceId;inherited.spanId = this.spanId; // 繼承時保持相同的spanIdinherited.userId = this.userId;inherited.sessionId = this.sessionId;inherited.startTime = this.startTime;inherited.extraInfo.putAll(this.extraInfo);return inherited;}private String generateChildSpanId(String parentSpanId) {if (parentSpanId == null) {return "1";}// 簡單的層級編號:1 -> 1.1, 1.1 -> 1.1.1return parentSpanId + "." + (System.currentTimeMillis() % 1000);}public boolean isValid() {return requestId != null && traceId != null;}// getter/setter方法省略...
}
ThreadLocalTraceHolder - 線程本地存儲
package com.simpleflow.log.context;import com.simpleflow.log.util.RequestIdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 線程本地追蹤上下文持有者* 使用InheritableThreadLocal實現跨線程的上下文傳遞*/
public class ThreadLocalTraceHolder {private static final Logger logger = LoggerFactory.getLogger(ThreadLocalTraceHolder.class);/*** 使用InheritableThreadLocal支持父子線程間的上下文傳遞*/private static final InheritableThreadLocal<TraceContext> TRACE_CONTEXT_HOLDER = new InheritableThreadLocal<TraceContext>() {@Overrideprotected TraceContext childValue(TraceContext parentValue) {// 子線程繼承父線程的上下文if (parentValue != null) {TraceContext childContext = parentValue.inherit();logger.debug("子線程繼承追蹤上下文: {}", childContext);return childContext;}return null;}};/*** 獲取當前線程的追蹤上下文*/public static TraceContext getCurrentTrace() {return TRACE_CONTEXT_HOLDER.get();}/*** 設置當前線程的追蹤上下文*/public static void setCurrentTrace(TraceContext context) {if (context != null) {TRACE_CONTEXT_HOLDER.set(context);logger.debug("設置追蹤上下文: {}", context);} else {TRACE_CONTEXT_HOLDER.remove();}}/*** 清除當前線程的追蹤上下文*/public static void clearCurrentTrace() {TRACE_CONTEXT_HOLDER.remove();logger.debug("清除當前線程追蹤上下文");}/*** 初始化新的追蹤上下文*/public static TraceContext initTrace() {TraceContext context = new TraceContext();context.setRequestId(RequestIdGenerator.generate());context.setTraceId(RequestIdGenerator.generateTraceId());context.setSpanId("1");setCurrentTrace(context);return context;}/*** 獲取當前請求ID*/public static String getCurrentRequestId() {TraceContext context = getCurrentTrace();return context != null ? context.getRequestId() : null;}/*** 獲取當前用戶ID*/public static String getCurrentUserId() {TraceContext context = getCurrentTrace();return context != null ? context.getUserId() : null;}/*** 設置當前用戶ID*/public static void setCurrentUserId(String userId) {TraceContext context = getCurrentTrace();if (context != null) {context.setUserId(userId);}}/*** 添加額外信息到當前上下文*/public static void addExtra(String key, String value) {TraceContext context = getCurrentTrace();if (context != null) {context.addExtra(key, value);}}/*** 創建子上下文(用于子操作)*/public static TraceContext createChildTrace() {TraceContext current = getCurrentTrace();if (current != null) {TraceContext child = current.createChild();setCurrentTrace(child);return child;}return null;}/*** 在指定上下文中執行操作*/public static <T> T executeWithTrace(TraceContext context, TraceCallback<T> callback) {TraceContext original = getCurrentTrace();try {setCurrentTrace(context);return callback.execute();} finally {setCurrentTrace(original);}}@FunctionalInterfacepublic interface TraceCallback<T> {T execute() throws Exception;}
}
RequestIdGenerator - ID生成器
package com.simpleflow.log.util;import java.net.InetAddress;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicLong;/*** 請求ID生成器 - 生成全局唯一的請求ID和追蹤ID*/
public class RequestIdGenerator {private static final String HOST_NAME;private static final AtomicLong SEQUENCE = new AtomicLong(1);private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");static {String hostName = "unknown";try {hostName = InetAddress.getLocalHost().getHostName();} catch (Exception e) {// 使用默認值}HOST_NAME = hostName;}/*** 生成請求ID* 格式:REQ_時間戳_主機名_序列號*/public static String generate() {String timestamp = LocalDateTime.now().format(TIME_FORMATTER);long sequence = SEQUENCE.getAndIncrement();return String.format("REQ_%s_%s_%d", timestamp, getShortHostName(), sequence);}/*** 生成追蹤ID* 格式:TRACE_時間戳_主機名_序列號*/public static String generateTraceId() {String timestamp = LocalDateTime.now().format(TIME_FORMATTER);long sequence = SEQUENCE.getAndIncrement();return String.format("TRACE_%s_%s_%d", timestamp, getShortHostName(), sequence);}private static String getShortHostName() {int dotIndex = HOST_NAME.indexOf('.');if (dotIndex > 0) {return HOST_NAME.substring(0, dotIndex);}return HOST_NAME;}
}
跨線程傳遞機制
InheritableThreadLocal原理演示
/*** 跨線程上下文傳遞演示*/
public class TraceInheritanceDemo {public static void main(String[] args) throws InterruptedException {// 在主線程中初始化追蹤上下文TraceContext mainContext = ThreadLocalTraceHolder.initTrace();mainContext.setUserId("user123");System.out.println("主線程: " + ThreadLocalTraceHolder.getCurrentRequestId());// 創建子線程 - 會自動繼承上下文Thread childThread = new Thread(() -> {System.out.println("子線程: " + ThreadLocalTraceHolder.getCurrentRequestId());System.out.println("用戶ID: " + ThreadLocalTraceHolder.getCurrentUserId());// 在子線程中添加額外信息ThreadLocalTraceHolder.addExtra("childInfo", "fromChild");});childThread.start();childThread.join();// 主線程的上下文不受子線程影響System.out.println("主線程最終: " + ThreadLocalTraceHolder.getCurrentRequestId());}
}
線程池場景的處理
/*** 線程池場景下的上下文傳遞*/
@Component
public class TraceAwareExecutor {private final ExecutorService executor = Executors.newFixedThreadPool(10);/*** 帶上下文傳遞的任務執行*/public <T> CompletableFuture<T> executeWithTrace(Callable<T> task) {// 捕獲當前上下文TraceContext currentContext = ThreadLocalTraceHolder.getCurrentTrace();return CompletableFuture.supplyAsync(() -> {TraceContext originalContext = ThreadLocalTraceHolder.getCurrentTrace();try {ThreadLocalTraceHolder.setCurrentTrace(currentContext);return task.call();} catch (Exception e) {throw new RuntimeException(e);} finally {ThreadLocalTraceHolder.setCurrentTrace(originalContext);}}, executor);}
}
實戰測試
@SpringBootTest
class TraceContextTest {@Testvoid testTraceInheritance() {// 初始化追蹤上下文TraceContext context = ThreadLocalTraceHolder.initTrace();context.setUserId("testUser");context.addExtra("testKey", "testValue");// 驗證上下文設置assertEquals("testUser", ThreadLocalTraceHolder.getCurrentUserId());// 在新線程中驗證上下文繼承CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 子線程應該繼承父線程的上下文assertNotNull(ThreadLocalTraceHolder.getCurrentTrace());assertEquals("testUser", ThreadLocalTraceHolder.getCurrentUserId());return ThreadLocalTraceHolder.getCurrentRequestId();});String childRequestId = future.join();assertEquals(context.getRequestId(), childRequestId);}@Testvoid testChildContextCreation() {// 創建父上下文TraceContext parent = ThreadLocalTraceHolder.initTrace();parent.setUserId("parentUser");String parentSpanId = parent.getSpanId();// 創建子上下文TraceContext child = ThreadLocalTraceHolder.createChildTrace();// 驗證繼承關系assertEquals(parent.getRequestId(), child.getRequestId());assertEquals(parent.getTraceId(), child.getTraceId());assertEquals(parent.getUserId(), child.getUserId());assertNotEquals(parentSpanId, child.getSpanId()); // SpanId應該不同}
}
本章小結
? 完成的任務
- 追蹤上下文:設計了完整的TraceContext類
- 線程本地存儲:實現了ThreadLocalTraceHolder
- ID生成器:創建了RequestIdGenerator工具
- 跨線程傳遞:解決了異步場景的上下文傳遞
- 實戰測試:驗證了追蹤系統的功能
🎯 學習要點
- InheritableThreadLocal的原理和應用
- 上下文設計的完整性和可擴展性
- ID生成策略的唯一性保證
- 跨線程傳遞的實現機制
💡 思考題
- 如何處理線程池復用導致的上下文污染?
- 分布式環境下如何實現跨服務的追蹤?
- 追蹤信息的持久化和查詢如何設計?
🚀 下章預告
下一章我們將實現Web集成模塊,學習如何在HTTP請求層面集成鏈路追蹤,包括Filter、攔截器的實現,以及如何從HTTP頭中提取和傳遞追蹤信息。
💡 設計原則: 鏈路追蹤系統的精髓在于輕量級、無侵入、高性能。通過ThreadLocal機制,我們實現了跨線程的上下文傳遞,為分布式日志奠定了堅實基礎。