本系列代码地址:https://github.com/JoJoTec/sp...

咱们在这一节首先剖析下 Spring Cloud Gateway 一些其余可能失落链路信息的点,之后来做一些能够防止链路信息失落的设计,之后基于这个设计去实现咱们须要的一些定制化的 GlobalFilter

Spring Cloud Gateway 其余的可能失落链路信息的点

通过后面的剖析,咱们能够看出,不止这里,还有其余中央会导致 Spring Cloud Sleuth 的链路追踪信息隐没,这里举几个大家常见的例子:

1.在 GatewayFilter 中指定了异步执行某些工作,因为线程切换了,并且这时候可能 Span 曾经完结了,所以没有链路信息,例如

@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {    return chain.filter(exchange).publishOn(Schedulers.parallel()).doOnSuccess(o -> {            //这里就没有链路信息了            log.info("success");    });}

2.将 GatewayFilter 中持续链路的 chain.filter(exchange) 放到了异步工作中执行,下面的 AdaptCachedBodyGlobalFilter 就属于这种状况,这样会导致之后的 GatewayFilter 都没有链路信息,例如:

@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {    return Mono.delay(Duration.ofSeconds(1)).then(chain.filter(exchange));}

Java 并发编程模型与 Project Reactor 编程模型的抵触思考

Java 中的很多框架,都用到了 ThreadLocal,或者通过 Thread 来标识唯一性。例如:

  • 日志框架中的 MDC,个别都是 ThreadLocal 实现。
  • 所有的锁、基于 AQS 的数据结构,都是通过 Thread 的属性来惟一标识谁获取到了锁的。
  • 分布式锁等数据结构,也是通过 Thread 的属性来惟一标识谁获取到了锁的,例如 Redisson 中分布式 Redis 锁的实现。

然而放到 Project Reactor 编程模型,这就显得心心相印了,因为 Project Reactor 异步响应式编程就是不固定线程,没法保障提交工作和回调能在同一个线程,所以 ThreadLocal 的语义在这里很难成立。Project Reactor 尽管提供了对标 ThreadLocal 的 Context,然而支流框架还没有兼容这个 Context,所以给 Spring Cloud Sleuth 粘合这些链路追踪带来了很大艰难,因为 MDC 是一个 ThreadLocal 的 Map 实现,而不是基于 Context 的 Map。这就须要 Spring Cloud Sleuth 在订阅一开始,就须要将链路信息放入 MDC,同时还须要保障运行时不切换线程。

运行不切换线程,这样其实限度了 Project Reactor 的灵便调度,是有一些性能损失的。咱们其实想尽量就算退出了链路追踪信息,也不必强制运行不切换线程。然而 Spring Cloud Sleuth 是非侵入式设计,很难实现这一点。然而对于咱们本人业务的应用,咱们能够定制一些编程标准,来保障大家写的代码不失落链路信息

能够从哪里获取以后申请的 Span

Spring Cloud Sleuth 的链路信息外围即 Span,在之前的源码剖析中,咱们晓得,在入口的 WebFilter 中,TraceWebFilter 生成 Span 并将其放入本次 HTTP 申请响应形象的 ServerWebExchange 的 attributes 中:

TraceWebFilter.java

protected static final String TRACE_REQUEST_ATTR = Span.class.getName();private Span findOrCreateSpan(Context c) {    Span span;    AssertingSpan assertingSpan = null;    //如果以后 Reactor 的上下文中有 Span,就用这个 Span    if (c.hasKey(Span.class)) {        Span parent = c.get(Span.class);        try (Tracer.SpanInScope spanInScope = this.tracer.withSpan(parent)) {            span = this.tracer.nextSpan();        }        if (log.isDebugEnabled()) {            log.debug("Found span in reactor context" + span);        }    }    else {        //如果以后申请中自身蕴含 span 信息,就用这个 span 启动一个新的子 span        if (this.span != null) {            try (Tracer.SpanInScope spanInScope = this.tracer.withSpan(this.span)) {                span = this.tracer.nextSpan();            }            if (log.isDebugEnabled()) {                log.debug("Found span in attribute " + span);            }        }        //从以后所处的上下文中获取 span        span = this.spanFromContextRetriever.findSpan(c);        //没获取到就新生成一个        if (this.span == null && span == null) {            span = this.handler.handleReceive(new WrappedRequest(this.exchange.getRequest()));            if (log.isDebugEnabled()) {                log.debug("Handled receive of span " + span);            }        }        else if (log.isDebugEnabled()) {            log.debug("Found tracer specific span in reactor context [" + span + "]");        }        assertingSpan = SleuthWebSpan.WEB_FILTER_SPAN.wrap(span);        //将 span 放入 `ServerWebExchange` 的 attributes 中        this.exchange.getAttributes().put(TRACE_REQUEST_ATTR, assertingSpan);    }    if (assertingSpan == null) {        assertingSpan = SleuthWebSpan.WEB_FILTER_SPAN.wrap(span);    }    return assertingSpan;}

这样能够看出,咱们在编写 GlobalFilter 的时候能够通过读取 ServerWebExchange 的 attributes 获取以后链路信息的 Span。然而 TRACE_REQUEST_ATTR 是 protected 的,咱们能够上面这个工具类将其裸露进去。

package com.github.jojotech.spring.cloud.apigateway.common;import org.springframework.cloud.sleuth.CurrentTraceContext;import org.springframework.cloud.sleuth.Tracer;import org.springframework.cloud.sleuth.http.HttpServerHandler;import org.springframework.cloud.sleuth.instrument.web.TraceWebFilter;public class TraceWebFilterUtil extends TraceWebFilter {    public static final String TRACE_REQUEST_ATTR = TraceWebFilter.TRACE_REQUEST_ATTR;        //仅仅为了裸露 TraceWebFilter 的 TRACE_REQUEST_ATTR 应用的工具类    private TraceWebFilterUtil(Tracer tracer, HttpServerHandler handler, CurrentTraceContext currentTraceContext) {        super(tracer, handler, currentTraceContext);    }}

下一节,咱们将持续解说防止链路信息失落做的设计,次要针对获取到现有 Span 之后,如何保障每个 GlobalFilter 都能放弃链路信息。

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种offer