SpringCloud升級之路2020.0.x版-44.避免鏈路資訊丟失做的設計(2)

乾貨滿滿張雜湊發表於2021-11-30

本系列程式碼地址:https://github.com/JoJoTec/spring-cloud-parent

我們在這一節我們將繼續講解避免鏈路資訊丟失做的設計,主要針對獲取到現有 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

相關文章