核心组件
可以看出代码中围绕的是
- Tracing
- Tracer
- PendingSpans
- TraceContext
什么是Tracing,Tracing可以看做是构建Tracer的一个构建器,他们是1:1的关系
什么是Tracer,Trace是控制产生Span的接口,用于创建一个Span
什么是Span,Span可以看做是一个阶段, 一个链路由多个阶段组成, 因此一个Span可以有父Span,以及生成子Span
什么是TraceContext, TraceContext可以作为一个Span阶段的参数存储以及远程传输的参数存储,例如SpanId,ParentSpanId 等,
和Span 是 1:1的关系
- 如何开启一个新的追踪阶段, 这是Tracer类的第一个方法,可以看出, 一个Span对应一个父类的Context和当前的Context,父可以为空
Q1. Span和TraceContext放在哪里
Q2. Span如何使用
public Span newTrace() {
return _toSpan(null, newRootContext(0));
}
Span _toSpan(@Nullable TraceContext parent, TraceContext context) {
if (isNoop(context)) return new NoopSpan(context);
// 这一步创建一个Span
PendingSpan pendingSpan = pendingSpans.getOrCreate(parent, context, false);
TraceContext pendingContext = pendingSpan.context();
// A lost race of Tracer.toSpan(context) is the only known situation where "context" won't be
// the same as pendingSpan.context()
if (pendingContext != null) context = pendingContext;
// 【最终返回的是RealSpan】
return new RealSpan(context, pendingSpans, pendingSpan.state(), pendingSpan.clock());
}
A1. Span放在pendingSpans的Map里, Key为Context,Value为Span, 因此 Span和Context是1:1的关系
// 创建Span的逻辑
// 可以看出一个Span (PendingSpan)包含了 一个 context, span, clock
public PendingSpan getOrCreate(
@Nullable TraceContext parent, TraceContext context, boolean start) {
// 【这个是典型的先从缓存拿, 没有就创建, 可以知道】
PendingSpan result = get(context);
if (result != null) return result;
// 【创建 Span】
MutableSpan span = new MutableSpan(context, defaultSpan);
PendingSpan parentSpan = parent != null ? get(parent) : null;
// =================================== 可以忽略 ============================ //
// save overhead calculating time if the parent is in-progress (usually is)
TickClock clock;
if (parentSpan != null) {
TraceContext parentContext = parentSpan.context();
if (parentContext != null) parent = parentContext;
clock = parentSpan.clock;
if (start) span.startTimestamp(clock.currentTimeMicroseconds());
} else {
long currentTimeMicroseconds = this.clock.currentTimeMicroseconds();
clock = new TickClock(currentTimeMicroseconds, System.nanoTime());
if (start) span.startTimestamp(currentTimeMicroseconds);
}
// =================================== 可以忽略 end ============================ //
// 包装, 有种DTO的感觉
PendingSpan newSpan = new PendingSpan(context, span, clock);
// Probably absent because we already checked with get() at the entrance of this method
PendingSpan previousSpan = putIfProbablyAbsent(context, newSpan);
if (previousSpan != null) return previousSpan; // lost race
// We've now allocated a new trace context.
assert parent != null || context.isLocalRoot() :
"Bug (or unexpected call to internal code): parent can only be null in a local root!";
// handlerContext 就是 TraceContext, 是TraceContext的深拷贝 早期被拷贝的Context已经被回收了
// 一个阶段的创建可以
spanHandler.begin(newSpan.handlerContext, newSpan.span, parentSpan != null
? parentSpan.handlerContext : null);
return newSpan;
}
A2. 实际操纵的是RealSpan,对Span的包装,主要有Start() finish()等方法,为什么要包装, 主要是隐藏了对于Context内存资源的回收以及多线程的线程安全处理, 这里可以适当怀疑就是被包装的Span没写充分后面又加上去的
Tracer再了解
熟悉了Span,看看Tracer怎么操作Span。
【1】
public Span newTrace() {
return _toSpan(null, newRootContext(0));
}
【2】
public final Span joinSpan(TraceContext context) {
if (context == null) throw new NullPointerException("context == null");
if (!supportsJoin) return newChild(context);
// set shared flag if not already done
int flags = InternalPropagation.instance.flags(context);
if (!context.shared()) {
flags |= FLAG_SHARED;
return toSpan(context, InternalPropagation.instance.withFlags(context, flags));
} else {
flags &= ~FLAG_SHARED;
return toSpan(InternalPropagation.instance.withFlags(context, flags), context);
}
}
【3】
TraceContext swapForPendingContext(TraceContext context) {
PendingSpan pendingSpan = pendingSpans.get(context);
return pendingSpan != null ? pendingSpan.context() : null;
}
【4】
public Span newChild(TraceContext parent) {
if (parent == null) throw new NullPointerException("parent == null");
return _toSpan(parent, decorateContext(parent, parent.spanId()));
}
【5】
*/
@Nullable public Span currentSpan() {
TraceContext context = currentTraceContext.get();
if (context == null) return null;
// Returns a lazy span to reduce overhead when tracer.currentSpan() is invoked just to see if
// one exists, or when the result is never used.
return new LazySpan(this, context);
}
【6】
public ScopedSpan startScopedSpanWithParent(String name, @Nullable TraceContext parent) {
if (name == null) throw new NullPointerException("name == null");
TraceContext context =
parent != null ? decorateContext(parent, parent.spanId()) : newRootContext(0);
return newScopedSpan(parent, context, name);
}
【7】
ScopedSpan newScopedSpan(@Nullable TraceContext parent, TraceContext context, String name) {
Scope scope = currentTraceContext.newScope(context);
if (isNoop(context)) return new NoopScopedSpan(context, scope);
PendingSpan pendingSpan = pendingSpans.getOrCreate(parent, context, true);
Clock clock = pendingSpan.clock();
MutableSpan state = pendingSpan.state();
state.name(name);
return new RealScopedSpan(context, scope, state, clock, pendingSpans);
}
【1】方法 已经了解
【2】相当于服用一个Context 生成Span, 用于远程调用当前服务的时候,当前服务可以再续前面的Span,什么场景会续用呢?暂时不了解
extracted = extractor.extract(request);
span = contextOrFlags.context() != null
? tracer.joinSpan(contextOrFlags.context())
: tracer.nextSpan(extracted);
【3】返回这个Span的会被回收的Context,handlerContext不会被回收
【4】什么是newChild其实就是多了一个parendID的Span, 【1】+ parentId, 这里想到还有一个方法是nextSpan()
【5】这里要和【7】一起看
为什么叫CurrentSpan,之前说Span是放在Tracer的PendingSpans的map里, 这个方法从currentTraceContext拿是为什么,这里看【7】newScopedSpan() 在构建一个Scope的时候并不会用到ParentScope
可以理解如下:
----> newTrace ----> new ChildSpan ----> new ChildSpan2
----> newScope1()
----> newScopeChildSpan11
----> newScope2()
----> newScopeChildSpan12
也就是说一个链路并不是一个直线,而是一个有分叉的链路。
【5】这个方法可以用来在Tracer newScop()期间使用,来获取当前Scope所处的Span
在不同中间件中传输Context
Tracer 中有一个属性为 Propagation.Factory
其中有两个方法, 一个是注入, 另一个是解注
他的默认实现为B3
如何使用其实就是比如在Http之间注入的话, 我自定义一个拦截器,实现一个我的Setter, Propagation.Factory会调用Setter中的put方法, 那你只用在Setter 实现的put方法中 对HttpHeader put参数就行。