三. 扩展点分析
通过前面的分析我们了解到,Opentracing对分布式链路追踪中的各种概念进行了统一的定义,某种程度上,已经成为分布式链路追踪的规范。
在Java语言中,Opentracing定义了诸如Span和Tracer等概念对应的接口,不同的分布式链路实现方需要结合具体的实现方案来提供相应实现,例如本文选择的Jaeger,其提供的JaegerSpan实现了Span接口,JaegerTracer实现了Tracer接口等。
现在接口定义已经有了,具体的实现也有了,该怎么用起来呢。在本文的示例中,具体的使用案例就是我们提供的RestTemplate拦截器,以及过滤器TracingFilter,那么问题就来了,为什么我知道可以这么用,是因为我比较聪明吗,那必然不是,当然是Opentracing告诉我该这么用,所以我才这么用,既然Opentracing定义好了接口,还告诉了用户该怎么用,那么有没有一种可能,Opentracing来提供RestTemplate拦截器,来提供过滤器TracingFilter呢,那完全是有可能的,Opentracing也正是这么做的。
Opentracing为RestTemplate提供了一个拦截器叫做TracingRestTemplateInterceptor,也提供了一个过滤器叫做TracingFilter,好吧,到这里我就不装了,示例中的RestTemplate拦截器和过滤器TracingFilter,其实就是抄的Opentracing的,不过我没抄全,毕竟我只是需要搭建一个演示demo,所以官方的很多为了提升扩展性的扩展点,我都给砍掉了,而这些扩展点,正是我们基于已有的轮子造更好的轮子的基础,也正是本节的分析重点。
1. ServletFilterSpanDecorator和RestTemplateSpanDecorator
我们先看一下io.opentracing.contrib.web.servlet.filter.TracingFilter中有哪些扩展点。Opentracing为Servlet提供了一个专门服务于分布式链路追踪的过滤器TracingFilter,其实现了javax.servlet.Filter接口,关键的doFilter() 方法如下所示。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;if (!isTraced(httpRequest, httpResponse)) {chain.doFilter(httpRequest, httpResponse);return;}if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) {chain.doFilter(servletRequest, servletResponse);} else {SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS,new HttpServletRequestExtractAdapter(httpRequest));final Span span = tracer.buildSpan(httpRequest.getMethod()).asChildOf(extractedContext).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER).start();httpRequest.setAttribute(SERVER_SPAN_CONTEXT, span.context());for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {spanDecorator.onRequest(httpRequest, span);}try (Scope scope = tracer.activateSpan(span)) {chain.doFilter(servletRequest, servletResponse);if (!httpRequest.isAsyncStarted()) {for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {spanDecorator.onResponse(httpRequest, httpResponse, span);}}} catch (Throwable ex) {for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {spanDecorator.onError(httpRequest, httpResponse, ex, span);}throw ex;} finally {if (httpRequest.isAsyncStarted()) {httpRequest.getAsyncContext().addListener(new AsyncListener() {@Overridepublic void onComplete(AsyncEvent event) throws IOException {HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {spanDecorator.onResponse(httpRequest,httpResponse,span);}span.finish();}@Overridepublic void onTimeout(AsyncEvent event) throws IOException {HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {spanDecorator.onTimeout(httpRequest,httpResponse,event.getAsyncContext().getTimeout(),span);}}@Overridepublic void onError(AsyncEvent event) throws IOException {HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {spanDecorator.onError(httpRequest,httpResponse,event.getThrowable(),span);}}@Overridepublic void onStartAsync(AsyncEvent event) throws IOException {}});} else {span.finish();}}}
}
通过上述代码,可以发现有一个满脸长着我是扩展点的东西,就是ServletFilterSpanDecorator,其主要负责在过滤器链执行之前,之后以及发生异常时对Span进行装饰,怎么理解这里的装饰呢,其实就是往Span添加一些东西或者修改一些东西,举个例,在过滤器链执行前,往Span的Tags中添加本次请求的URI,然后在过滤器链执行后,往Span的Tags中添加本次请求的响应码,等等这些需求,都可以在ServletFilterSpanDecorator中完成,这其实就赋予了我们对Span极高的可操作性。
装饰器除了在TracingFilter中有被使用,同样也在Opentracing提供的TracingRestTemplateInterceptor中被使用,其intercept() 方法如下所示。
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body,ClientHttpRequestExecution execution) throws IOException {ClientHttpResponse httpResponse;Span span = tracer.buildSpan(httpRequest.getMethod().toString()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).start();tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS,new HttpHeadersCarrier(httpRequest.getHeaders()));for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {try {spanDecorator.onRequest(httpRequest, span);} catch (RuntimeException exDecorator) {log.error("Exception during decorating span", exDecorator);}}try (Scope scope = tracer.activateSpan(span)) {httpResponse = execution.execute(httpRequest, body);for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {try {spanDecorator.onResponse(httpRequest, httpResponse, span);} catch (RuntimeException exDecorator) {log.error("Exception during decorating span", exDecorator);}}} catch (Exception ex) {for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {try {spanDecorator.onError(httpRequest, ex, span);} catch (RuntimeException exDecorator) {log.error("Exception during decorating span", exDecorator);}}throw ex;} finally {span.finish();}return httpResponse;
}
上述实现中,在请求前,请求后以及报错时使用了RestTemplateSpanDecorator来装饰Span,所以RestTemplateSpanDecorator也是一个重要的扩展点,具体如何使用,在后续的文章中会逐步进行演示。
2. Injector和Extractor
Injector和Extractor分别用来处理SpanContext的注入和提取操作,以Jaeger为例,在创建JaegerTracer时,可以按照键值对的方式,向JaegerTracer注册Injector和Extractor,就像下面这样。
@Configuration
public class TracerConfig {@Autowiredprivate SpanReporter spanReporter;@Autowiredprivate Sampler sampler;@Beanpublic Tracer tracer(MyHttpHeadersInjector myHttpHeadersInjector,MyHttpHeadersExtractor myHttpHeadersExtractor) {return new JaegerTracer.Builder(TRACER_SERVICE_NAME).withTraceId128Bit().withSampler(sampler).withReporter(spanReporter).registerInjector(Format.Builtin.HTTP_HEADERS, myHttpHeadersInjector).registerExtractor(Format.Builtin.HTTP_HEADERS, myHttpHeadersExtractor).build();}}
键是Format,可以自己定义,也可以使用Opentracing为我们定义好的,例如Format.Builtin#HTTP_HEADERS,值就是Injector或Extractor的实现类,那么我们就可以自己提供Injector或Extractor的实现类来扩展SpanContext的注入和提取操作。
3. ScopeManager
大多数情况下,Opentracing提供的ThreadLocalScopeManager能满足我们的使用需求,但如果是异步链路追踪的场景,ThreadLocal就无法满足使用需求,此时需要使用InheritableThreadLocal,我们就可以基于InheritableThreadLocal来提供一个ScopeManager接口的实现类,并在创建Tracer时指定我们要使用的ScopeManager。还是以Jaeger为例,在创建JaegerTracer时,可以指定要使用的ScopeManager,如下所示。
@Configuration
public class TracerConfig {@Autowiredprivate SpanReporter spanReporter;@Autowiredprivate Sampler sampler;@Beanpublic Tracer tracer(ScopeManager scopeManager) {return new JaegerTracer.Builder(TRACER_SERVICE_NAME).withTraceId128Bit().withSampler(sampler).withReporter(spanReporter).withScopeManager(scopeManager).build();}}
具体如何使用,以及如何基于InheritableThreadLocal来实现适用于异步链路追踪的ScopeManager,这里就不再深入,这一块儿将在后续的文章中进行分析和演示。
4. 其它扩展点
扩展点还有很多,限于本文篇幅,这里就不再一一介绍,后面在实现分布式链路工具包的时候,用到了,自然会进行说明。
总结
一不小心,又扯了这么多,本文其实重点就是聚焦于Opentracing中的若干重要概念,这里用于一张图进行总结吧。