引言
由于原有业务服务中使用的是第三方的HttpClient组件调用业务服务接口,而SpringCloud-Sleuth本身没有对AsyncHttpClient提供类似于ClientRequestInterceptor和ClientResponseInterceptor,所以需要我们定制,而ServerRequestInterceptor和ServerResponseInterceptor是在BraveServletFilter部分直接指定就好了(这在任何client技术下都可以复用)。
为了不影响现有业务服务代码逻辑的修改和侵入,保证原有业务服务系统的稳定性和减少人员成本的投入,亟需一种轻量的方式加入少量的代码达到技术实现目标。
分析
SpringCloud-Sleuth默认提供的是基于RestTemplate和javax.servlet.HttpServletRequest的拦截器实现链路追踪,代码如下:
public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor
implements ClientHttpRequestInterceptor {
private final ErrorParser errorParser;
public TraceRestTemplateInterceptor(Tracer tracer, HttpSpanInjector spanInjector,
HttpTraceKeysInjector httpTraceKeysInjector, ErrorParser errorParser) {
super(tracer, spanInjector, httpTraceKeysInjector);
this.errorParser = errorParser;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
publishStartEvent(request);
return response(request, body, execution);
}
private ClientHttpResponse response(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
try {
return new TraceHttpResponse(this, execution.execute(request, body));
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e);
}
this.errorParser.parseErrorTags(currentSpan(), e);
finish();
throw e;
}
}
}
通过上面的代码我们可以发现它重写了intercept方法,接收HttpRequest参数。同时,它继承了AbstractTraceHttpRequestInterceptor抽象拦截器。
为了搞清楚内部是如何将多个请求进行串联起来的,延着这个思路,我们分析一下其抽象拦截器的代码:
abstract class AbstractTraceHttpRequestInterceptor {
protected static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
protected final Tracer tracer;
protected final HttpSpanInjector spanInjector;
protected final HttpTraceKeysInjector keysInjector;
protected AbstractTraceHttpRequestInterceptor(Tracer tracer,
HttpSpanInjector spanInjector, HttpTraceKeysInjector keysInjector) {
this.tracer = tracer;
this.spanInjector = spanInjector;
this.keysInjector = keysInjector;
}
/**
* Enriches the request with proper headers and publishes
* the client sent event
*/
protected void publishStartEvent(HttpRequest request) {
URI uri = request.getURI();
String spanName = getName(uri);
Span newSpan = this.tracer.createSpan(spanName);
this.spanInjector.inject(newSpan, new HttpRequestTextMap(request));
addRequestTags(request);
newSpan.logEvent(Span.CLIENT_SEND);
if (log.isDebugEnabled()) {
log.debug("Starting new client span [" + newSpan + "]");
}
}
private String getName(URI uri) {
// The returned name should comply with RFC 882 - Section 3.1.2.
// i.e Header values must composed of printable ASCII values.
return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getRawPath());
}
private String uriScheme(URI uri) {
return uri.getScheme() == null ? "http" : uri.getScheme();
}
/**
* Adds HTTP tags to the client side span
*/
protected void addRequestTags(HttpRequest request) {
this.keysInjector.addRequestTags(request.getURI().toString(),
request.getURI().getHost(),
request.getURI().getPath(),
request.getMethod().name(),
request.getHeaders());
}
/**
* Close the current span and log the client received event
*/
public void finish() {
if (!isTracing()) {
return;
}
currentSpan().logEvent(Span.CLIENT_RECV);
this.tracer.close(this.currentSpan());
}
protected Span currentSpan() {
return this.tracer.getCurrentSpan();
}
protected boolean isTracing() {
return this.tracer.isTracing();
}
}
没错,这就是整个链路追踪请求拦截器的核心之处,在构造方法中会接收三个参数,分别是:Tracer,HttpSpanInjector,HttpTraceKeysInjector。