spring cloud sleuth的ZUUL源码解析
一. 整体流程
TracingFilter
- doFilter 拦截servlet请求进行创建span,之后向下传递执行其他filter,最后结束trace
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = servlet.httpServletResponse(response);
// Prevent duplicate spans for the same request
TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());
if (context != null) {
// A forwarded request might end up on another thread, so make sure it is scoped
Scope scope = currentTraceContext.maybeScope(context);
try {
chain.doFilter(request, response);
} finally {
scope.close();
}
return;
}
// 创建server端span
Span span = handler.handleReceive(new HttpServerRequest(httpRequest));
// Add attributes for explicit access to customization or span context
request.setAttribute(SpanCustomizer.class.getName(), span.customizer());
request.setAttribute(TraceContext.class.getName(), span.context());
Throwable error = null;
Scope scope = currentTraceContext.newScope(span.context());
try {
// any downstream code can see Tracer.currentSpan() or use Tracer.currentSpanCustomizer()
// 将请求转发给过滤器链下一个filter
chain.doFilter(httpRequest, httpResponse);
} catch (IOException | ServletException | RuntimeException | Error e) {
error = e;
throw e;
} finally {
scope.close();
// 结束trace
if (servlet.isAsync(httpRequest)) { // we don't have the actual response, handle later
servlet.handleAsync(handler, httpRequest, httpResponse, span);
} else { // we have a synchronous response, so we can finish the span
// handleSend中调用HttpServerHandler.handleSend, 会判断当前上下文是否还有没有结束的span,如果没有了,则不作任何处理。如果有,则结束该span
handler.handleSend(servlet.httpServerResponse(httpRequest, httpResponse), error, span);
}
}
}
HttpServerHandler.handleSend
public void handleSend(@Nullable Resp response, @Nullable Throwable error, Span span) {
if (response instanceof HttpServerResponse) {
HttpServerResponse.Adapter adapter =
new HttpServerResponse.Adapter((HttpServerResponse) response);
handleFinish(adapter, adapter.unwrapped, error, span);
} else {
handleFinish(adapter, response, error, span);
}
}
RealSpan.finish
span 结束逻辑
@Override public void finish(long timestamp) {
if (!pendingSpans.remove(context)) return;
synchronized (state) {
// 设置结束时间戳
state.finishTimestamp(timestamp);
}
// 添加tags
finishedSpanHandler.handle(context, state);
}
HttpHandler.finishInNullScope
关闭span
/** Clears the scope to prevent remote reporters from accidentally tracing */
void finishInNullScope(Span span, long timestamp) {
Scope ws = currentTraceContext.maybeScope(null);
try {
if (timestamp == 0L) {
span.finish();
} else {
span.finish(timestamp);
}
} finally {
ws.close();
}
}
ZipkinFinishedSpanHandler.handle
Reporter上报span
@Override public boolean handle(TraceContext context, MutableSpan span) {
if (!alwaysReportSpans && !Boolean.TRUE.equals(context.sampled())) return true;
Span.Builder builderWithContextData = Span.newBuilder()
.traceId(context.traceIdString())
.parentId(context.parentIdString())
.id(context.spanIdString());
if (context.debug()) builderWithContextData.debug(true);
converter.convert(span, builderWithContextData);
spanReporter.report(builderWithContextData.build());
return true;
}
TracingProtocolExec
httpclient 拦截器,用于创建client span,结束client span
@Override public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
HttpClientContext clientContext, HttpExecutionAware execAware)
throws IOException, HttpException {
Span span = handler.nextSpan(new HttpClientRequest(request));
HttpClientResponse response = null;
Throwable error = null;
try (SpanInScope ws = tracer.withSpanInScope(span)) {
CloseableHttpResponse result = protocolExec.execute(route, request, clientContext, execAware);
response = new HttpClientResponse(result);
return result;
} catch (IOException | HttpException | RuntimeException | Error e) {
error = e;
throw e;
} finally {
// 结束client span
handler.handleReceive(response, error, span);
}
}
TracingMainExec
真正的执行http请求,网关转发到后端服务,默认使用httpclient,可选okHttpclient等。
@Override public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
HttpClientContext context, HttpExecutionAware execAware)
throws IOException, HttpException {
Span span = tracer.currentSpan();
if (span != null) {
handler.handleSend(new TracingProtocolExec.HttpClientRequest(request), span);
}
CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);
if (span != null) {
if (isRemote(context, span)) {
if (serverName != null) span.remoteServiceName(serverName);
// 为span设置远程remoteIpAndPort
HttpAdapter.parseTargetAddress(request, span);
} else {
span.kind(null); // clear as cache hit
}
}
return response;
}
TracePostZuulFilter
zuul拦截器,type=post, order=0,所以在转发请求之后最先执行
@Override
public Object run() {
if (log.isDebugEnabled()) {
log.debug("Marking current span as handled");
}
HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
Throwable exception = RequestContext.getCurrentContext().getThrowable();
Span currentSpan = this.tracer.currentSpan();
// 结束server span
this.handler.handleSend(response, exception, currentSpan);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + currentSpan);
}
return null;
}
后续
那么为什么sleuth能拦截httpclient呢。因为这个配置,默认每次使用HttpClientBuilder都会返回TracingHttpClientBuilder对象。而brave对TracingHttpClientBuilder进行了拦截处理、
项目推荐
IT-CLOUD :IT服务管理平台,集成基础服务,中间件服务,监控告警服务等。
IT-CLOUD-ACTIVITI6 :Activiti教程源码。博文在本CSDN Activiti系列中。
IT-CLOUD-ELASTICSEARCH :elasticsearch教程源码。博文在本CSDN elasticsearch系列中。
IT-CLOUD-KAFKA :spring整合kafka教程源码。博文在本CSDN kafka系列中。
IT-CLOUD-KAFKA-CLIENT :kafka client教程源码。博文在本CSDN kafka系列中。开源项目,持续更新中,喜欢请 Star~