spring-cloud Sleuth

59次阅读

共计 6835 个字符,预计需要花费 18 分钟才能阅读完成。

一直没弄明白 sleuth 的 tracerContext 是如何创建和传递的,闲来无事研究了一下。由于对 sleuth 的源码不熟悉,准备通过 debug brave.Tracer 的 nextId() 方法,查看方法调用栈来找来龙去脉。
首先创建两个 service A 和 B,记作 srvA、srvB,在 srvA 中添加 testA controller,sevB 中添加 testB controller,testA 中通过 Feign 调用 testB。

先看当用户通过浏览器调用 srvA 的时候,srvA 是作为 server 的。
configuration:TraceWebServletAutoConfiguration==>TracingFilterTraceHttpAutoConfiguration==>HttpTracingTraceAutoConfiguration==>TracingSleuthLogAutoConfiguration.Slf4jConfiguration==>CurrentTraceContext

配置中,TracingFilter 在实例化时需要一个 HttpTracing:
public static Filter create(HttpTracing httpTracing) {
return new TracingFilter(httpTracing);
}

//Servlet 运行时类
final ServletRuntime servlet = ServletRuntime.get();
//Slf4jCurrentTraceContext
final CurrentTraceContext currentTraceContext;
final Tracer tracer;
final HttpServerHandler<HttpServletRequest, HttpServletResponse> handler;
//TraceContext 的数据提取器
final TraceContext.Extractor<HttpServletRequest> extractor;

TracingFilter(HttpTracing httpTracing) {
tracer = httpTracing.tracing().tracer();
currentTraceContext = httpTracing.tracing().currentTraceContext();
handler = HttpServerHandler.create(httpTracing, ADAPTER);
extractor = httpTracing.tracing().propagation().extractor(GETTER);
}
HttpTracing Builder 模式构造时接收一个 Tracing:
Tracing tracing;
// 客户端 span 解析器
HttpClientParser clientParser;
String serverName;
// 服务端 span 解析器
HttpServerParser serverParser;
HttpSampler clientSampler, serverSampler;

Builder(Tracing tracing) {
if (tracing == null) throw new NullPointerException(“tracing == null”);
final ErrorParser errorParser = tracing.errorParser();
this.tracing = tracing;
this.serverName = “”;
// override to re-use any custom error parser from the tracing component
this.clientParser = new HttpClientParser() {
@Override protected ErrorParser errorParser() {
return errorParser;
}
};
this.serverParser = new HttpServerParser() {
@Override protected ErrorParser errorParser() {
return errorParser;
}
};
this.clientSampler = HttpSampler.TRACE_ID;
this.serverSampler(HttpSampler.TRACE_ID);
}
Tracing 实例化:
@Bean
@ConditionalOnMissingBean
// NOTE: stable bean name as might be used outside sleuth
Tracing tracing(@Value(“${spring.zipkin.service.name:${spring.application.name:default}}”) String serviceName,
Propagation.Factory factory,
CurrentTraceContext currentTraceContext,
Reporter<zipkin2.Span> reporter,
Sampler sampler,
ErrorParser errorParser,
SleuthProperties sleuthProperties
) {
return Tracing.newBuilder()
.sampler(sampler)
.errorParser(errorParser)
.localServiceName(serviceName)
//ExtraFieldPropagation.Factory
.propagationFactory(factory)
.currentTraceContext(currentTraceContext)
.spanReporter(adjustedReporter(reporter))
.traceId128Bit(sleuthProperties.isTraceId128())
.supportsJoin(sleuthProperties.isSupportsJoin())
.build();
}
下面看 TracingFilter 的 doFilter:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = servlet.httpResponse(response);

// Prevent duplicate spans for the same request
TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());
if (context != null) {
// A forwarded request might end up on another thread, so make sure it is scoped
Scope scope = currentTraceContext.maybeScope(context);
try {
chain.doFilter(request, response);
} finally {
scope.close();
}
return;
}

Span span = handler.handleReceive(extractor, httpRequest);

// Add attributes for explicit access to customization or span context
request.setAttribute(SpanCustomizer.class.getName(), span.customizer());
request.setAttribute(TraceContext.class.getName(), span.context());

Throwable error = null;
Scope scope = currentTraceContext.newScope(span.context());
try {
// any downstream code can see Tracer.currentSpan() or use Tracer.currentSpanCustomizer()
chain.doFilter(httpRequest, httpResponse);
} catch (IOException | ServletException | RuntimeException | Error e) {
error = e;
throw e;
} finally {
scope.close();
if (servlet.isAsync(httpRequest)) {// we don’t have the actual response, handle later
servlet.handleAsync(handler, httpRequest, httpResponse, span);
} else {// we have a synchronous response, so we can finish the span
handler.handleSend(ADAPTER.adaptResponse(httpRequest, httpResponse), error, span);
}
}
}
在 SleuthLogAutoConfiguration 中如果有 slfj 的包,则注入 CurrentTraceContext:
@Configuration
@ConditionalOnClass(MDC.class)
@EnableConfigurationProperties(SleuthSlf4jProperties.class)
protected static class Slf4jConfiguration {

@Bean
@ConditionalOnProperty(value = “spring.sleuth.log.slf4j.enabled”, matchIfMissing = true)
@ConditionalOnMissingBean
public CurrentTraceContext slf4jSpanLogger() {
return Slf4jCurrentTraceContext.create();
}


}
Slf4jCurrentTraceContext 中,delegate 就是 CurrentTraceContext.Default.inheritable():
public static final class Default extends CurrentTraceContext {
static final ThreadLocal<TraceContext> DEFAULT = new ThreadLocal<>();
// Inheritable as Brave 3’s ThreadLocalServerClientAndLocalSpanState was inheritable
static final InheritableThreadLocal<TraceContext> INHERITABLE = new InheritableThreadLocal<>();

final ThreadLocal<TraceContext> local;

// 静态方法 create,local 对象为 ThreadLocal 类型
/** Uses a non-inheritable static thread local */
public static CurrentTraceContext create() {
return new Default(DEFAULT);
}

//local 对象为 InheritableThreadLocal 类型
// 官方文档指出,inheritable 方法在线程池的环境中需谨慎使用,可能会取出错误的 TraceContext,这样会导致 Span 等信息会记录并关联到错误的 traceId 上
/**
* Uses an inheritable static thread local which allows arbitrary calls to {@link
* Thread#start()} to automatically inherit this context. This feature is available as it is was
* the default in Brave 3, because some users couldn’t control threads in their applications.
*
* <p>This can be a problem in scenarios such as thread pool expansion, leading to data being
* recorded in the wrong span, or spans with the wrong parent. If you are impacted by this,
* switch to {@link #create()}.
*/
public static CurrentTraceContext inheritable() {
return new Default(INHERITABLE);
}

Default(ThreadLocal<TraceContext> local) {
if (local == null) throw new NullPointerException(“local == null”);
this.local = local;
}

@Override public TraceContext get() {
return local.get();
}

// 替换当前 TraceContext,close 方法将之前的 TraceContext 设置回去
//Scope 接口继承了 Closeable 接口,在 try 中使用会自动调用 close 方法,为了避免用户忘记 close 方法,还提供了 Runnable,Callable,Executor,ExecutorService 包装方法
@Override public Scope newScope(@Nullable TraceContext currentSpan) {
final TraceContext previous = local.get();
local.set(currentSpan);
class DefaultCurrentTraceContextScope implements Scope {
@Override public void close() {
local.set(previous);
}
}
return new DefaultCurrentTraceContextScope();
}
}
Slf4jCurrentTraceContext 的 delegate 使用的就是一个 InheritableThreadLocal,InheritableThreadLocal 在创建子线程的时候,会将父线程的 inheritableThreadLocals 继承下来。这样就实现了 TraceContext 在父子线程中的传递。
看一下 CurrentTraceContext 的 maybeScope:
// 返回一个新的 scope,如果当前 scope 就是传入的 scope,返回一个空 scope
public Scope maybeScope(@Nullable TraceContext currentSpan) {
// 获取当前 TraceContext
TraceContext currentScope = get();
// 如果传入的 TraceContext 为空,且当前 TraceContext 为空返回空 scope
if (currentSpan == null) {
if (currentScope == null) return Scope.NOOP;
return newScope(null);
}
return currentSpan.equals(currentScope) ? Scope.NOOP : newScope(currentSpan);
}
TracingFilter 中 HttpServerHandler 解析 Request:
请输入代码
2.srvA 请求到 servB 时作为 Client。
TraceLoadBalancerFeignClient–>LoadBalancerFeignClient–>FeignLoadBalancer–>LazyTracingFeignClient–>Client

正文完
 0