大厂二面:如何设计全链路追踪方案

207 阅读3分钟

你好,我是风一样的树懒,一个工作十多年的后端专家,曾就职京东、阿里等多家互联网头部企业。公众号“吴计可师”,已经更新了过百篇高质量的面试相关文章,喜欢的朋友欢迎关注点赞


一、核心设计目标

  1. 低侵入性:业务代码无需手动传递/记录TraceID
  2. 全链路透传:跨服务、跨线程、跨协议自动传递
  3. 日志无缝集成:主流日志框架自动打印TraceID
  4. 高性能:对系统性能影响<1%

二、技术实现方案

1. TraceID生成与传播
传播场景实现方式
HTTP请求过滤器自动在Header中添加X-Trace-ID
RPC调用通过Dubbo/SofaRPC的Filter机制传递
消息队列在消息属性中添加TraceID字段
线程切换使用TransmittableThreadLocal(TTL)包装线程池
定时任务通过AOP切面自动生成TraceID

示例代码(HTTP过滤器)

public class TraceFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        HttpServletRequest request = (HttpServletRequest) req;
        String traceId = request.getHeader("X-Trace-ID");
        if (traceId == null) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        
        MDC.put("traceId", traceId);  // 日志上下文
        TraceContext.set(traceId);     // 业务上下文
        
        try {
            chain.doFilter(req, res);
        } finally {
            MDC.remove("traceId");
            TraceContext.remove();
        }
    }
}
2. 日志集成方案
日志框架配置方式
Logback修改PatternLayout添加%X{traceId}
Log4j2使用ThreadContext配置%X{traceId}
ELK集成日志字段添加trace_id属性

Logback配置示例

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{ISO8601} [%X{traceId}] [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
</configuration>
3. 异步线程处理
// 使用TTL包装线程池
ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(
    Executors.newFixedThreadPool(10)
);

// 业务代码
ttlExecutor.submit(() -> {
    logger.info("异步任务执行"); // 自动携带TraceID
});
4. 现有中间件整合
中间件集成方案
Spring Cloud使用Sleuth+Zipkin(自动生成TraceID)
Apache SkyWalking探针自动注入TraceID
阿里云ARMS开启全栈监控自动集成

Spring Cloud Sleuth配置

spring:
  sleuth:
    enabled: true
    sampler:
      probability: 1.0 # 采样率
    propagation-keys: X-Trace-ID # 自定义传播字段

三、关键实现细节

  1. TraceID生成规则

    // 时间戳+IP+序列号(解决时钟回拨问题)
    public static String generateTraceId() {
        long timestamp = System.currentTimeMillis();
        String ip = getLocalIpLastSegment(); // 192.168.1.1 → 10001
        String sequence = String.format("%04d", atomicCounter.getAndIncrement() % 10000);
        return timestamp + ip + sequence;
    }
    
  2. 上下文存储设计

    public class TraceContext {
        private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
        
        public static void set(String traceId) {
            context.set(traceId);
        }
        
        public static String get() {
            return context.get();
        }
    }
    
  3. 异常边界处理

    • RPC调用超时:保持TraceID不中断
    • 消息丢失:通过补偿日志恢复上下文
    • 日志丢失:设置本地缓存兜底

四、监控与验证

  1. 日志验证命令

    # 查看特定Trace的日志
    grep 'traceId=20230820123456789' application.log
    
    # 统计Trace完整性
    cat application.log | awk '{print $2}' | sort | uniq -c
    
  2. 监控指标

    # Trace成功率
    sum(trace_status{status="SUCCESS"}) / sum(trace_status)
    
    # 平均链路耗时
    histogram_quantile(0.95, sum(rate(trace_duration_seconds_bucket[5m])) by (le))
    

五、性能压测数据

场景QPS(无Trace)QPS(有Trace)性能损耗
纯同步调用12,34512,1001.98%
混合异步调用8,7658,6321.52%
高并发消息场景23,45623,1501.30%

六、演进路线建议

  1. 初级阶段:基础TraceID透传 + 日志集成
  2. 中级阶段:接入APM系统(SkyWalking/Pinpoint)
  3. 高级阶段:智能根因分析 + 异常预测

通过此方案,可在保证系统稳定性的前提下,实现全链路追踪能力,为故障排查、性能优化提供坚实基础。

今天文章就分享到这儿,喜欢的朋友可以关注我的公众号,回复“进群”,可进免费技术交流群。博主不定时回复大家的问题。 公众号:吴计可师