当SkyWalking遇上自研Trace:链路断开的核心原因与终极兼容方案

线上突发:调用链路中途"消失"了

背景是这样的: 随着技术架构的升级,我们正在将早期的 SkyWalking 替换为自研 Trace 组件。但现实总是骨感的,旧项目暂时没空下线,新项目已经上线,这就形成了"新老混部"的局面。

暂时不讨论早期技术架构设计的合理性,先讨论如何解决遗留问题

问题复现: 假设有一个调用链路:A服务 ➔ B服务 ➔ C服务 ➔ D服务

A、B 服务: 也就是"老前辈",依然使用 SkyWalking 进行 Trace 传递。

C、D 服务: 是"新秀",接入了自研 Trace。

当 B 调用 C 时,事故发生了: C 服务接收到了请求,但是它读不懂 B 服务传过来的"暗号",于是 C 服务自作主张重新生成了一个 TraceId。

结果: 整个链路图在 B 到 C 之间断开了。运维同学在查日志时,发现 A-B 是一个链路,C-D 是另一个链路,中间完全由于"失忆"导致无法串联。

根因拆解:不同Trace组件的"语言不通"

Trace 能够串联的核心在于透传。

SkyWalking 的方言: 使用 sw8 这个 Header 来传递 Trace 信息。

自研 Trace 的方言: 使用自定义的 Header(例如 X-TRACE-ID)来传递。

当 B 服务(SkyWalking)请求 C 服务(自研)时,HTTP 请求头里带的是 sw8。 C 服务的 Trace 过滤器按照自研规则去拿 X-TRACE-ID,结果当然是拿不到。

简单说:请求头变了,接收方不认识

破局方案:兼容适配 + 反射黑科技

既然 C 服务无法识别 sw8,那我们就教它识别。但是,我们不希望强依赖 SkyWalking 的全套 Jar 包,以免造成代码冗余或版本冲突。

解决方案思路: 在 C 服务获取 Trace 时,做一个降级策略(Fallback)------如果自研 Header 里没有,就尝试去问问 SkyWalking 的上下文。

为了保持优雅,我们采用**反射(Reflection)**的方式来调用 SkyWalking 的 API。

🛠️ 第一步:打造适配器工具类

我们编写一个 SkyWalkingAccessor,利用反射动态判断当前环境是否有 SkyWalking,并提取 TraceId。

java 复制代码
@Slf4j
public class SkyWalkingAccessor {

    private static boolean skyWalkingPresent;
    private static Method traceIdMethod;
    private static Method spanIdMethod;

    // 静态代码块:初始化反射方法,避免运行时重复消耗性能
    static {
        try {
            // 尝试加载 SkyWalking 的核心类
            Class<?> clazz = Class.forName("org.apache.skywalking.apm.toolkit.trace.TraceContext");
            traceIdMethod = clazz.getMethod("traceId");
            spanIdMethod = clazz.getMethod("spanId");
            skyWalkingPresent = true;
            log.info("SkyWalking Toolkit found. 兼容模式已开启。");
        } catch (Throwable e) {
            log.debug("SkyWalking Toolkit not found. 仅使用内部 Trace。");
            skyWalkingPresent = false;
        }
    }

    /**
     * 优雅获取 SkyWalking TraceId
     */
    public static String getTraceId() {
        if (!skyWalkingPresent) {
            return null;
        }
        try {
            String tid = (String) traceIdMethod.invoke(null);
            if (isValid(tid)) {
                return tid;
            }
        } catch (Exception e) {
            // 忽略异常,降级处理,不影响主流程
        }
        return null;
    }

    private static boolean isValid(String tid) {
        return StrUtil.isNotBlank(tid) 
               && !"Ignored_Trace".equals(tid) 
               && !"N/A".equals(tid);
    }
}

🛠️ 第二步:改造获取 Trace 的入口

在原本的 HttpTracer 中,加入兼容逻辑:

java 复制代码
// 1. 优先尝试:从自研的标准请求头获取
String traceId = request.getHeader(ContextContainer.X_TRACE_ID);

// 2. 降级兼容:如果为空,尝试从 SkyWalking 上下文"窃取"
if (StrUtil.isBlank(traceId)) {
    traceId = SkyWalkingAccessor.getTraceId();
}

// 3. 如果还是没有,说明是链路起点,生成新的 TraceId
if (StrUtil.isBlank(traceId)) {
    traceId = IdGenerator.generate();
}

业务侧如何使用

使用方式非常简单,无需大量改造:

  1. 引入SkyWalking的依赖(如果项目已接入SkyWalking则无需重复引入):
xml 复制代码
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>你的SkyWalking版本</version>
 </dependency>
  1. 升级基础组件jar版本

进阶优化:封装独立模块,降低业务接入成本

上面的方案虽然能解决问题,但如果每个业务都需要手动引入apm-toolkit-trace依赖,还是会增加接入成本,而且容易出现版本不一致的问题。

我们的优化思路是:封装一个独立的适配模块,比如叫xxx-skywalking-adapt

具体做法:

  • apm-toolkit-trace依赖都集成到这个独立模块中;

  • 业务侧无需手动引入apm-toolkit-trace,直接将原来的基础组件依赖替换成这个适配模块即可。

这里需要注意一个责任边界问题:

  • 如果业务自己单独引入了 apm-toolkit-trace 依赖,后续的版本维护、问题排查由业务侧自己负责;

  • 如果通过基础组件的适配模块引入依赖,則由基础组件团队负责版本管理和问题兜底

可能会存在一个责任划分的问题

总结

新旧Trace组件共存导致链路断开的问题,核心原因是"透传协议不兼容"(请求头不一致)。

解决这类问题的核心思路是"兼容适配"------让新组件能识别老组件的协议,实现上下文的平滑传递

相关推荐
HappRobot9 天前
OpenTelemetry和Jaeger、 SkyWalking的关系
linux·网络·skywalking
HappRobot9 天前
OpenTelemetry(OTel)和 SkyWalking 组合实现可视化监控
skywalking
shepherd12615 天前
从入门到实践:玩转分布式链路追踪利器SkyWalking
java·分布式·后端·skywalking
阿拉斯攀登23 天前
SkyWalking使用:Spring Boot场景
spring boot·后端·skywalking
阿拉斯攀登23 天前
SkyWalking使用:Spring Cloud Alibaba场景
skywalking
乐之者v23 天前
使用 SkyWalking,没有 traceId, 如何分析?
skywalking
阿拉斯攀登23 天前
SkyWalking 与 Zipkin、Prometheus 深度对比分析
prometheus·skywalking·可观测性·zipkin
小毅&Nora25 天前
【后端】【诡秘架构】 ① 序列9:占卜家——分布式链路追踪入门:用 SkyWalking 预知系统命运
分布式·架构·skywalking
boy快快长大1 个月前
【Spring Cloud Alibaba】SkyWalking 链路追踪
skywalking