29

OpenTracing Java Library教程(1)——trace和span入门

 4 years ago
source link: https://niyanchun.com/opentracing-java-library-tutorial-1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文内容主要翻译(意译)自Yurishkuro大神的 opentracing-tutorial java ,加了一些补充说明,方便理解,习惯看英文的也可以看原文。总共4篇,本文是第1篇。如果你还没接触过OpenTracing,建议先读这篇文章《 OpenTracing概念术语介绍 》和 官方文档

  1. 第1篇:单span的trace创建本文 )。
  2. 第2篇:多span的trace创建(进程内SpanContext传递)
  3. 第3篇:跨服务(进程)传递SpanContext
  4. 第4篇:Baggage介绍

学习如何:

  • 实例化一个Tracer
  • 创建一个简单的链路
  • 给链路增加一些注解(annotation):即增加Tag和Log。

开发步骤

说明:源代码的exercise包下面的类是空的,是留给我们按教程一步步补充完善的;solution包是已经编写好的代码。我翻译的时候,都直接运行的是solution里面的代码,但教程里面是逐步完善代码的,也就是会有一个中间状态。所以我会根据内容作了一些必要的注释和修改。但如果你是第一次看的话,建议按照教程自己手动在exercise里面完善。跟着教程一步步学习。

创建一个简单的Hello-World程序

创建一个简单的打印程序:接受一个参数,输出"Hello, {arg}!"。代码如下:

package lesson01.exercise;

public class Hello {

    private void sayHello(String helloTo) {
        String helloStr = String.format("Hello, %s!", helloTo);
        System.out.println(helloStr);
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("Expecting one argument");
        }
        String helloTo = args[0];
        new Hello().sayHello(helloTo);
    }
}

运行:

$ ./run.sh lesson01.exercise.Hello Bryan
Hello, Bryan!

创建一个trace

一个trace是由若干span组成的有向无环图。一个span代表应用中的一个逻辑操作,每个span至少包含三个属性: 操作名(an operation time)、开始时间(start time)、结束时间(finish time)。

下面我们使用一个 io.opentracing.Tracer 实例创建由一个span组成的trace,可以使用 io.opentracing.util.GlobalTracer.get() 创建一个全局的Tracer实例。代码如下:

import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;

public class Hello {

    private final Tracer tracer;

    private Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();

        String helloStr = String.format("Hello, %s!", helloTo);
        System.out.println(helloStr);

        span.finish();
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("Expecting one argument");
        }
        String helloTo = args[0];
        new Hello(GlobalTracer.get()).sayHello(helloTo);
    }
}

这里我们使用了OpenTracing API的一些基本特性:

  • 调用 tracer 实例的 buildSpan() 方法创建一个span
  • buildSpan() 方法的参数就是span的 操作名
  • 调用 start() 方法真正创建出一个span
  • 通过调用 finish() 方法结束一个span
  • span的开始时间和结束时间由具体的tracer实现自动生成(获取创建和结束span时的系统时间戳)

此时,我们运行程序并不会和原来的程序有什么区别,也不会产生链路数据。因为OpenTracing只提供了SDK,并没有提供具体的链路实现,所以要产生真正的链路数据,需要借助具体的链路实现。

部署Jaeger(补充段落,原文没有)

这里我们选择Uber开源的Jaeger(发音为ˈyā-gər ),因为它对OpenTracing支持的比较好,而且部署使用也非常简单。另外Jaeger的作者就是Yurishkuro。这里就不介绍Jaeger的细节了,有兴趣的可以去官网了解: Jaeger官网

Jaeger部署非常简单,从这里 下载 安装包或者下载docker镜像。这里我下载的macOS的安装包,解压后可以看到如下文件:

example-hotrod
jaeger-agent      
jaeger-all-in-one 
jaeger-collector  
jaeger-ingester   
jaeger-query

直接运行 ./jaeger-all-in-one 便可以启动一个完整的Jaeger。此时访问 http://localhost :16686/即可查看Jaeger的UI:

EJvqiyb.png!web

这样,一个OpenTracing的实现(Jaeger)就有了。接下来我们看如何在代码中集成。

集成Jaeger

在pom.xml中引入Jaeger的依赖:

<dependency>
    <groupId>io.jaegertracing</groupId>
    <artifactId>jaeger-client</artifactId>
    <version>0.32.0</version>
</dependency>

然后写一个创建tracer的函数:

import io.jaegertracing.Configuration;
import io.jaegertracing.Configuration.ReporterConfiguration;
import io.jaegertracing.Configuration.SamplerConfiguration;
import io.jaegertracing.internal.JaegerTracer;

public static JaegerTracer initTracer(String service) {
    SamplerConfiguration samplerConfig = SamplerConfiguration.fromEnv().withType("const").withParam(1);
    ReporterConfiguration reporterConfig = ReporterConfiguration.fromEnv().withLogSpans(true);
    Configuration config = new Configuration(service).withSampler(samplerConfig).withReporter(reporterConfig);
    return config.getTracer();
}

最后,修改原来代码中的main函数:

Tracer tracer = initTracer("hello-world");
new Hello(tracer).sayHello(helloTo);

注意我们给 initTracer() 方法传入了一个参数hello-world,这个是服务名。该服务里面产生的所有span公用这个服务名,一般服务名会用来做过滤和聚合。

现在运行代码,可以看到日志中有输出产生的span信息,而且也能看到Tracer实例的一些信息:

19:07:10.645 [main] DEBUG io.jaegertracing.thrift.internal.senders.ThriftSenderFactory - Using the UDP Sender to send spans to the agent.
19:07:10.729 [main] DEBUG io.jaegertracing.internal.senders.SenderResolver - Using sender UdpSender()
# tracer实例信息
19:07:10.776 [main] INFO io.jaegertracing.Configuration - Initialized tracer=JaegerTracer(version=Java-1.1.0, serviceName=hello-world, reporter=CompositeReporter(reporters=[RemoteReporter(sender=UdpSender(), closeEnqueueTimeout=1000), LoggingReporter(logger=Logger[io.jaegertracing.internal.reporters.LoggingReporter])]), sampler=ConstSampler(decision=true, tags={sampler.type=const, sampler.param=true}), tags={hostname=NYC-MacBook, jaeger.version=Java-1.1.0, ip=192.168.0.109}, zipkinSharedRpcSpan=false, expandExceptionLogs=false, useTraceId128Bit=false)
Hello, Bryan!
# span信息
19:07:10.805 [main] INFO io.jaegertracing.internal.reporters.LoggingReporter - Span reported: a86d76defe28d413:a86d76defe28d413:0:1 - say-hello

当然也可以以调试模式启动,观察更多细节。

这个时候,我们打开Jaeger的UI,左侧的Service选择“hello-world”,然后点击最下面的“Find Traces”,就可以查到刚才这次程序运行产生的Trace信息了:

yQfyYvf.png!web

点击链路详情进去后,再次点击操作名,可以查看一些基本信息,Jaeger默认已经加了一些基本的信息。

mQ3IRff.png!web

下面我们来看如何加一些自定义的信息。

增加Tags和Logs

OpenTracing规定了可以给Span增加三种类型的注解信息:

  • Tags :key-value格式的数据,key和value完全由用户自定义。需要注意的是Tags增加的信息应该是属于描述整个span的,也就是它是span的一个静态属性,记录的信息适用于span从创建到完成的任何时刻。再说直白点就是记录和时间点无关的信息,这个主要是和下面的Logs作区分。
  • Logs :和Tags类似,也是key-value格式的数据,区别在于Logs的信息都会带一个时间戳属性,记录这条属性产生的时间戳,所以比较适合记录日志、异常栈等一些和时间相关的信息。
  • Baggage Items :这个主要是用于跨进程全局传输数据,后面的lesson04专门演示这个特性,这里先不展开介绍了。

Tags和Logs的记录非常的简单和方便:

private void sayHello(String helloTo) {
    Span span = tracer.buildSpan("say-hello").start();
    // 增加Tags信息
    span.setTag("hello-to", helloTo);

    String helloStr = String.format("Hello, %s!", helloTo);
    // 增加Logs信息
    span.log(ImmutableMap.of("event", "string-format", "value", helloStr));

    System.out.println(helloStr);
    // 增加Logs信息
    span.log(ImmutableMap.of("event", "println"));

    span.finish();
}

注意这里使用了Guava's ImmutableMap.of() 来构造一个Map。

再次运行程序,同样会产生一个span,但这次span会多了一个Tag和Log信息(Jaeger默认已经加了一些内部的tag数据):

umUVVvJ.png!web

从图中可以看到代码中加的Tags信息和Logs信息,而且Logs信息是带了时间了(这里展示的是从span开始时间经过的毫秒数)。关于Tags和Logs的规范,OpenTracing做了一些引导规范,可以参考: semantic_conventions .

本文主要展示了如何创建一个span,下篇文章演示如何如果创建一个包含多个span的trace,以及如何在进程内部(不同方法间)传递span信息。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK