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

我们在这一节首先分析下 Spring Cloud Gateway 一些其他可能丢失链路信息的点,之后来做一些可以避免链路信息丢失的设计,之后基于这个设计去实现我们需要的一些定制化的 GlobalFilter
Spring Cloud Gateway 其他的可能丢失链路信息的点
经过前面的分析,我们可以看出,不止这里,还有其他地方会导致 Spring Cloud Sleuth 的链路追踪信息消失,这里举几个大家常见的例子:
1.在 GatewayFilter 中指定了异步执行某些任务,由于线程切换了,并且这时候可能 Span 已经结束了,所以没有链路信息,例如:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).publishOn(Schedulers.parallel()).doOnSuccess(o -> {
//这里就没有链路信息了
log.info("success");
});
}
2.将 GatewayFilter 中继续链路的 chain.filter(exchange) 放到了异步任务中执行,上面的 AdaptCachedBodyGlobalFilter 就属于这种情况,这样会导致之后的 GatewayFilter 都没有链路信息,例如:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.delay(Duration.ofSeconds(1)).then(chain.filter(exchange));
}
Java 并发编程模型与 Project Reactor 编程模型的冲突思考
Java 中的很多框架,都用到了 ThreadLocal,或者通过 Thread 来标识唯一性。例如:
- 日志框架中的 MDC,一般都是 ThreadLocal 实现。
- 所有的锁、基于 AQS 的数据结构,都是通过 Thread 的属性来唯一标识谁获取到了锁的。
- 分布式锁等数据结构,也是通过 Thread 的属性来唯一标识谁获取到了锁的,例如 Redisson 中分布式 Redis 锁的实现。
但是放到 Project Reactor 编程模型,这就显得格格不入了,因为 Project Reactor 异步响应式编程就是不固定线程,没法保证提交任务和回调能在同一个线程,所以 ThreadLocal 的语义在这里很难成立。Project Reactor 虽然提供了对标 ThreadLocal 的 Context,但是主流框架还没有兼容这个 Context,所以给 Spring Cloud Sleuth 粘合这些链路追踪带来了很大困难,因为 MDC 是一个 ThreadLocal 的 Map 实现,而不是基于 Context 的 Map。这就需要 Spring Cloud Sleuth 在订阅一开始,就需要将链路信息放入 MDC,同时还需要保证运行时不切换线程。
运行不切换线程,这样其实限制了 Project Reactor 的灵活调度,是有一些性能损失的。我们其实想尽量就算加入了链路追踪信息,也不用强制运行不切换线程。但是 Spring Cloud Sleuth 是非侵入式设计,很难实现这一点。但是对于我们自己业务的使用,我们可以定制一些编程规范,来保证大家写的代码不丢失链路信息。
可以从哪里获取当前请求的 Span
Spring Cloud Sleuth 的链路信息核心即 Span,在之前的源码分析中,我们知道,在入口的 WebFilter 中,TraceWebFilter 生成 Span 并将其放入本次 HTTP 请求响应抽象的 ServerWebExchange 的 attributes 中:
protected static final String TRACE_REQUEST_ATTR = Span.class.getName();
private Span findOrCreateSpan(Context c) {
Span span;
AssertingSpan assertingSpan = null;
//如果当前 Reactor 的上下文中有 Span,就用这个 Span
if (c.hasKey(Span.class)) {
Span parent = c.get(Span.class);
try (Tracer.SpanInScope spanInScope = this.tracer.withSpan(parent)) {
span = this.tracer.nextSpan();
}
if (log.isDebugEnabled()) {
log.debug("Found span in reactor context" + span);
}
}
else {
//如果当前请求中本身包含 span 信息,就用这个 span 启动一个新的子 span
if (this.span != null) {
try (Tracer.SpanInScope spanInScope = this.tracer.withSpan(this.span)) {
span = this.tracer.nextSpan();
}
if (log.isDebugEnabled()) {
log.debug("Found span in attribute " + span);
}
}
//从当前所处的上下文中获取 span
span = this.spanFromContextRetriever.findSpan(c);
//没获取到就新生成一个
if (this.span == null && span == null) {
span = this.handler.handleReceive(new WrappedRequest(this.exchange.getRequest()));
if (log.isDebugEnabled()) {
log.debug("Handled receive of span " + span);
}
}
else if (log.isDebugEnabled()) {
log.debug("Found tracer specific span in reactor context [" + span + "]");
}
assertingSpan = SleuthWebSpan.WEB_FILTER_SPAN.wrap(span);
//将 span 放入 `ServerWebExchange` 的 attributes 中
this.exchange.getAttributes().put(TRACE_REQUEST_ATTR, assertingSpan);
}
if (assertingSpan == null) {
assertingSpan = SleuthWebSpan.WEB_FILTER_SPAN.wrap(span);
}
return assertingSpan;
}
这样可以看出,我们在编写 GlobalFilter 的时候可以通过读取 ServerWebExchange 的 attributes 获取当前链路信息的 Span。但是 TRACE_REQUEST_ATTR 是 protected 的,我们可以下面这个工具类将其暴露出来。
package com.github.jojotech.spring.cloud.apigateway.common;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.http.HttpServerHandler;
import org.springframework.cloud.sleuth.instrument.web.TraceWebFilter;
public class TraceWebFilterUtil extends TraceWebFilter {
public static final String TRACE_REQUEST_ATTR = TraceWebFilter.TRACE_REQUEST_ATTR;
//仅仅为了暴露 TraceWebFilter 的 TRACE_REQUEST_ATTR 使用的工具类
private TraceWebFilterUtil(Tracer tracer, HttpServerHandler handler, CurrentTraceContext currentTraceContext) {
super(tracer, handler, currentTraceContext);
}
}
下一节,我们将继续讲解避免链路信息丢失做的设计,主要针对获取到现有 Span 之后,如何保证每个 GlobalFilter 都能保持链路信息。
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:

SpringCloud升级之路2020.0.x版-44.避免链路信息丢失做的设计(1)的更多相关文章
- SpringCloud升级之路2020.0.x版-44.避免链路信息丢失做的设计(2)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们在这一节我们将继续讲解避免链路信息丢失做的设计,主要针对获取到现有 Span 之后,如 ...
- SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...
- SpringCloud升级之路2020.0.x版-45. 实现公共日志记录
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们这一节在前面实现的带有链路信息的 Publisher 的工厂的基础上,实现公共日志记录 ...
- SpringCloud升级之路2020.0.x版-1.背景
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...
- SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...
- SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...
- SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 网关由于是所有外部用户请求的入口,记录这些请求中我们需要的元素,对于线上监控以及业务问题定 ...
- SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...
随机推荐
- opencv中的exp32f函数
exp32f opencv的exp函数和cmath的exp函数在精度上存在一定差异,通过查找源码,发现了这么一段实现.代码如下: 点击查看代码 #define EXPTAB_SCALE 6 #defi ...
- mysql join语句的执行流程是怎么样的
mysql join语句的执行流程是怎么样的 join语句是使用十分频繁的sql语句,同样结果的join语句,写法不同会有非常大的性能差距. select * from t1 straight_joi ...
- Boost Started on Windows
Boost 官网指南 Boost C++ Libraries Boost Getting Started on Windows - 1.77.0 ① 下载 Boost.7z包 下载 .7z包 boos ...
- docker内服务访问宿主机服务
目录 1. 场景 2. 解决 4. 参考 1. 场景 使用windows, wsl2 进行日常开发测试工作. 但是wsl2经常会遇到网络问题.比如今天在测试一个项目,核心功能是将postgres 的数 ...
- 机器学习:SVM
SVM 前言:支持向量机(Support Vector Machine, SVM),作为最富盛名的机器学习算法之一,其本身是一个二元分类算法,为了更好的了解SVM,首先需要一些前提知识,例如:梯度下降 ...
- Alpha阶段初始任务分配
项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目-计划-Alpha阶段说明书 一.Alpha阶段总体规划 进行服务器相关部署 进行开发相关技术学习 ...
- logstash的mutate过滤器的使用
logstash的mutate过滤器的使用 一.背景 二.需求 三.实现步骤 1.安装 `csv codec` 插件 2.准备需要读取的文件数据 3.编写 pipeline ,读取和输出数据 4.mu ...
- [火星补锅] 非确定性有穷状态决策自动机练习题Vol.1 T3 第K大区间 题解
前言: 老火星人了 解析: 很妙的二分题.如果没想到二分答案.. 很容易想到尝试用双指针扫一下,看看能不能统计答案. 首先,tail指针右移时很好处理,因为tail指针右移对区间最大值的影响之可能作用 ...
- 小白自制Linux开发板 十. NES游戏玩起来
本篇基于我们制作的Debian文件系统而展开,而且我们这会玩一些高级的操作方式--用我们的小电脑进行程序编译. 所以本篇操作全部都在我们个的开发板上完成. 1. 开发环境搭建 首先安装gcc, ...
- python解释器下载安装指导
一.python解释器下载 想要通关python这项语言与计算机进行沟通,我们就必须下载一款能让计算机理解python这项语言的解释器,这时候我们就需要到网上下一个python解释器. python解 ...