编程知识 cdmana.com

Spring cloud gateway has no link information. I'm stupid (Part 2)

This series is I TM People are stupid The fifth issue of the series [ Over your face ], Past highlights :

Spring Cloud Gateway  No link information , I  TM  People are stupid ( Next )

This article deals with the underlying design and principles , And problem location and possible problem points , Very deep , Length is longer than the , So it is divided into three parts :

  • On : A brief description of the problem and Spring Cloud Gateway Basic structure and process and underlying principles
  • in :Spring Cloud Sleuth How to be in Spring Cloud Gateway Join link tracking and why this problem occurs
  • Next : existing Spring Cloud Sleuth Performance problems caused by non-invasive design , Other possible problems , And how to solve it

Spring Cloud Gateway Other points that may lose link information

After the previous analysis , We can see that , Not just here , There are other places that can lead to Spring Cloud Sleuth The link tracking information disappeared , Here are some common examples :

1. stay GatewayFilter Asynchronous execution of certain tasks is specified in , Due to thread switching , And this time may Span It's over , So there is no link information , for example

@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {    return chain.filter(exchange).publishOn(Schedulers.parallel()).doOnSuccess(o -> {            // There is no link information here             log.info("success");    });}

2. take GatewayFilter To continue the link chain.filter(exchange) Put it into asynchronous tasks to execute , above AdaptCachedBodyGlobalFilter This is the case , This will lead to later GatewayFilter There is no link information , for example :

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

Java Concurrent programming model and Project Reactor Conflict thinking of programming model

Java Many frameworks in , Are used to ThreadLocal, Or by Thread To identify uniqueness . for example :

  • In the log framework MDC, It's usually ThreadLocal Realization .
  • All locks 、 be based on AQS Data structure of , It's all through Thread To uniquely identify who acquired the lock .
  • Data structures such as distributed locks , through Thread To uniquely identify who acquired the lock , for example Redisson Medium distributed Redis Lock implementation .

But put it in Project Reactor Programming model , This seems out of place , because Project Reactor Asynchronous responsive programming is not fixed threads , There is no guarantee that the submit task and callback can be in the same thread , therefore ThreadLocal The semantics of is difficult to hold here .Project Reactor Although benchmarking is provided ThreadLocal Of Context, But the mainstream framework is not compatible with this Context, So here it is Spring Cloud Sleuth Bonding these links brings great difficulties to tracking , because MDC It's a ThreadLocal Of Map Realization , Not based on Context Of Map. This requires Spring Cloud Sleuth At the beginning of the subscription , You need to put the link information into MDC, At the same time, it is also necessary to ensure that threads are not switched at runtime .

Run without switching threads , This actually limits Project Reactor Flexible scheduling of , There is some performance loss . We actually want to try to add link tracking information , You don't have to force running without switching threads . however Spring Cloud Sleuth yes Non-invasive design , It's hard to achieve this . But for the use of our own business , We can Customize some programming specifications , To ensure that the code you write does not lose link information .

Improve our programming specification

First , We Customize Mono and Flux Our factory

public Subscriber encapsulation , take reactor Subscriber All key interfaces , Check whether there is link information in the current context , namely Span, If not, wrap it , If so, just execute it directly .

public class TracedCoreSubscriber<T> implements Subscriber<T>{    private final Subscriber<T> delegate;    private final Tracer tracer;    private final CurrentTraceContext currentTraceContext;    private final Span span;    TracedCoreSubscriber(Subscriber<T> delegate, Tracer tracer, CurrentTraceContext currentTraceContext, Span span) {        this.delegate = delegate;        this.tracer = tracer;        this.currentTraceContext = currentTraceContext;        this.span = span;    }    @Override    public void onSubscribe(Subscription s) {        executeWithinScope(() -> {            delegate.onSubscribe(s);        });    }    @Override    public void onError(Throwable t) {        executeWithinScope(() -> {            delegate.onError(t);        });    }    @Override    public void onComplete() {        executeWithinScope(() -> {            delegate.onComplete();        });    }    @Override    public void onNext(T o) {        executeWithinScope(() -> {            delegate.onNext(o);        });    }    private void executeWithinScope(Runnable runnable) {        // If there is currently no link information , Mandatory package         if (tracer.currentSpan() == null) {            try (CurrentTraceContext.Scope scope = this.currentTraceContext.maybeScope(this.span.context())) {                runnable.run();            }        } else {            // If there is currently link information , Directly             runnable.run();        }    }}

Then define all... Separately Flux Agent for TracedFlux, And all Mono Agent for TracedMono, In fact, in the subscribe When , use TracedCoreSubscriber Package the incoming CoreSubscriber:

public class TracedFlux<T> extends Flux<T> {    private final Flux<T> delegate;    private final Tracer tracer;    private final CurrentTraceContext currentTraceContext;    private final Span span;    TracedFlux(Flux<T> delegate, Tracer tracer, CurrentTraceContext currentTraceContext, Span span) {        this.delegate = delegate;        this.tracer = tracer;        this.currentTraceContext = currentTraceContext;        this.span = span;    }    @Override    public void subscribe(CoreSubscriber<? super T> actual) {        delegate.subscribe(new TracedCoreSubscriber(actual, tracer, currentTraceContext, span));    }}public class TracedMono<T> extends Mono<T> {    private final Mono<T> delegate;    private final Tracer tracer;    private final CurrentTraceContext currentTraceContext;    private final Span span;    TracedMono(Mono<T> delegate, Tracer tracer, CurrentTraceContext currentTraceContext, Span span) {        this.delegate = delegate;        this.tracer = tracer;        this.currentTraceContext = currentTraceContext;        this.span = span;    }    @Override    public void subscribe(CoreSubscriber<? super T> actual) {        delegate.subscribe(new TracedCoreSubscriber(actual, tracer, currentTraceContext, span));    }}

Define factory class , Use request ServerWebExchange And primitive Flux establish TracedFlux, And use requests ServerWebExchange And primitive Mono establish TracedMono, also Span It's through Attributes Acquired , According to the above source code analysis, we know , This Attribute It's through TraceWebFilter Put in Attributes Of . Because we are only GatewayFilter Use in , It must be TraceWebFilter after So this Attribute There must be .

@Componentpublic class TracedPublisherFactory {    protected static final String TRACE_REQUEST_ATTR = Span.class.getName();    @Autowired    private Tracer tracer;    @Autowired    private CurrentTraceContext currentTraceContext;    public <T> Flux<T> getTracedFlux(Flux<T> publisher, ServerWebExchange exchange) {        return new TracedFlux<>(publisher, tracer, currentTraceContext, (Span) exchange.getAttributes().get(TRACE_REQUEST_ATTR));    }    public <T> Mono<T> getTracedMono(Mono<T> publisher, ServerWebExchange exchange) {        return new TracedMono<>(publisher, tracer, currentTraceContext, (Span) exchange.getAttributes().get(TRACE_REQUEST_ATTR));    }}

then , Our rules :1. be-all GatewayFilter, We need to inherit our custom abstract class , This abstract class is just filter The result of using TracedPublisherFactory Of getTracedMono To encapsulate a layer of TracedMono, With GlobalFilter As an example :

public abstract class AbstractTracedFilter implements GlobalFilter {    @Autowired    protected TracedPublisherFactory tracedPublisherFactory;    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        return tracedPublisherFactory.getTracedMono(traced(exchange, chain), exchange);    }    protected abstract Mono<Void> traced(ServerWebExchange exchange, GatewayFilterChain chain);}

2. GatewayFilter New generation of Flux perhaps Mono, Unified use TracedPublisherFactory Another layer of encapsulation .

3. about AdaptCachedBodyGlobalFilter Read Request Body Resulting link loss , I asked the community Pull Request: fix #2004 Span is not terminated properly in Spring Cloud Gateway, You can refer to . It can also be here Filter Before I will Request Body Use TracedPublisherFactory Encapsulate and solve .

WeChat search “ My programming meow ” Official account , Once a day , Easy to upgrade technology , Capture all kinds of offer

Spring Cloud Gateway  No link information , I  TM  People are stupid ( Next )

版权声明
本文为[Dry goods are full]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/10/20211002145637050R.html

Scroll to Top