本篇文章给大家分享的是有关springcloud-sleuth源码怎么解析2-TraceFilter,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
基于spring cloud 1.2.1版本
将分析server接收一个请求,trace究竟是怎么处理的。
首先介绍下一个span的生命周期:
start
创建一个span,这时候会记录创建时间以及设置span name。如果当前线程已经存在一个span,则创建的新的span是childSpan。
// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.createSpan("calculateTax");
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
newSpan.logEvent("taxCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin
this.tracer.close(newSpan);
}
close
如果一个span已经准备好将自身发送到zipkin server或者其他collect的时候,便会调用close方法,记录endTime,上报span,然后从当前线程中移除span。
continue
将一个新的span设置到当前线程中,成为continueSpan。该方法作用是传递不同线程之间的span。对于一些异步处理代码,就需要 将span设置到异步处理线程中了。
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
detach
从当前线程中删除span,剥离出去,但不会stop或者close span。一般跟continue方法结合使用。
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
continuedSpan.logEvent("taxCalculated");
} finally {
// Once done remember to detach the span. That way you'll
// safely remove it from the current thread without closing it
this.tracer.detach(continuedSpan);
}
create with explicit parent
创建一个span并且指定它的parent span。该方法的使用场景是在当前线程中想创建一个span,但parent span存在另一个线程当中,这样你 就可以获取到parent span,明确指定该span为要创建的span的parent。
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("commissionValue", commissionValue);
// ...
// You can log an event on a span
newSpan.logEvent("commissionCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin. The tags and events set on the
// newSpan will not be present on the parent
this.tracer.close(newSpan);
}
下面介绍TraceFilter过滤器,它拦截所有请求,我们直接看它的doFilter方法。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) {
throw new ServletException("Filter just supports HTTP requests");
}
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//首先从request获取到uri
String uri = this.urlPathHelper.getPathWithinApplication(request);
//判断是否忽略本次trace。根据两个条件判断是否忽略本次trace:
//A、根据skipPattern判断此uri是否是skip uri,如果是返回true
//B、从request、response的head中获取X-B3-Sampled属性,如果值为0则返回true,即不进行采样
boolean skip = this.skipPattern.matcher(uri).matches()
|| Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME));
//从request中getAttribute span。表示在一个request中,如果发生了转发那直接可以在request中获取span,不需要重新生成。
Span spanFromRequest = getSpanFromAttribute(request);
if (spanFromRequest != null) {
//不为空的话则continueSpan,下面看看continueSpan方法。
continueSpan(request, spanFromRequest);
}
if (log.isDebugEnabled()) {
log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]");
}
// in case of a response with exception status a exception controller will close the span
//正如上面的注释所说,这是请求出现异常时,跳转到异常controller时处理逻辑,然后关闭span立即结束filter。
if (!httpStatusSuccessful(response) && isSpanContinued(request)) {
Span parentSpan = parentSpan(spanFromRequest);
processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest);
return;
}
//设置span name
String name = HTTP_COMPONENT + ":" + uri;
Throwable exception = null;
try {
//根据request创建span,下面分析createSpan代码。
spanFromRequest = createSpan(request, skip, spanFromRequest, name);
//这里会触发springmvc的trace拦截器TraceHandlerInterceptor的一些方法,下一章会分析。
filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest));
} catch (Throwable e) {
exception = e;
this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e));
throw e;
} finally {
//对于异步request则不进行处理
if (isAsyncStarted(request) || request.isAsyncStarted()) {
if (log.isDebugEnabled()) {
log.debug("The span " + spanFromRequest + " will get detached by a HandleInterceptor");
}
// TODO: how to deal with response annotations and async?
return;
}
//如果该请求没有被spring mvc的trace拦截器拦截到,则会人工的生成一个lc类型span,作为spanFromRequest的child span,
//弥补springmvc的trace拦截器缺失的部分,这样能保证对于zipkin来说是一个合理的调用链路。
spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip);
//分离获取关闭span,最后来分析下该方法
detachOrCloseSpans(request, response, spanFromRequest, exception);
}
}
private void continueSpan(HttpServletRequest request, Span spanFromRequest) {
//tracer的continueSpan方法的作用是将新的span设置到当前线程中。
//比如span a 在线程X中,span b在线程Y中,现在上下文处于线程b中,然后操作continueSpan(a),
//即将线程Y中的span b替换成span a,然后span a中的saved span属性设置成span b,即设置当前线程span之前的current span。
//下面分析下continueSpan方法。
this.tracer.continueSpan(spanFromRequest);
request.setAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true");
if (log.isDebugEnabled()) {
log.debug("There has already been a span in the request " + spanFromRequest);
}
}
public Span continueSpan(Span span) {
if (span != null) {
//日志组件,主要用于MDC输出的
this.spanLogger.logContinuedSpan(span);
} else {
return null;
}
//createContinuedSpan方法第一个参数span是request中保存的span,或者其他上下文传递下来的。
//第二个span,SpanContextHolder.getCurrentSpan()是从ThreadLocal获取当前线程中的span。
//下面看下createContinuedSpan方法
Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan());
//将新的span保存到当前线程中
SpanContextHolder.setCurrentSpan(newSpan);
return newSpan;
}
//如果当前线程span为空且被传递过来的span的saved span属性不为空,则设置新的saved span为被传递过来的span的saved span,
//否则saved span使用当前线程中的span。
private Span createContinuedSpan(Span span, Span saved) {
if (saved == null && span.getSavedSpan() != null) {
saved = span.getSavedSpan();
}
return new Span(span, saved);
}
对于new Span(span, saved)这种构造span的形式我们来分析下saved span有何作用:
saved的span是在创建该新的span之前就已经存在当前线程中的span。有两种情况会调用该api:
正如之前所说的,span从线程X复制到线程Y中
当前线程中已有span,然后创建child span,child span的saved span就是parent span
/**
* Creates a span and appends it as the current request's attribute
*/
private Span createSpan(HttpServletRequest request,
boolean skip, Span spanFromRequest, String name) {
//如果spanFromRequest不为空,即发生了转发等情况,那直接返回,不需要创建新的span。
if (spanFromRequest != null) {
if (log.isDebugEnabled()) {
log.debug("Span has already been created - continuing with the previous one");
}
return spanFromRequest;
}
//抽取request中的header、path等信息创建span,下面将分析joinTrace方法。
Span parent = this.spanExtractor.joinTrace(new HttpServletRequestTextMap(request));
//如果成功从request中提取了trace信息,生成了parent
if (parent != null) {
if (log.isDebugEnabled()) {
log.debug("Found a parent span " + parent + " in the request");
}
//正常tags中信息不会在server端添加,而是在client端添加tags。
//但是如果request header中不存在span name信息,说明client没有生成span信息,导致span信息不完整,
//那么就需要在server端生成tags。
addRequestTagsForParentSpan(request, parent);
spanFromRequest = parent;
//将生成的span保存到当前线程中,详情看DefaultTracer.continueSpan方法,前面已分析。
this.tracer.continueSpan(spanFromRequest);
//判断该span是否跨进程,是的话会加SR标识,即span生命周期中server recive阶段
if (parent.isRemote()) {
parent.logEvent(Span.SERVER_RECV);
}
//将span保存到request中。
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
if (log.isDebugEnabled()) {
log.debug("Parent span is " + parent + "");
}
} else {
//parent为空需要生成新的span
//如果skip为true,则会生成一个不采样标识的span
if (skip) {
spanFromRequest = this.tracer.createSpan(name, NeverSampler.INSTANCE);
}
else {
//根据request header中的采样标识判断直接采样,还是根据本地设置的采样器判断是否采样
//下面分析下DefaultTracer.createSpan方法
String header = request.getHeader(Span.SPAN_FLAGS);
if (Span.SPAN_SAMPLED.equals(header)) {
spanFromRequest = this.tracer.createSpan(name, new AlwaysSampler());
} else {
spanFromRequest = this.tracer.createSpan(name);
}
}
spanFromRequest.logEvent(Span.SERVER_RECV);
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
if (log.isDebugEnabled()) {
log.debug("No parent span present - creating a new span");
}
}
return spanFromRequest;
}
public Span joinTrace(SpanTextMap textMap) {
//carrier中保存request header、uri信息
Map<String, String> carrier = TextMapUtil.asMap(textMap);
//判断header中是否有Span.SPAN_FLAGS标识,且值为1,即需要采样。
boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
if (debug) {
// we're only generating Trace ID since if there's no Span ID will assume
// that it's equal to Trace ID
//header中如果不存在trace id,则生成一个。
generateIdIfMissing(carrier, Span.TRACE_ID_NAME);
} else if (carrier.get(Span.TRACE_ID_NAME) == null) {
// can't build a Span without trace id
//header中没有trace id则直接返回null,不能从reqeust中提取信息构建span
return null;
}
try {
String uri = carrier.get(URI_HEADER);
//根据uri判断是够skip
boolean skip = this.skipPattern.matcher(uri).matches()
|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
//如果header中span id为空则根据trace id生成一个,否则直接返回header中的span id。
long spanId = spanId(carrier);
//构建span
return buildParentSpan(carrier, uri, skip, spanId);
} catch (Exception e) {
log.error("Exception occurred while trying to extract span from carrier", e);
return null;
}
}
public Span createSpan(String name, Sampler sampler) {
String shortenedName = SpanNameUtil.shorten(name);
Span span;
//如果本地即当前线程已经存在span,则创建child span,当前线程中的span为parent span
//如果不存在span,则创建一个完全新的span
if (isTracing()) {
span = createChild(getCurrentSpan(), shortenedName);
}
else {
long id = createId();
span = Span.builder().name(shortenedName)
.traceIdHigh(this.traceId128 ? createId() : 0L)
.traceId(id)
.spanId(id).build();
if (sampler == null) {
sampler = this.defaultSampler;
}
span = sampledSpan(span, sampler);
this.spanLogger.logStartedSpan(null, span);
}
//将创建的span保存在当前线程
return continueSpan(span);
}
private void detachOrCloseSpans(HttpServletRequest request,
HttpServletResponse response, Span spanFromRequest, Throwable exception) {
Span span = spanFromRequest;
if (span != null) {
//添加response status到tags中
addResponseTags(response, exception);
//这里判断span中savedSpan不为空且请求被TraceHandlerInterceptor拦截器拦截处理过则上报savedSpan信息
//这里上报savedSpan,我的理解是traceFilter在filter一个request的时候会创建第一个parentSpan,
//期间不会创建childSpan,但进入springmvc handler处理期间可能会创建一些childSpan,然后设置为current span,
//但这种span不是traceFilter关注的,它只关注server reciver时即刚接收到请求创建的span。
if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) {
//首先停止span的clock,即记录结束时间,算下开始时间与结束时间的duration。
//然后记录server send event,表明作为server端,什么时候响应请求返回结果的。
//最后上报span,比如上报到zipkin或者打印log,这就取决于初始化哪种spanReporter的了。
recordParentSpan(span.getSavedSpan());
} else if (!requestHasAlreadyBeenHandled(request)) {
//如果该请求没有被TraceHandlerInterceptor拦截器拦截处理,则直接把span从当前线程中移除,停止span的clock,然后上报
//这里的span可能是createSpanIfRequestNotHandled创建的span。
//close返回savedSpan,即parentSpan
span = this.tracer.close(span);
}
//上报parentSpan
recordParentSpan(span);
// in case of a response with exception status will close the span when exception dispatch is handled
// checking if tracing is in progress due to async / different order of view controller processing
if (httpStatusSuccessful(response) && this.tracer.isTracing()) {
if (log.isDebugEnabled()) {
log.debug("Closing the span " + span + " since the response was successful");
}
this.tracer.close(span);
} else if (errorAlreadyHandled(request) && this.tracer.isTracing()) {
if (log.isDebugEnabled()) {
log.debug(
"Won't detach the span " + span + " since error has already been handled");
}
} else if (shouldCloseSpan(request) && this.tracer.isTracing() && stillTracingCurrentSapn(span)) {
if (log.isDebugEnabled()) {
log.debug(
"Will close span " + span + " since some component marked it for closure");
}
this.tracer.close(span);
} else if (this.tracer.isTracing()) {
if (log.isDebugEnabled()) {
log.debug("Detaching the span " + span + " since the response was unsuccessful");
}
this.tracer.detach(span);
}
}
}
以上就是springcloud-sleuth源码怎么解析2-TraceFilter,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/xiaominmin/blog/3095993