当前位置: 首页 > backend >正文

手写链路追踪

1. 什么是链路追踪

链路追踪是指在分布式系统中,将一次请求的处理过程进行记录并聚合展示的一种方法。目的是将一次分布式请求的调用情况集中在一处展示,如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等。这样就可以轻松了解一个请求在系统中的完整生命周期,包括经过的服务、调用的操作以及每个操作的延迟等。通过链路追踪,可以更好地理解系统的性能瓶颈、找出问题的根源以及优化系统的性能。
如下图就是一个简单的微服务中的调用过程,如果我们没有链路追踪,且每个服务都是一个多节点集群,想要搞清楚一个请求是怎么走的就非常困难。
链路追踪

2. 链路追踪的重要性

在分布式系统中,由于服务节点众多且相互之间存在复杂的依赖关系,所以一旦出现故障,排查起来往往非常困难。而链路追踪可以有效地帮助解决这个问题。具体是以下几个方面:

快速定位问题:当应用程序出现故障时,开发人员可以通过链路追踪来快速定位到故障的原因。通过查看元数据,可以确定故障发生的位置以及导致故障的请求数据,加速故障的排查过程。

优化程序性能:链路追踪可以帮助开发人员分析应用程序的性能瓶颈。通过观察数据在各个节点之间的流动情况,可以确定哪些节点的性能较差,并针对这些节点进行优化。

分析安全问题:通过观察数据在系统中的流动情况,可以发现潜在的安全漏洞和攻击路径,例如DDoS攻击、中间人攻击、SQL注入攻击等。有助于提高系统的安全性,并减少潜在的安全风险。

3. 链路追踪的实现

链路追踪的实现方式很多,你可以通过不同工具去实现。
如果你的微服务用的是Spring Cloud, 其实spring cloud已经有很完美的解决方案。
Spring Cloud 链路追踪通常使用 Spring Cloud Sleuth 来实现。Spring Cloud Sleuth 集成了 Zipkin 和 Brave 来提供链路追踪功能。
但是也有很多微服务没有用Spring Cloud,如果我们只需要简单的链路追踪,也可以自己手写一份实现。
手写也不复杂,但是需要实现的人考虑周全,把链路追踪写好一点。本篇及后续篇章主要介绍手写的不同实现方式。
链路追踪的核心是日志追踪,下面我们尝试用代码来实现日志追踪。

3.1 实现一个API接口

模拟一个登录接口API。 API包含如下实现

  • API接口参数包括request body;
  • 能接收可能包含trace id的header;
  • 读取当前线程名(用于线程结束前还原线程名);
  • 如果请求头中没有包含trace id, 自动生成一个;
  • 在请求开始的时候替换当前线程名,直到维持到请求结束前,用于修改日志文件的线程名,用它来做日志信息追踪;
  • 用一个for loop模拟登录过程的日志
package com.sandwich.logtracing.controller;import com.sandwich.logtracing.entity.ApiResponse;
import com.sandwich.logtracing.util.RandomStrUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;/*** @Author 公众号: IT三明治* @Date 2025/8/29* @Description: login demo controller*/
@Slf4j
@RestController
@RequestMapping("/test")
public class LoginController {@PostMapping("/login")public ApiResponse<String> login(@RequestBody LoginRequest loginRequest,@RequestHeader(value = "x-request-correlation-id", required = false) String traceId) {String currentThreadName = Thread.currentThread().getName();//if the request header don't have a trace id,then generate a random oneif (StringUtils.isBlank(traceId)) {traceId = RandomStrUtils.generateRandomString(15);}//replace current thread name with a random stringThread.currentThread().setName(traceId);log.info("previous thread name:{}", currentThreadName);for (int i=1; i<= 10; i++) {log.info("processing login for user {}, login step {} done", loginRequest.getUsername(), i);}log.info("user {} login success", loginRequest.getUsername());//restore thread name before api request endThread.currentThread().setName(currentThreadName);return ApiResponse.success("Sandwich login success", traceId);}@Datapublic static class LoginRequest {private String username;private String password;}
}

3.2 定义一个response entity

这个entity跟其他response不同之处在于它除了能支持一个泛型的data,还可以支持trace id返回

package com.sandwich.logtracing.entity;import lombok.Data;
import lombok.experimental.Accessors;/*** @Author 公众号: IT三明治* @Date 2025/8/29* @Description: api response entity*/
@Data
@Accessors(chain = true)
public class ApiResponse<T> {private int responseCode;private String message;private T data;private String traceId;public static  <T> ApiResponse<T> success(T data, String traceId) {return new ApiResponse<T>().setResponseCode(ResponseCode.SUCCESS.getCode()).setMessage(ResponseCode.SUCCESS.getMessage()).setData(data).setTraceId(traceId);}public static ApiResponse<String> success() {return new ApiResponse<String>().setResponseCode(ResponseCode.SUCCESS.getCode()).setMessage(ResponseCode.SUCCESS.getMessage());}public static  <T> ApiResponse<T> success(T data) {return new ApiResponse<T>().setResponseCode(ResponseCode.SUCCESS.getCode()).setMessage(ResponseCode.SUCCESS.getMessage()).setData(data);}}

准备一个枚举保存response code和对应的message, 这个demo我只用一个success的

package com.sandwich.logtracing.entity;import lombok.Data;
import lombok.Getter;/*** @Author 公众号: IT三明治* @Date 2025/8/29* @Description:*/
@Getter
public enum ResponseCode {SUCCESS(200, "success"),FAIL(500, "internal error"),NOT_FOUND(404, "not found"),UNAUTHORIZED(401, "unauthorized"),FORBIDDEN(403, "forbidden"),NOT_ACCEPTABLE(406, "not acceptable"),REQUEST_TIMEOUT(408, "request timeout"),CONFLICT(409, "conflict"),UNSUPPORTED_MEDIA_TYPE(415, "unsupported media type"),TOO_MANY_REQUESTS(429, "too many requests");private final int code;private final String message;ResponseCode(int code, String message) {this.code = code;this.message = message;}
}

3.3 用shell写一个api请求(login.shell)

为了更好地展示api的所有信息,我选择用shell完成api请求,shell需要完成的功能如下:

  • 自动生成trace id
  • 自动组装api request,包括请求数据类型,header, payload
  • 用python格式化返回的json结构体
#!/bin/bash# Define the API endpoint
API_URL="http://localhost:8080/test/login"function generate_random_string() {# 使用openssl生成随机字符串(如果已安装)if command -v openssl &> /dev/null; thenopenssl rand -base64 20 | tr -dc 'a-zA-Z0-9' | fold -w 15 | head -n 1else# 使用系统方法生成local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"local result=""result=$(printf "%s" "${chars:$((RANDOM % ${#chars})):1}"{1..15} | tr -d '\n')echo "$result"fi
}function normalLogin() {# 生成15位随机字符串作为traceIdtraceId=$(generate_random_string)response=$(curl -X POST $API_URL \-H "Content-Type: application/json" \-H "x-request-correlation-id: $traceId" \-d '{"username": "Sandwich", "password": "test"}')echo "Response from login API:"echo "$response" | python -m json.tool
}normalLogin

4. 测试验证

  • 启动项目
  • 执行请求
Administrator@USER-20230930SH MINGW64 /d/git/java/log-tracing/shell (master)
$ ./login.sh% Total    % Received % Xferd  Average Speed   Time    Time     Time  CurrentDload  Upload   Total   Spent    Left  Speed
100   144    0   100  100    44    493    217 --:--:-- --:--:-- --:--:--   712
Response from login API:
{"responseCode": 200,"message": "success","data": "Sandwich login success","traceId": "wwDJbM12XdX562A"
}
  • 用trace id追踪日志信息

5. 总结

这就是一个最小的链路追踪过程,非常简单,我连日志管理文件都没有配置,只用了springboot 默认的日志系统。

无疑它是非常不完善的,请关注我,下期我再逐步优化它。

http://www.xdnf.cn/news/18996.html

相关文章:

  • 新手向:从零开始理解百度语音识别API的Python实现
  • 跨境物流数字化转型怎么做?集运/转运系统定制,源码交付,助力企业降本增效,抢占市场先机
  • 【前端教程】JavaScript 对象与数组操作实战:从基础到优化
  • linux安装海康工业相机MVS SDK(3.0)会导致ROS的jsk插件崩溃
  • Java IO 流-详解
  • 从零开始学习单片机16
  • 循环高级(2)
  • 血缘元数据采集开放标准:OpenLineage Integrations Manually Annotated Lineage
  • 企业级数据库管理实战(二):数据库权限最小化原则的落地方法
  • 【分治法 BFS 质因数分解】P12255 [蓝桥杯 2024 国 Java B] 园丁|普及+
  • 智慧养老建设方案(PPT)
  • 开源大语言模型(Qwen3)
  • 深入探讨可视化技术如何实现安全监测
  • 【小白笔记】Visual Studio 在 2025年7月更新的功能说明(英文单词记忆)
  • 智慧工地系统:基于Java微服务与信创国产化的建筑施工数字化管理平台
  • 171-178CSS3新增
  • NullPointerException 空指针异常,为什么老是遇到?
  • 评价指标FID/R Precision
  • vscode编辑器中设置断点,不会自动启动调试器
  • 介绍⼀下Llama的结构
  • Spring Boot 整合 MongoDB:CRUD 与聚合查询实战
  • Jenkins 全方位指南:安装、配置、部署与实战应用(含图解)
  • 如何规划一年、三年、五年的IP发展路线图?
  • 01.<<基础入门:了解网络的基本概念>>
  • Leetcode 深度优先搜索 (15)
  • WINTRUST!_ExplodeMessag函数中的pCatAdd
  • Yolov8 pose 推理部署笔记
  • Vue开发避坑:箭头函数与普通函数的正确使用指南
  • LeetCode 刷题【55. 跳跃游戏】
  • 从协作机器人到智能协作机器人:工业革命的下一跳