Code address of this series: https://github.com/JoJoTec/sp...
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:
- Ensure that the GlobalFilter inheriting this abstract class and the spliced links have link information. In fact, it is enough to ensure that the mono < void > returned by the 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: