Spring cloud upgrade 2020.0.x version - 44. Design to avoid link information loss

Code address of this series: https://github.com/JoJoTec/spring-cloud-parent

In this section, we first analyze some other points of Spring Cloud Gateway that may lose link information, then make some designs that can avoid link information loss, and then implement some customized globalfilters we need based on this design

Other points of Spring Cloud Gateway that may lose link information

Through the previous analysis, we can see that not only here, but also other places will lead to the disappearance of the link tracking information of Spring Cloud Sleuth. Here are some common examples:

1. Asynchronous execution of some tasks is specified in the GatewayFilter. Because the thread is switched and the Span may have ended at this time, there is no link information, such as:

@Override
public 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. Put the chain.filter(exchange) of the continuing link in the GatewayFilter into the asynchronous task for execution. This is the case with the AdaptCachedBodyGlobalFilter above. This will lead to no link information for subsequent gatewayfilters, for example:

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

Conflict between Java Concurrent Programming Model and Project Reactor programming model

Many frameworks in Java use ThreadLocal or use Thread to identify uniqueness. For example:

  • MDC in the logging framework is generally implemented by ThreadLocal.
  • All locks and AQS based data structures uniquely identify who has obtained the lock through the Thread attribute.
  • Distributed locks and other data structures also uniquely identify who has obtained the lock through the Thread attribute, such as the implementation of distributed Redis locks in Redisson.

However, when it comes to the Project Reactor programming model, it seems out of place. Because the asynchronous responsive programming of Project Reactor does not have a fixed thread, it is impossible to ensure that the submitted task and callback can be on the same thread, so the semantics of ThreadLocal is difficult to establish here. Although Project Reactor provides a Context for benchmarking ThreadLocal, the mainstream framework is not compatible with this Context, so it is very difficult for Spring Cloud Sleuth to bond these link tracking, because MDC is a ThreadLocal Map implementation rather than a Context based Map. This requires Spring Cloud Sleuth to put the link information into the MDC at the beginning of subscription, and ensure that threads are not switched at runtime.

Running without switching threads actually limits the flexible scheduling of Project Reactor, resulting in some performance losses. In fact, we want to try our best, even if the link tracking information is added, we don't have to force the running and do not switch threads. However, Spring Cloud Sleuth is a non intrusive design, which is difficult to achieve. However, 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.

Where can I get the currently requested Span

Span is the link information core of Spring Cloud Sleuth. In the previous source code analysis, we know that in the WebFilter of the entry, TraceWebFilter generates a span and puts it into the attributes of ServerWebExchange in the abstract HTTP request response:

TraceWebFilter.java

protected static final String TRACE_REQUEST_ATTR = Span.class.getName();
private Span findOrCreateSpan(Context c) {
	Span span;
	AssertingSpan assertingSpan = null;
	//If there is a Span in the context of the current Reactor, use this 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 {
	    //If the current request itself contains span information, start a new sub span with this 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);
			}
		}
		//Get span from the current context
		span = this.spanFromContextRetriever.findSpan(c);
		//Generate a new one if you don't get it
		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);
		//Put span into the attributes of 'ServerWebExchange'
		this.exchange.getAttributes().put(TRACE_REQUEST_ATTR, assertingSpan);
	}
	if (assertingSpan == null) {
		assertingSpan = SleuthWebSpan.WEB_FILTER_SPAN.wrap(span);
	}
	return assertingSpan;
}

This shows that when writing GlobalFilter, we can obtain the Span of current link information by reading the attributes of ServerWebExchange. But TRACE_REQUEST_ATTR is protected. We can expose it in the following tool class.

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;
	
	//Just to expose trace of TraceWebFilter_ REQUEST_ Tool classes used by attr
	private TraceWebFilterUtil(Tracer tracer, HttpServerHandler handler, CurrentTraceContext currentTraceContext) {
		super(tracer, handler, currentTraceContext);
	}
}

In the next section, we will continue to explain the design to avoid link information loss, mainly focusing on how to ensure that each GlobalFilter can maintain link information after obtaining the existing Span.

WeChat search "my programming meow" attention to the official account, daily brush, easy to upgrade technology, and capture all kinds of offer:

Tags: Spring Cloud

Posted on Sun, 28 Nov 2021 23:32:25 -0500 by nodi