OpenTracing的使用实例(Java)

您所在的位置:网站首页 pic16c57c使用实例 OpenTracing的使用实例(Java)

OpenTracing的使用实例(Java)

2023-08-11 21:13| 来源: 网络整理| 查看: 265

构件组织

OpenTracing API的Java构件如下:

opentracing-api:主要的API,无其他依赖。 opentracing-noop:为主要API提供无意义实现(NoopTracer),依赖于opentracing-api。 opentracing-util:工具类,例如GlobalTracer和默认的基于ThreadLocal存储的ScopeManager实现,依赖于上面所有的构件。 opentracing-mock:用于测试的mock层。包含MockTracer,简单的将Span存储在内存中,依赖于opentracing-api和opentracing-noop。 安装(Maven) io.opentracing opentracing-api VERSION

也可以使用opentracing-noop,opentracing-mock,opentracing-util来安装其他的构件,如果安装多个构件,需要提供一致的VERSION。

主要API

主要的OpenTracing API将所有主要组件声明为接口以及辅助类,例如Tracer,Span,SpanContext,Scope,ScopeManager,Format(用映射定义通用的SpanContext注入和提取格式)。

OpenTracing 社区贡献

除了官方的API,也有一些苦在opentracing-contribe,保管通用的辅助类像TracerResolver和框架工具库,例如 Java Web Servlet Filter and Spring Cloud,可以用于在使用这些框架工具的项目中方便的集成OpenTracing。

Quick Start

下面使用opentracing-mock中的MockTracer来进行示例:

import java.util.Map; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockSpan; import io.opentracing.tags.Tags; // Initialize MockTracer with the default values. MockTracer tracer = new MockTracer(); // Create a new Span, representing an operation. MockSpan span = tracer.buildSpan("foo").start(); // Add a tag to the Span. span.setTag(Tags.COMPONENT, "my-own-application"); // do something for business logic // Finish the Span. span.finish(); // Analyze the saved Span. System.out.println("Operation name = " + span.operationName()); System.out.println("Start = " + span.startMicros()); System.out.println("Finish = " + span.finishMicros()); // Inspect the Span's tags. Map tags = span.tags(); 使用Span

在任何时间点,OpenTracing Java API仅允许同一个线程中只存在一个活跃的Span。但是在同一个线程中允许同时存在符合下述条件的Span:

Started,新建的Span,但是没有在任何作用域(Scope)中激活 Not Finished,调用finish方法之前均处于该状态 Not Active,未被激活

同一个线程上可能有多个Span,如果它们:

正在等待I/O操作完成 被子Span阻塞 或被溢出关键路径

人工地将活跃的Span从一个函数传递到另一个函数是极为不便的,所以OpenTracing要求每个Tracer包含一个作用域管理器(ScopeManager)。ScopeManager可以通过Scope来方法激活的Span,Scope来管理Span的激活与失活。ScopeManager API运行将Span传到到另一个线程或回调,而不是传递Scope。

开发这在创建新的Span时,如果当前线程的Scope中已经存在活跃的Span,则该活跃Span则会成为新创建Span的父亲,除非开发者在buildSpan()时调用ignoreActiveSpan()或者明确指定父上下文(parent context)。

访问活跃的Span

开发者可以通Scope对象访问活跃的Span

io.opentracing.Tracer tracer = ...; ... Scope scope = tracer.scopeManager().active(); if (scope != null) { scope.span().log("..."); } 在线程间移动Span

使用OpenTracing API,开发者可以在多个不同的线程间传输Span。一个Span的生命周期可以在一个线程中开始在另一个线程中结束。不支持传递Scope到另一个线程或回调。Span的内部时序细节看来如下:

[ ServiceHandlerSpan ] | FunctionA | waiting on an RPC | FunctionB | ---------------------------------------------------------> time

当执行FunctionA和FunctionB时ServiceHandlerSpan是活跃的,但是在等待RPC调用的过程中是失活的。RPC可能有自己的Span,但我们现在只关注ServerHandlerSpan如何从FunctionA传播到FunctionB。使用ScopeManager API可以在FunctionA中获取Span,RPC结束后在FunctionB中重新获取Span。步骤如下:

通过startManager或startActive(false)方法创建一个Span以阻止Scope失活时令Span终止。 在回调代码(闭包/Runnable/Future)中调用tracer.scopeManager().active(span,false)来重新激活Span获取一个新的Scope,当Span不再活跃时关闭Scope(或者使用try-with-resources以简化代码) 在回调代码末尾,调用tracer.scopeManager().active(span,true)来重新激活Span并得到一个自动关闭的Scope。

代码如下:

io.opentracing.Tracer tracer = ...;//通过具体的实现来创建tracer对象 ... // STEP 1 ABOVE: 开启新的Span和Scope try (Scope scope = tracer.buildSpan("ServiceHandlerSpan").startActive(false)) { // Span在Scope中被激活 final Span span = scope.span(); doAsyncWork(new Runnable() { @Override public void run() { // STEP 2 ABOVE: 重新激活Span // 如果需要自动终止激活的Span,传递true给active方法 try (Scope scope = tracer.scopeManager().activate(span, true)) { ... } } }); } 通过框架的拦截器能力实现HTTP请求追踪

通过上文中的代码,我们知道了如何使用Tracer对象构建Span,如何在线程中激活Span,以及如何在异步环境的不同线程间传递Span。

在实际的业务开发中,我们很难使用这种侵入的方式来实现追踪,更多的是利用各种框架提供的拦截器机制,来对各种业务调用进行自动追踪,比如Spring AOP,Servlet Filter,等等。下面一段代码展示了如何通过Servlet Filter来进行服务端的HTTP请求追踪。

@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; // 从Http Headers中提取上下文 SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(httpRequest)); // 创建并激活一个新的Span,如果前面提取到的上下文不为null,则作为父SpanContext final Scope scope = tracer.buildSpan(httpRequest.getMethod()) .asChildOf(extractedContext) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .startActive(false); final Span span = scope.span(); // 追踪请求的地址 span.setTag(Tags.HTTP_URL, httpRequest.getRequestURI()); try { // 实际执行过滤器链处理请求 chain.doFilter(servletRequest, servletResponse); } finally { if (httpRequest.isAsyncStarted()) { // 如果请求是异步的,那么需要将Span对象传递到异步回调中 httpRequest.getAsyncContext() .addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { // 回调是在异步线程中执行的 // 需要使用Scope在异步先线程中激活Span try(Scope sc = tracer.scopeManager().activite(span, true)){ HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse(); // 追踪响应状态 sc.span().setTag(Tags.HTTP_STATUS, httpResponse.getStatus()); } } @Override public void onTimeout(AsyncEvent event) throws IOException { try(Scope sc = tracer.scopeManager().activite(span, true)){ // 记录错误 sc.span().setTag(Tags.ERROR, true) sc.span().log(Maps.of(Fields.EVENT, event, Fields.ERROR_KIND, "TIMEOUT")) } } @Override public void onError(AsyncEvent event) throws IOException { try(Scope sc = tracer.scopeManager().activite(span, true)){ // 记录错误 sc.span().setTag(Tags.ERROR, true) sc.span().log( Maps.of(Fields.EVENT, event, Fields.ERROR_KIND, event.getThrowable().getClass())) } } @Override public void onStartAsync(AsyncEvent event) throws IOException { } }); } else { // 如果是同步请求,直接终止Span scope.span().finish(); } // 释放当前线程中的Span scope.close(); } }

利用这个过滤器,在Servlet应用中,用于追踪请求代码与业务代码解耦,并且仅需要一次编写,下面来看客户端如何追踪请求并向处理请求的服务端传递上下文,以Spring RestTemplate为例:

可以通过RestTemplate.setInterceptors注册拦截器。

@Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 创建新的Span,以当前线程中的SpanContext为父,如没有则自己成为根Span try (Scope scope = tracer.buildSpan(httpRequest.getMethod().toString()) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).startActive(true)) { // 追踪请求地址 scope.span().setTag(Tags.HTTP_URL, httpRequest.getURI().toString()) // 将SpanContext注入到请求头中 // 看前文中的代码可以知道,服务端通过Tracer.extract可以从请求头中提取出SpanContext tracer.inject(scope.span().context(), Format.Builtin.HTTP_HEADERS, new HttpHeadersCarrier(httpRequest.getHeaders())); // 实际执行请求 return execution.execute(httpRequest, body); } } 使用opentracing-spring-cloud

上文中通过代码示例了,如何通过框架工具提供的拦截器能力来实现请求追踪,由于Spring MVC,RestTemplate,Servlet……这些开源工具是的用户相当广泛,所以在opentracing-contrib项目中提供了非常多针对这些被广泛使用的开源工具的集成支持包。

其中java-spring-cloud子项目,为spring-cloud项目提供了opentracing-spring-cloud-starter,这个starter通过依赖了很多其他的opentrcing集成支持库,来为基于spring-cloud架构的应用提供一站式opentracing集成方案,其中包括如下组件:

利用SpringBoot的AutoConfiguration机制为用户提供了几乎无须手动配置的集成方案。

Spring Web (RestControllers, RestTemplates, WebAsyncTask, WebClient, WebFlux) @Async, @Scheduled, Executors WebSocket STOMP Feign, HystrixFeign Hystrix JMS JDBC Mongo Zuul Reactor RxJava Redis Standard logging - logs are added to active span Spring Messaging - trace messages being sent through Messaging Channels RabbitMQ

使用SpringCloud的开发者,可以简单的将opentracing-spring-cloud-starter添加到自己项目的依赖中,来体验它带来的opentracing集成。

如果不使用SpringCloud也可以其为起点,按自己的需求从其依赖中挑选自己需要的部分,或者浏览opentracing-contrib项目来寻找自己需要的支持库。

使用Jaeger

前文中描述的API以及中间件集成方案,都是对OpenTracing API的集成,仔细看代码中缺少一个必要的构建Tracer对象的步骤。在实际场景中,我们需要一种具体的OpenTracing实现,来创建Tracer对象。

Jaeger是由Uber开源的OpenTracing实现项目,它提供了追踪数据上报服务以及数据的视图,来帮助开发者解决分布式系统中的如下问题:

分布式事务监控 性能和延迟优化 分析故障源头 服务以来分析 分布式上下文传播

以来jaeger-client-java可以利用如下代码创建一个Tracer对象:

Configuration config = new io.jaegertracing.Configuration("服务名称"); // 设置数据发送方式 Configuration.SenderConfiguration sender = new Configuration.SenderConfiguration(); sender.withEndpoint(""); // endpoint可以是在阿里云上购买的链路追踪服务或者自己使用Jaeger搭建的服务 // 设置采样方式 config.withSampler(new Configuration.SamplerConfiguration().withType("const").withParam(1)); // 设置数据上报方式 config.withReporter(new Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000)); Tracer tracer = config.getTracer();

Configuration类提供了非常多的配置功能,有兴趣的开发者可以阅读其API文档来了解更多的自定义选项,甚至扩展Jaeger的功能。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3