本文内容主要翻译(意译)自Yurishkuro大神的 opentracing-tutorial java ,加了一些补充说明,方便理解,习惯看英文的也可以看原文。总共4篇,本文是第1篇。如果你还没接触过OpenTracing,建议先读这篇文章《 OpenTracing概念术语介绍 》和 官方文档 。
学习如何:
说明:源代码的exercise包下面的类是空的,是留给我们按教程一步步补充完善的;solution包是已经编写好的代码。我翻译的时候,都直接运行的是solution里面的代码,但教程里面是逐步完善代码的,也就是会有一个中间状态。所以我会根据内容作了一些必要的注释和修改。但如果你是第一次看的话,建议按照教程自己手动在exercise里面完善。跟着教程一步步学习。
创建一个简单的打印程序:接受一个参数,输出"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是由若干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 此时,我们运行程序并不会和原来的程序有什么区别,也不会产生链路数据。因为OpenTracing只提供了SDK,并没有提供具体的链路实现,所以要产生真正的链路数据,需要借助具体的链路实现。
这里我们选择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:
这样,一个OpenTracing的实现(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信息了:
点击链路详情进去后,再次点击操作名,可以查看一些基本信息,Jaeger默认已经加了一些基本的信息。
下面我们来看如何加一些自定义的信息。
OpenTracing规定了可以给Span增加三种类型的注解信息:
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数据):
从图中可以看到代码中加的Tags信息和Logs信息,而且Logs信息是带了时间了(这里展示的是从span开始时间经过的毫秒数)。关于Tags和Logs的规范,OpenTracing做了一些引导规范,可以参考: semantic_conventions .
本文主要展示了如何创建一个span,下篇文章演示如何如果创建一个包含多个span的trace,以及如何在进程内部(不同方法间)传递span信息。