You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenTracing在gRPC服务间注入/提取Span Context失败问题排查

问题核心分析与解决方案

1. 协议头命名不匹配(最直接原因)

你在客户端传递的是trace-id/span-id,服务端代码里又换成了x-b3-trace-id/x-b3-spanid,但B3追踪协议的标准头是全小写的x-b3-traceid和x-b3-spanid(注意spanid没有横杠)。头名不匹配直接导致extract操作无法识别,返回null。

2. 完全搞反了inject和extract的作用

你在服务端的injectParentContext方法里调用了tracer.inject——这是错误的:

  • inject是把当前Span的Context注入到载体(比如HTTP/gRPC头),用于客户端向服务端传递;
  • extract才是从传入的载体中提取父Span的Context,用于服务端创建子Span。
    你想用header里的信息构建父Context,却用了inject,当然不会有任何效果。

3. 误解了SpanContext的不可变性

SpanContext是不可变对象,调用任何方法都不会改变它的状态,所以你代码里的pre和after必然完全一致。要基于父Context创建新的子Span,必须重新构建Span,而不是修改已有Span的Context。


正确实现步骤

第一步:客户端拦截器(正确注入SpanContext)

不要手动写trace-id/span-id,直接用tracer从当前活跃Span中提取Context,注入到gRPC Metadata中,同时使用标准B3头:

public class TracingClientInterceptor implements ClientInterceptor {
    private final Tracer tracer;

    public TracingClientInterceptor(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                // 获取当前客户端的SpanContext
                SpanContext currentSpanCtx = tracer.activeSpan().context();
                // 注入到gRPC Metadata,自动使用B3标准头
                tracer.inject(
                    currentSpanCtx,
                    Format.Builtin.TEXT_MAP,
                    key -> value -> headers.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value)
                );
                super.start(responseListener, headers);
            }
        };
    }
}

第二步:服务端拦截器(正确提取并创建子Span)

从gRPC Metadata中提取父SpanContext,然后创建子Span并绑定到请求Context:

public class TracingServerInterceptor implements ServerInterceptor {
    private final Tracer tracer;

    public TracingServerInterceptor(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        // 从Metadata提取父SpanContext
        SpanContext parentSpanCtx = tracer.extract(
            Format.Builtin.TEXT_MAP,
            (carrier, key) -> carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)),
            headers
        );

        // 创建服务端子Span,关联父Context
        Span serverSpan = tracer.buildSpan(call.getMethodDescriptor().getFullMethodName())
            .asChildOf(parentSpanCtx)
            .start();

        // 将Span绑定到请求Context,后续业务代码可直接获取
        Context ctx = Context.current().withValue(Tracer.CONTEXT_KEY, serverSpan);
        return Contexts.interceptCall(ctx, call, headers, next);
    }
}

第三步:避免手动处理的最佳实践

手动写拦截器容易出错,直接用官方提供的工具:

  • OpenTelemetry:使用opentelemetry-grpc依赖,自动生成拦截器,无需手动处理头;
  • OpenTracing:使用opentracing-grpc依赖,提供现成的TracingClientInterceptor和TracingServerInterceptor。

内容的提问来源于stack exchange,提问作者Deus Asakura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:56:12