在使用链路追踪控制台追踪应用的链路数据之前,需要通过客户端将应用数据上报至链路追踪。本文介绍如何通过 Jaeger 客户端上报 Java 应用数据。

前提条件

  1. 登录链路追踪控制台,在概览页面的 Region对应信息区域打开查看Token 开关。
  2. 客户端采集工具区域单击需要使用的链路数据采集客户端。
  3. 在下方表格中相应地域的相关信息列中,单击接入点信息末尾的复制按钮。

Tracing Analysis Endpoint Section

提示:如果应用部署于阿里云生产环境,则选择内网接入点,否则选择公网接入点。对于 Zipkin,一般情况下请使用 v2 版接入点,v1 版接入点仅限对 Zipkin 十分了解的高阶用户使用。

背景信息

Jaeger 是一款开源分布式追踪系统,兼容 OpenTracing API,且已 加入 CNCF 开源组织。其主要功能是聚合来自各个异构系统的实时监控数据。目前 OpenTracing 社区已有许多组件可支持各种 Java 框架,例如:

要通过 Jaeger 将 Java 应用数据上报至链路追踪控制台,首先需要完成埋点工作。您可以手动埋点,也可以利用各种现有插件实现埋点的目的。本文介绍以下三种埋点方法。

  • 手动埋点
  • 通过 Spring Cloud 组件埋点
  • 通过 gRPC 组件埋点

不通过 Agent 而直接上报数据的原理如下图所示。

Report Tracing Data Directly

通过 Agent 上报数据的原理如下图所示。

Report Tracing Data By Agent

为 Java 应用手动埋点

要通过 Jaeger 将 Java 应用数据上报至链路追踪控制台,首先需要完成埋点工作。本示例为手动埋点。

说明下载 Demo 工程,进入 manualDemo 目录,并按照 Readme 的说明运行程序。
  1. 打开 pom.xml,添加对 Jaeger 客户端的依赖。
    <dependency>
        <groupId>io.jaegertracing</groupId>
        <artifactId>jaeger-client</artifactId>
        <version>0.31.0</version>
    </dependency>
  2. 配置初始化参数并创建 Tracer。

    Tracer 对象可以用来创建 Span 对象(记录分布式操作时间)、跨机器透传数据(Extract/Inject 方法),或设置当前 Span(activeSpan)。Tracer 对象还配置了上报数据的网关地址、本机 IP、采样率、服务名等数据。您可以通过调整采样率来减少因上报数据产生的开销。

    注意 请将 <endpoint> 替换成链路追踪控制台概览页面上相应客户端和相应地域的接入点。关于获取接入点信息的方法,请参见前提条件中的获取接入点信息
    // 将 manualDemo 替换为您的应用名称
    io.jaegertracing.Configuration config = new io.jaegertracing.Configuration("manualDemo");
    io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();
    // 将 <endpoint> 替换为控制台概览页面上相应客户端和相应地域的接入点
    sender.withEndpoint("<endpoint>");
    config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(1));
    config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000));
    GlobalTracer.register(config.getTracer());
  3. 记录请求数据。
    Tracer tracer = GlobalTracer.get();
    // 创建 Span
    Span span = tracer.buildSpan("parentSpan").withTag("myTag", "spanFirst").start();
    tracer.scopeManager().activate(span, false);
    tracer.activeSpan().setTag("methodName", "testTracing");
    // ...业务逻辑
    secondBiz();
    span.finish();
  4. 可选: 上一步用于记录请求的根操作,如果需要记录请求的上一步和下一步操作,则需要传入上下文。
    Tracer tracer = GlobalTracer.get();
    Span parentspan = tracer.activeSpan();
    Tracer.SpanBuilder spanBuilder = tracer.buildSpan("childSpan").withTag("myTag", "spanSecond");
    if (parentspan !=null) {
        spanBuilder.asChildOf(parentspan).start();
    }
    Span childSpan = spanBuilder.start();
    tracer.scopeManager().activate(childSpan, false);
    // ...业务逻辑
    childSpan.finish();
    tracer.activeSpan().setTag("methodName", "testCall");
  5. 可选: 为了方便排查问题,您可以为某个记录添加一些自定义标签(Tag),例如记录是否发生错误、请求的返回值等。
    tracer.activeSpan().setTag("methodName", "testCall");
  6. 在分布式系统中发送 RPC 请求时会带上 Tracing 数据,包括 TraceId、ParentSpanId、SpanId、Sampled 等。您可以在 HTTP 请求中使用 Extract/Inject 方法在 HTTP Request Headers 上透传数据。总体流程如下:
       Client Span                                                Server Span
    ┌──────────────────┐                                       ┌──────────────────┐
    │                  │                                       │                  │
    │   TraceContext   │           Http Request Headers        │   TraceContext   │
    │ ┌──────────────┐ │          ┌───────────────────┐        │ ┌──────────────┐ │
    │ │ TraceId      │ │          │ X-B3-TraceId      │        │ │ TraceId      │ │
    │ │              │ │          │                   │        │ │              │ │
    │ │ ParentSpanId │ │ Inject   │ X-B3-ParentSpanId │Extract │ │ ParentSpanId │ │
    │ │              ├─┼─────────>│                   ├────────┼>│              │ │
    │ │ SpanId       │ │          │ X-B3-SpanId       │        │ │ SpanId       │ │
    │ │              │ │          │                   │        │ │              │ │
    │ │ Sampled      │ │          │ X-B3-Sampled      │        │ │ Sampled      │ │
    │ └──────────────┘ │          └───────────────────┘        │ └──────────────┘ │
    │                  │                                       │                  │
    └──────────────────┘                                       └──────────────────┘
    1. 在客户端调用 Inject 方法传入 Context 信息。

      private void attachTraceInfo(Tracer tracer, Span span, final Request request) {
              tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMap() {
                  @Override
                  public void put(String key, String value) {
                      request.setHeader(key, value);
                  }
                  @Override
                  public Iterator<Map.Entry<String, String>> iterator() {
                      throw new UnsupportedOperationException("TextMapInjectAdapter should only be used with Tracer.inject()");
                  }
              });
          }
    2. 在服务端调用 Extract 方法解析 Context 信息。

      protected Span extractTraceInfo(Request request, Tracer tracer) {
          Tracer.SpanBuilder spanBuilder = tracer.buildSpan("/api/xtrace/test03");
          try {
              SpanContext spanContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(request.getAttachments()));
              if (spanContext != null) {
                  spanBuilder.asChildOf(spanContext);
              }
          } catch (Exception e) {
              spanBuilder.withTag("Error", "extract from request fail, error msg:" + e.getMessage());
          }
          return spanBuilder.start();
      }
      								

通过 Spring Cloud 组件为 Java 应用埋点

要通过 Jaeger 将 Java 应用数据上报至链路追踪控制台,首先需要完成埋点工作。本示例为通过 Spring Cloud 组件埋点。Spring Cloud 提供了下列组件的埋点。

  • @Async, @Scheduled, Executors
  • Feign, HystrixFeign
  • Hystrix
  • JDBC
  • JMS
  • Mongo
  • RabbitMQ
  • Redis
  • RxJava
  • Spring Messaging - 链路消息通过消息通道发送
  • Spring Web (RestControllers, RestTemplates, WebAsyncTask)
  • Standard Logging - 日志被添加至当前 Span
  • WebSocket STOMP
  • Zuul

请按照以下步骤通过 Spring Cloud 组件埋点。

说明下载 Demo 工程,进入 springMvcDemo/webmvc4-boot 目录,并按照 Readme 的说明运行程序。
  1. 打开 pom.xml,添加 Jar 包依赖。
    <dependency>
       <groupId>io.opentracing.contrib</groupId>
      <artifactId>opentracing-spring-cloud-starter</artifactId>
      <version>0.2.0</version>
    </dependency>
    <dependency>
      <groupId>io.jaegertracing</groupId>
      <artifactId>jaeger-client</artifactId>
      <version>0.31.0</version>
    </dependency>
  2. 添加 OpenTracing Tracer Bean。
    注意 请将 <endpoint> 替换成链路追踪控制台概览页面上相应客户端和相应地域的接入点。关于获取接入点信息的方法,请参见前提条件中的获取接入点信息
    @Bean
    public io.opentracing.Tracer tracer() {
        io.jaegertracing.Configuration config = new io.jaegertracing.Configuration("springFrontend");
        io.jaegertracing.Configuration.SenderConfiguration sender = new io.jaegertracing.Configuration.SenderConfiguration();
        sender.withEndpoint("<endpoint>");
        config.withSampler(new io.jaegertracing.Configuration.SamplerConfiguration().withType("const").withParam(1));
        config.withReporter(new io.jaegertracing.Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000));
        return config.getTracer();
    }

通过 gRPC 组件为 Java 应用埋点

要通过 Jaeger 将 Java 应用数据上报至链路追踪控制台,首先需要完成埋点工作。本示例为通过 gRPC 组件埋点。

请按照以下步骤通过 gRPC 组件埋点。

说明下载 Demo 工程,进入 grpcDemo 目录,并按照 Readme 的说明运行程序。
  1. 打开 pom.xml,添加 Jar 包依赖。
    <dependency>
        <groupId>io.opentracing.contrib</groupId>
        <artifactId>opentracing-grpc</artifactId>
        <version>0.0.7</version>
    </dependency>
  2. 在服务端初始化 Tracing 对象,创建 ServerTracingInterceptor,并添加对 Server 的拦截。
    import io.opentracing.Tracer;
    
        public class YourServer {
    
            private int port;
            private Server server;
            private final Tracer tracer;
    
            private void start() throws IOException {
                ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor(this.tracer);
    
                // If GlobalTracer is used: 
                // ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor();
    
                server = ServerBuilder.forPort(port)
                    .addService(tracingInterceptor.intercept(someServiceDef))
                    .build()
                    .start();
            }
        }
  3. 在客户端初始化 Tracing 对象,创建 ClientTracingInterceptor,并添加对 Client Channel 的拦截。
    import io.opentracing.Tracer;
    
        public class YourClient {
    
            private final ManagedChannel channel;
            private final GreeterGrpc.GreeterBlockingStub blockingStub;
            private final Tracer tracer;
    
            public YourClient(String host, int port) {
    
                channel = ManagedChannelBuilder.forAddress(host, port)
                    .usePlaintext(true)
                    .build();
    
                ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor(this.tracer);
    
                // If GlobalTracer is used: 
                // ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor();
    
                blockingStub = GreeterGrpc.newBlockingStub(tracingInterceptor.intercept(channel));
            }
        }
    						

常见问题

问:Demo 程序执行成功,为什么控制台上没有上报数据?

答:请调试方法 io.jaegertracing.thrift.internal.senders.HttpSender.send(Process process, List<Span> spans),并查看上报数据时的返回值。如果报 403 错误,则表明接入点配置不正确,请检查并修改。

更多信息