我們在這一節我們將繼續講解避免鏈路資訊丟失做的設計,主要針對獲取到現有 Span 之後,如何保證每個 GlobalFilter 都能保持鏈路資訊。首先,我們自定義 Reactor 的核心 Publisher 即 Mono 和 Flux 的工廠,將鏈路資訊封裝進去,保證由這個工廠生成的 Mono 和 Flux,都是隻要是這個工廠生成的 Mono 和 Flux 之間無論怎麼拼接都會保持鏈路資訊的:
自定義 Mono 和 Flux 的工廠
公共 Subscriber 封裝,將 reactor Subscriber 的所有關鍵介面,都檢查當前上下文是否有鏈路資訊,即 Span,如果沒有就包裹上,如果有則直接執行即可。
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 (tracer.currentSpan() == null) {
try (CurrentTraceContext.Scope scope = this.currentTraceContext.maybeScope(this.span.context())) {
runnable.run();
}
} else {
//如果當前已有鏈路資訊,則直接執行
runnable.run();
}
}
}
之後分別定義所有 Flux 的代理 TracedFlux,和所有 Mono 的代理 TracedMono,其實就是在 subscribe 的時候,用 TracedCoreSubscriber 包裝傳入的 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));
}
}
定義工廠類,使用請求 ServerWebExchange 和原始 Flux 建立 TracedFlux,以及使用請求 ServerWebExchange 和原始 Mono 建立 TracedMono,並且 Span 是通過 Attributes 獲取的,根據前文的原始碼分析我們知道,這個 Attribute 是通過 TraceWebFilter 放入 Attributes 的。由於我們只在 GatewayFilter 中使用,一定在 TraceWebFilter 之後 所以這個 Attribute 一定存在。
@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));
}
}
公共抽象 GlobalFilter - CommonTraceFilter
我們編寫所有我們後面要實現的 GlobalFilter 的抽象類,這個抽象類的主要功能是:
- 保證繼承這個抽象類的 GlobalFilter 本身以及拼接的鏈路中,是有鏈路資訊的,其實就是保證它 filter 返回的 Mono
是由我們上面實現的 Factory 生成的即可。 - 不同 GlobalFilter 之間需要排序,有順序的執行,這個通過實現 Ordered 介面即可
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;
/**
* 所有 filter 的子類
* 主要保證 span 的完整性,在某些情況下,span 會半途停止,導致日誌中沒有 traceId 和 spanId
* 參考: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 {
//如果當前已有鏈路資訊,則直接執行
traced = traced(exchange, chain);
}
return tracedPublisherFactory.getTracedMono(traced, exchange);
}
protected abstract Mono<Void> traced(ServerWebExchange exchange, GatewayFilterChain chain);
}
這樣,我們就可以基於這個抽象類去實現需要定製的 GlobalFilter 了
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: