本系列代码地址: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

SpringCloud升级之路2020.0.x版-44.避免链路信息丢失做的设计(2)的更多相关文章

  1. SpringCloud升级之路2020.0.x版-44.避免链路信息丢失做的设计(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们在这一节首先分析下 Spring Cloud Gateway 一些其他可能丢失链路信息 ...

  2. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  3. SpringCloud升级之路2020.0.x版-45. 实现公共日志记录

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们这一节在前面实现的带有链路信息的 Publisher 的工厂的基础上,实现公共日志记录 ...

  4. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  5. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  6. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  7. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  8. SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 网关由于是所有外部用户请求的入口,记录这些请求中我们需要的元素,对于线上监控以及业务问题定 ...

  9. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

随机推荐

  1. 【NXOpen.UF扩展】修改表达式

    public static class UFExpEx { /// <summary> /// 修改当前部件的表达式 /// </summary> /// <param ...

  2. NX屏蔽窗口的按钮

    有时候在激活一个命令按钮的时候,需要同时禁止掉另外一个或多个命令按钮 ''' <summary> ''' 取按钮是否敏感 ''' </summary> ''' <para ...

  3. Kettle的安装及简单使用

    Kettle的安装及简单使用 目录 Kettle的安装及简单使用 一.kettle概述 二.kettle安装部署和使用 Windows下安装 案例1:MySQL to MySQL 案例2:使用作业执行 ...

  4. Java:并发笔记-01

    Java:并发笔记-01 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 1. 进程与线程 本章内容 进程和线程的概念 并行和并发的概念 线程基本应用 1.1 ...

  5. 四万字32图,Kafka知识体系保姆级教程宝典

    本文目录: 一.消息队列 Apache Pulsar Pulsar 与 Kafka 对比 二.Kafka基础 三.Kafka架构及组件 四.Kafka集群操作 五.Kafka的JavaAPI操作 六. ...

  6. Scrum Meeting 0607

    零.说明 日期:2021-6-7 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 困难 qsy PM&前端 重新设计产品 ...

  7. Noip模拟47 2021.8.25

    期望得分:55+24+53 实际得分:0+0+3 乐死 累加变量清零了吗? 打出更高的部分分暴力删了吗? 样例解释换行你看见了吗? T1 Prime 打出55分做法没删原来的暴力,结果就轻松挂55分 ...

  8. numpy数组的计算

    1.数组的形状 查看数组的形状: import numpy as np a = np.array([[1, 2, 3, 4, 5], [5, 6, 7, 8, 9]]) print(a.shape) ...

  9. 攻防世界 杂项 12.Training-Stegano-1

    题目描述: 这是我能想到的最基础的图片隐写术.啊这 题目分析: 最初还以为直接右击属性查看呢 然后用notepad++看看,一团乱码,结果在最后发现了passwd, 然后这就是flag:stegano ...

  10. Spring---IoC(控制反转)原理学习笔记【全】

    1.IoC创建对象的方式 使用无参构造创建对象 假如要使用有参构造创建: 下标赋值constructor-arg <!--有参--> <bean id="User" ...