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
相关产品推荐
相关产品推荐

