这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战
本系列代码地址:github.com/JoJoTec/spr…
我们在这一节首先分析下 Spring Cloud Gateway 一些其他可能丢失链路信息的点,之后来做一些可以避免链路信息丢失的设计,之后基于这个设计去实现我们需要的一些定制化的 GlobalFilter
Spring Cloud Gateway 其他的可能丢失链路信息的点
经过前面的分析,我们可以看出,不止这里,还有其他地方会导致 Spring Cloud Sleuth 的链路追踪信息消失,这里举几个大家常见的例子:
1.在 GatewayFilter 中指定了异步执行某些任务,由于线程切换了,并且这时候可能 Span 已经结束了,所以没有链路信息,例如:
1 | kotlin复制代码@Override |
2.将 GatewayFilter 中继续链路的 chain.filter(exchange)
放到了异步任务中执行,上面的 AdaptCachedBodyGlobalFilter 就属于这种情况,这样会导致之后的 GatewayFilter 都没有链路信息,例如:
1 | kotlin复制代码@Override |
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 中:
1 | kotlin复制代码protected static final String TRACE_REQUEST_ATTR = Span.class.getName(); |
这样可以看出,我们在编写 GlobalFilter 的时候可以通过读取 ServerWebExchange
的 attributes 获取当前链路信息的 Span。但是 TRACE_REQUEST_ATTR
是 protected 的,我们可以下面这个工具类将其暴露出来。
1 | scala复制代码package com.github.jojotech.spring.cloud.apigateway.common; |
下一节,我们将继续讲解避免链路信息丢失做的设计,主要针对获取到现有 Span 之后,如何保证每个 GlobalFilter 都能保持链路信息。
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer
本文转载自: 掘金