项目地址: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…