第6篇:鏈路追蹤系統 - 分布式環境下的請求跟蹤

項目地址:https://github.com/nemoob/atlas-log 開箱即用。

前言

在微服務架構中,一個用戶請求往往會經過多個服務的協作處理。本章將實現一個輕量級的鏈路追蹤系統,讓日志具備分布式追蹤能力。

分布式鏈路追蹤基礎概念

鏈路追蹤的核心價值

追蹤信息
TraceID: abc123
RequestID: req456
UserID: user789
用戶請求
API網關
用戶服務
訂單服務
數據庫
支付服務

追蹤的核心元素:

  • 🎯 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應該不同}
}

本章小結

? 完成的任務

  1. 追蹤上下文:設計了完整的TraceContext類
  2. 線程本地存儲:實現了ThreadLocalTraceHolder
  3. ID生成器:創建了RequestIdGenerator工具
  4. 跨線程傳遞:解決了異步場景的上下文傳遞
  5. 實戰測試:驗證了追蹤系統的功能

🎯 學習要點

  • InheritableThreadLocal的原理和應用
  • 上下文設計的完整性和可擴展性
  • ID生成策略的唯一性保證
  • 跨線程傳遞的實現機制

💡 思考題

  1. 如何處理線程池復用導致的上下文污染?
  2. 分布式環境下如何實現跨服務的追蹤?
  3. 追蹤信息的持久化和查詢如何設計?

🚀 下章預告

下一章我們將實現Web集成模塊,學習如何在HTTP請求層面集成鏈路追蹤,包括Filter、攔截器的實現,以及如何從HTTP頭中提取和傳遞追蹤信息。


💡 設計原則: 鏈路追蹤系統的精髓在于輕量級、無侵入、高性能。通過ThreadLocal機制,我們實現了跨線程的上下文傳遞,為分布式日志奠定了堅實基礎。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/94837.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/94837.shtml
英文地址,請注明出處:http://en.pswp.cn/web/94837.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ubuntu25.04編譯最新版本qgroundcontrol

編譯系統版本: 編譯器版本: 編譯成功效果

如何在 Docker 和AKS上使用 IIS

前言 在我們的一個客戶項目中,我們有一個混合 Swarm 集群,其中包含 Linux 和 Windows 節點。在 Windows 節點上,我們運行了許多 IIS 容器,這些容器運行著多個 Web 應用程序。在這篇博文中,我想向您展示在 Docker 容器中將網站部署到 IIS 上是多么簡單。 Internet 信息服…

uniapp 頁面favicon.ico文件不存在提示404問題解決

1. uniapp 頁面favicon.ico文件不存在提示404問題解決 1.1. 場景 在uniapp中經常出現的&#xff0c;因為找不到 favicon.ico 而報404錯誤的問題。 GET http://localhost:5174/favicon.ico 404 (Not Found)1.2. 問題原因 在document.ejs中使用link標簽相對路徑引入文件。 <…

Magicodes.IE.Pdf 生成導出PDF文件 bytes Stream FileStreamResult 下載

1、ExporterAttribute&#xff1a;導出特性 Name&#xff1a;名稱 HeaderFontSize&#xff1a;頭部字體大小 FontSize&#xff1a;正文字體大小 MaxRowNumberOnASheet&#xff1a;一個Sheet最大允許的行數&#xff0c;設置了之后將輸出多個Sheet AutoFitAllColumn&#xff1a;自…

Python LangChain RAG從入門到項目實戰10.:質量評價指標體系

好的&#xff0c;RAG (Retrieval-Augmented Generation) 系統的評估是一個多維度的問題&#xff0c;需要同時對檢索器 (Retriever) 和生成器 (Generator) 的性能進行衡量。 評估指標主要分為三大類&#xff1a;檢索質量、生成質量 和 整體系統質量。下圖清晰地展示了這些核心指…

【記錄】Copilot|Github Copilot重新學生認證通過方法(2025年7月,包括2FA和認證材料、Why are you not on campus)

文章目錄前言步驟最重要的一步前言 事實上&#xff0c;Github Copilot馬上就要開源了&#xff0c;我原本的認證過期了。但是在我體驗了眾多的代碼補全工具實在是太難用了之后&#xff0c;我覺得一天也等不了了&#xff0c;就去再一次認證了學生認證。 這次嚴格了很多&#xff…

【C語言16天強化訓練】從基礎入門到進階:Day 13

&#x1f525;個人主頁&#xff1a;艾莉絲努力練劍 ?專欄傳送門&#xff1a;《C語言》、《數據結構與算法》、C語言刷題12天IO強訓、LeetCode代碼強化刷題、洛谷刷題、C/C基礎知識知識強化補充、C/C干貨分享&學習過程記錄 &#x1f349;學習方向&#xff1a;C/C方向學習者…

單元測試到底是什么?該怎么做?

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快一、什么是單元測試&#xff1f;單元測試&#xff08;unit testing&#xff09;&#xff0c;是指對軟件中的最小可測試單元進行檢查和驗證。至于“單元”的大小或范…

PostgreSQL【應用 04】加解密擴展 pgcrypto 使用實例(加密、導出、導入、解密流程說明)

加解密擴展 pgcrypto 使用實例1.需求說明2.工具說明2.1 環境說明2.2 插件添加3.實例分析3.1 測試數據3.2 進行加密3.3 數據導出3.3.1 Navicat 導出3.3.2 copy 命令導出3.4 數據解密3.4.1 Navicat 導入3.4.2 copy 導入3.5 坑1.需求說明 從內網導出敏感數據的時候&#xff0c;對…

SDK、JDK、JRE、JVM的區別

SDK、JDK、JRE、JVM的區別一、SDK二、JDK三、JRE四、JVM五、JDK、JRE、JVM三者關系圖一、SDK SDK&#xff08;Software Development Kit&#xff0c;程序軟件開發工具包&#xff09;&#xff0c;可以認為jdk只是sdk的一種&#xff08;子集&#xff09;&#xff0c;而當提及jav…

如何啟動一個分支網絡改造試點?三步走

在多云化、全球化的今天&#xff0c;企業的分支網絡早已不僅僅是“能連”的問題。視頻會議卡頓、ERP 響應延遲、跨境訪問不穩、合規風險增大……這些都讓 CIO 和 IT 負責人越來越清楚&#xff1a;分支網絡改造是數字化的必修課。但是&#xff0c;面對幾百甚至上千個分支機構&am…

四,設計模式-原型模式

目的原型模式的產生是為了解決一個問題&#xff0c;即復制對象時對被復制對象所屬類的依賴。當需要復制一個對象時&#xff0c;需要遍歷對象中的所有成員并進行復制&#xff0c;但存在一些問題&#xff1a;某些成員對象可能是私有的無法訪問。同時要復制某個對象&#xff0c;那…

(筆記)Android窗口管理系統分析

概述 Android窗口管理系統是Android UI框架的核心組件&#xff0c;負責管理所有應用窗口的顯示、布局、層級、焦點和輸入事件分發。WindowManagerService&#xff08;WMS&#xff09;作為系統服務&#xff0c;協調Surface、Activity、View等組件&#xff0c;為用戶提供流暢的界…

WebIDEPLOY 技術支撐草莓數字產業鏈的構建邏輯與實踐路徑—— 草莓智能育苗系統實踐應用分析

一、WebIDEPLOY 技術與草莓產業數字化的適配邏輯WebIDEPLOY 技術以 “低門檻接入、全鏈路協同、數據驅動” 為核心特征&#xff0c;其底層架構可精準對接草莓產業鏈的碎片化需求。通過零代碼設備接入模塊&#xff0c;能快速整合育苗棚傳感器、種植區智能設備、銷售端數據平臺等…

汽車電氣系統的發展演進為測試帶來了哪些影響?

隨著汽車智能化進程加速&#xff0c;車輛電氣系統方案持續演進。為滿足日益嚴格的功能安全要求&#xff0c;主機廠逐漸引入智能配電、冗余配電等新型方案&#xff0c;這給電氣系統的測試環節帶來了顯著影響。智能配電測試何為智能配電&#xff1f;下圖分別展示了傳統電氣架構以…

Rocky9配置完VMware橋接模式后沒有自動獲取IP

現象如下&#xff1a;查看網卡狀態&#xff1a; nmcli dev status可以看到ens160存在&#xff0c;但是disconnected查看已有連接配置&#xff1a; nmcli con show可以看到連接配置也在重啟NetworkManager systemctl restart NetworkManager激活網卡 sudo nmcli con up "en…

Unity List 相關

順序復制同類型的List①list2 new List<T>(list1);②list2.Clear(); list1.ForEach(item > list2.Add(item));倒序復制同類型的Listlist2 new List<T>(list1);//順序復制 list2.Reverse();//顛倒list亂序復制同類型的ListList<T> list2 new List<T&…

網絡安全測試(一)Kali Linux

Kali Linux 是一款專為網絡安全測試、滲透測試和白帽黑客設計的 Linux 發行版&#xff0c;預裝了大量安全測試工具。以下是其核心工具的分類及代表性工具介紹&#xff1a; 一、信息收集工具 用于獲取目標網絡、主機或系統的基礎信息。 Nmap&#xff1a;網絡掃描工具&#xff0…

go grpc使用場景和使用示例

Go gRPC 使用場景 微服務架構中的服務間通信&#xff1a;在微服務架構中&#xff0c;不同的服務之間需要高效、可靠地進行通信和數據交換&#xff0c;gRPC 可以很好地滿足這一需求。需要高并發、低延遲通信的場景&#xff1a;gRPC 基于 HTTP/2 協議&#xff0c;支持多路復用和頭…

6.8 學習ui組件方法和Element Plus介紹

學習 UI 組件庫的核心是 “文檔 實踐 深入”。從官方文檔入門&#xff0c;通過構建真實項目來鞏固和深化理解&#xff0c;適時探索源碼以提升認知。同時&#xff0c;掌握按需引入、主題定制、插槽等關鍵技術&#xff0c;并保持對性能、可訪問性和最佳實踐的關注。記住&#x…