背景:公司内部的RPC框架,需要接入Jaeger, 只能自己实现了下OpenTracing; 分析、借鉴了下 opentracing-contrib/java-spring-jaeger,记录一下。
其实 java-spring-jaeger web 仓库/依赖,其实只是一个SpringBoot Starter,使其自动注入了依赖,并且注册了拦截器, 拦截器原理主要是依赖 opentracing-contrib/java-spring-web;
下面我们分析下其源码;
一、主要依赖代码
git地址: https://github.com/opentracing-contrib/java-spring-jaeger
java 依赖:
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
</dependency>
引用关系: opentracing-spring-web-starter -> opentracing-spring-web
starter 描述了注册加载Bean逻辑, spring-web 为真正的spring web 拦截逻辑 ;
二、 是如何实现的?
2.1 实现逻辑
-
服务端: ServerTracingAutoConfiguration -> tracingFilter(), tracingHandlerInterceptor() -> TracingFilter.class, TracingHandlerInterceptor.class
-
客户端:
-
TracingRestTemplateInterceptor.class: restTemplate 拦截器
-
TracingAsyncRestTemplateInterceptor.class: AsyncRestTemplate拦截器
-
TracingWebClientBeanPostProcessor.class : webClient 拦截器
-
TracingWebFilter.class: Tracing {@link WebFilter} for Spring WebFlux.
-
2.2 客户端/服务端实现代码
下面已 TracingRestTemplateInterceptor 为例,列出来给大家看下拦截器的主要逻辑;
主要模块设计请见下图, 图片来自网络,链接:https://www.jianshu.com/p/82cd923191fb
2.1 客户端标准实现
Span span = tracer.buildSpan("...").start();
try (Scope scope = tracer.scopeManager().activate(span)) {
span.setTag("...", "...");
//... 业务调用逻辑
} catch (Exception e) {
span.log(...);
} finally {
// Optionally finish the Span if the operation it represents
//is logically completed at this point.
span.finish();
}
2.2 服务端标准实现
/**
* If request is traced then do not start new span.
*/
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 all exceptions (e.g. RuntimeException, ServletException...)
} catch (Throwable ex) {
for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {
spanDecorator.onError(httpRequest, httpResponse, ex, span);
}
throw ex;
} finally {
if (httpRequest.isAsyncStarted()) {
// what if async is already finished? This would not be called
httpRequest.getAsyncContext()
.addListener(new AsyncListener() {
@Override
public 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();
}
@Override
public 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);
}
}
@Override
public 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);
}
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
} else {
// If not async, then need to explicitly finish the span associated with the scope.
// This is necessary, as we don't know whether this request is being handled
// asynchronously until after the scope has already been started.
span.finish();
}
}
}