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 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. First, we customize the core Publisher of Reactor, that is, the Mono and Flux factories, and encapsulate the link information to ensure that the Mono and Flux generated by this factory will maintain the link information no matter how they are spliced between the Mono and Flux generated by this factory:

Customize Mono and Flux factories

Public Subscriber encapsulation checks whether the current context has link information, i.e. Span, on all key interfaces of reactor Subscriber. If not, wrap it. If so, 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 no link information at present, the package is forced
        if (tracer.currentSpan() == null) {
            try (CurrentTraceContext.Scope scope = this.currentTraceContext.maybeScope(this.span.context())) {
                runnable.run();
            }
        } else {
            //If there is currently link information, execute it directly
            runnable.run();
        }
    }
}

Then define TracedFlux for all Flux agents and TracedMono for all Mono agents respectively. In fact, when subscribing, wrap the incoming CoreSubscriber with tracedcore subscriber:

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 the factory class, create TracedFlux using the request ServerWebExchange and the original Flux, and create TracedMono using the request ServerWebExchange and the original Mono, and Span is obtained through Attributes. According to the source code analysis above, we know that this Attribute is put into Attributes through TraceWebFilter. Because we only use it in GatewayFilter, it must be after TraceWebFilter, so this Attribute must exist.

@Component
public 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));
    }
}

Public abstract GlobalFilter - CommonTraceFilter

We write all the abstract classes of GlobalFilter to be implemented later. The main functions of this abstract class are:

  • To ensure that the GlobalFilter that inherits this abstract class and the spliced links have link information is to ensure that the Mono returned by its filter is generated by the Factory implemented above.
  • Different globalfilters need to be sorted and executed in order. This can be done by implementing the Ordered interface
package com.github.jojotech.spring.cloud.apigateway.filter;

import com.github.jojotech.spring.cloud.apigateway.common.TraceWebFilterUtil;
import com.github.jojotech.spring.cloud.apigateway.common.TracedPublisherFactory;
import reactor.core.publisher.Mono;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;


/**
 * Subclasses of all filter s
 * It mainly ensures the integrity of span. In some cases, span will stop halfway, resulting in no traceId and spanId in the log
 * reference resources: https://github.com/spring-cloud/spring-cloud-sleuth/issues/2004
 */
public abstract class AbstractTracedFilter implements GlobalFilter, Ordered {
	@Autowired
	protected Tracer tracer;
	@Autowired
	protected TracedPublisherFactory tracedPublisherFactory;
	@Autowired
	protected CurrentTraceContext currentTraceContext;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		Mono<Void> traced;
		if (tracer.currentSpan() == null) {
			try (CurrentTraceContext.Scope scope = this.currentTraceContext
					.maybeScope(((Span) exchange.getAttributes().get(TraceWebFilterUtil.TRACE_REQUEST_ATTR))
							.context())) {
				traced = traced(exchange, chain);
			}
		}
		else {
			//If there is currently link information, execute it directly
			traced = traced(exchange, chain);
		}
		return tracedPublisherFactory.getTracedMono(traced, exchange);
	}

	protected abstract Mono<Void> traced(ServerWebExchange exchange, GatewayFilterChain chain);
}

In this way, we can implement the GlobalFilter that needs to be customized based on this abstract class

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 Mon, 29 Nov 2021 21:16:56 -0500 by shastry