zipkin 禁止_sleuth+zipkin系列之禁用URI调用链(八)

问题背景

在实际生产中,我们有可能会碰到有些接口,有些URI我们不想追踪,不想产生追踪数据,这个时候可以使用sleuth的追踪跳过的机制,默认sleuth已经禁用了如下URI,下面的URI访问系统的时候是不会被追踪的。

spring:sleuth:web:skip-pattern:"/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"

每个URI通过| 隔开,由于sleuth生成span的地方实在traceFilter中生成的,traceFilter默认是所有路径都会拦截的,只要拦截到了就会生成span,只不过这个span会不会上报,这就要看两个方面:

1.yaml中skip-pattern的配置,如果uri在这个里面配置了,那么就不会上报。

2.Sampler defaultTraceSampler()的配置,这个类里面有个isSample方法,如果返回false,那么就不会上报,后面会单独讲这一块。

效果演示

情景1

A 调用 B,再调用C

在系统A里面添加skip-pattern配置

spring:sleuth:web:skip-pattern:"/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream|/test"

skip-pattern的最后面添加了test, 也就是说uri为test的接口调用不追踪。 通过测试发现,A>B>C这一次调用链都没有被收集到。

情景2

A 调用 B,再调用C

在系统B里面添加skip-pattern配置,配置我就不贴上了。

测试结果: 调用链仅收集到A>B , 因为在B系统里面,请求B的URI过滤掉了,因此整个追踪链路在B系统就断掉了,C也不会上传他的span了。

原理解析

代码入口:org.springframework.cloud.sleuth.instrument.web.TraceFilter

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;

// 获取请求的URL String uri = this.urlPathHelper.getPathWithinApplication(request);

// 判断是否需要跳过,跳过的意思就是创建的span不上报 boolean skip = this.skipPattern.matcher(uri).matches()

|| Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME));

// 省略代码。。。。。

try {

// 创建span spanFromRequest = createSpan(request, skip, spanFromRequest, name);

filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest));

} catch (Throwable e) {

// 省略代码。。 } finally {

// 省略代码。。。。 }

当一个请求进入系统的时候,首先会经过TraceFilter , 进行span的创建和上报

步骤说明:

1.解析当前请求的URI

2.this.skipPattern.matcher(uri).matches(),这行代码的意思就是判断当前请求的URI是否在skip-pattern里面,匹配到了就返回true。

3.获取请求头里面X-B3-Sampled的值,当X-B3-Sampled==0的时候表示不上报,比如我们上面举的例子,系统A调用系统B的时候,系统A本身就已经

被限制了,说是不上报span, 那么他在请求系统B的时候,就会往请求头里面塞个值,X-B3-Sampled=0 , 这样系统A往下调用的那些系统都不会进行上报span了

4.上面三步,主要是确定可skip的值,true还是false,具体应用的时候还是在createSpan的时候会使用

createSpan

private Span createSpan(HttpServletRequest request,

boolean skip, Span spanFromRequest, String name) {

if (spanFromRequest != null) {

if (log.isDebugEnabled()) {

log.debug("Span has already been created - continuing with the previous one");

}

return spanFromRequest;

}

// 解析出请求里面的traceID,spanId ,parentId Span parent = spanExtractor().joinTrace(new HttpServletRequestTextMap(request));

// parent不为空,表示该请求是从上游系统调用过来的。 if (parent != null) {

if (log.isDebugEnabled()) {

log.debug("Found a parent span " + parent + " in the request");

}

// 添加请求tag进入span addRequestTagsForParentSpan(request, parent);

spanFromRequest = parent;

// 将spanFromRequest放入ThreadLocal中,并且记录日志 tracer().continueSpan(spanFromRequest);

if (parent.isRemote()) { // 是否是远程调用过来的。只要parent不为空,这个基本上就是true了 // 设置事件,server receive ,表示服务收到了,记录服务收到请求的时间 parent.logEvent(Span.SERVER_RECV);

}

request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);

if (log.isDebugEnabled()) {

log.debug("Parent span is " + parent + "");

}

} else {

// 如果skip==true, 那么设置当前span的上报器为NeverSampler.INSTANCE , 表示不上报span if (skip) {

spanFromRequest = tracer().createSpan(name, NeverSampler.INSTANCE);

}

else {

// skip==false , 设置他的上报器为new AlwaysSampler() , 表示本次的span是会上报的 String header = request.getHeader(Span.SPAN_FLAGS);

if (Span.SPAN_SAMPLED.equals(header)) {

spanFromRequest = tracer().createSpan(name, new AlwaysSampler());

} else {

spanFromRequest = tracer().createSpan(name);

}

}

// server receive时间,记录服务收到请求的时间 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;

}

上面的那一大串代码,跟本次分析的相关的,就是当parent等于null的那个else里面的代码, parent不等于空的时候,是否上报请求,跟parent一样,parent上报,那就上报。

当skip==true的时候,会将span的上报器设置为naverSampler,也就是从不上报span。

设置好Sampler之后,程序在哪里使用呢? 在createSpan里面会调用下面这个方法

private Span sampledSpan(Span span, Sampler sampler) {

if (!sampler.isSampled(span)) {

// Copy everything, except set exportable to false return Span.builder()

.begin(span.getBegin())

.traceIdHigh(span.getTraceIdHigh())

.traceId(span.getTraceId())

.spanId(span.getSpanId())

.name(span.getName())

.exportable(false).build();

}

return span;

}

调用sampler的isSampled方法,如果返回false,那么这是exportable = false

NeverSampler的isSampled至始至终都是返回false

public class NeverSampler implements Sampler {

public static final NeverSampler INSTANCE = new NeverSampler();

@Override

public boolean isSampled(Span span) {

return false;

}

}

设置好span的属性exportable 等于false之后,在最后closeSpan时,准备将其放入队列里面供异步线程(从队列里面取数据,发送给kafka)消费时。会对span的exportable属性做判断。

代码入口:org.springframework.cloud.sleuth.stream.StreamSpanReporter

@Override

public void report(Span span) {

Span spanToReport = span;

// 判断exportable 的属性值,只有等于true的时候才会进入if结构 if (spanToReport.isExportable()) {

try {

if (this.environment != null) {

processLogs(spanToReport);

}

// 调用span的调整器,对span做最后的调整 for (SpanAdjuster adjuster : this.spanAdjusters) {

spanToReport = adjuster.adjust(spanToReport);

}

// 加入队列供异步线程消费 this.queue.add(spanToReport);

} catch (Exception e) {

this.spanMetricReporter.incrementDroppedSpans(1);

if (log.isDebugEnabled()) {

log.debug("The span " + spanToReport + " will not be sent to Zipkin due to [" + e + "]");

}

}

} else {

// 等于false,不做任何处理 if (log.isDebugEnabled()) {

log.debug("The span " + spanToReport + " will not be sent to Zipkin due to sampling");

}

}

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值