上一篇博文中,我们了解了Brave框架的基本使用,并且分析了跟Tracer相关的部分源代码。这篇博文我们接着看看Tracing的初始化及相关类的源代码
public class TraceDemo {
public static void main(String[] args) {
Sender sender = OkHttpSender.create("http://localhost:9411/api/v2/spans");
AsyncReporter asyncReporter = AsyncReporter.builder(sender)
.closeTimeout(500, TimeUnit.MILLISECONDS)
.build(SpanBytesEncoder.JSON_V2);
Tracing tracing = Tracing.newBuilder()
.localServiceName("tracer-demo")
.spanReporter(asyncReporter)
.propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
.currentTraceContext(ThreadContextCurrentTraceContext.create())
.build();
Tracer tracer = tracing.tracer();
// ...
}
}
Brave中各个组件创建大量使用的builder设计模式,Tacing也不例外,先来看下Tracing.Builder
Tracing.Builder
public static final class Tracing.Builder {
String localServiceName;
Endpoint localEndpoint;
Reporter<zipkin2.Span> reporter;
Clock clock;
Sampler sampler = Sampler.ALWAYS_SAMPLE;
CurrentTraceContext currentTraceContext = CurrentTraceContext.Default.inheritable();
boolean traceId128Bit = false;
boolean supportsJoin = true;
Propagation.Factory propagationFactory = Propagation.Factory.B3;
public Tracing build() {
if (clock == null) clock = Platform.get();
if (localEndpoint == null) {
localEndpoint = Platform.get().localEndpoint();
if (localServiceName != null) {
localEndpoint = localEndpoint.toBuilder().serviceName(localServiceName).build();
}
}
if (reporter == null) reporter = Platform.get();
return new Default(this);
}
Builder() {
}
}
Tracing中依赖的几个重要类
- Endpoint – IP,端口和应用服务名等信息
- Sampler – 采样器,根据traceId来判断是否一条trace需要被采样,即上报到zipkin
- TraceContext – 包含TraceId,SpanId,是否采样等数据
- CurrentTraceContext – 是一个辅助类,可以用于获得当前线程的TraceContext
- Propagation – 是一个可以向数据携带的对象carrier上注入(inject)和提取(extract)数据的接口
- Propagation.Factory – Propagation的工厂类
前面TraceDemo例子中,我们初始化Tracing时设置了localServiceName,spanReporter,propagationFactory,currentTraceContext 其中spanReporter为AsyncReporter我们上一篇已经分析过其源代码了,在build方法中可以看到,其默认实现是Platform,默认会将Span信息用logger进行输出,而不是上报到zipkin中
@Override public void report(zipkin2.Span span) {
if (!logger.isLoggable(Level.INFO)) return;
if (span == null) throw new NullPointerException("span == null");
logger.info(span.toString());
}
Sampler
采样器,根据traceId来判断是否一条trace需要被采样,即上报到zipkin
public abstract class Sampler {
public static final Sampler ALWAYS_SAMPLE = new Sampler() {
@Override public boolean isSampled(long traceId) {
return true;
}
@Override public String toString() {
return "AlwaysSample";
}
};
public static final Sampler NEVER_SAMPLE = new Sampler() {
@Override public boolean isSampled(long traceId) {
return false;
}
@Override public String toString() {
return "NeverSample";
}
};
/** Returns true if the trace ID should be measured. */
public abstract boolean isSampled(long traceId);
/**
* Returns a sampler, given a rate expressed as a percentage.
*
* <p>The sampler returned is good for low volumes of traffic (<100K requests), as it is precise.
* If you have high volumes of traffic, consider {@link BoundarySampler}.
*
* @param rate minimum sample rate is 0.01, or 1% of traces
*/
public static Sampler create(float rate) {
return CountingSampler.create(rate);
}
}
Sampler.ALWAYS_SAMPLE 永远需要被采样 Sampler.NEVER_SAMPLE 永远不采样
Sampler还有一个实现类 CountingSampler可以指定采样率,如CountingSampler.create(0.5f)则对50%的请求数据进行采样,里面用到了一个算法,这里不展开分析了。
TraceContext
包含TraceId,SpanId,是否采样等数据
在Tracer的newRootContext方法中有这样一段代码,通过newBuilder来构建TraceContext对象
TraceContext newRootContext(SamplingFlags samplingFlags, List<Object> extra) {
long nextId = Platform.get().randomLong();
Boolean sampled = samplingFlags.sampled();
if (sampled == null) sampled = sampler.isSampled(nextId);
return TraceContext.newBuilder()
.sampled(sampled)
.traceIdHigh(traceId128Bit ? Platform.get().nextTraceIdHigh() : 0L).traceId(nextId)
.spanId(nextId)
.debug(samplingFlags.debug())
.extra(extra).build();
}
TraceContext中有以下一些属性
- traceIdHigh – 唯一标识trace的16字节id,即128-bit
- traceId – 唯一标识trace的8字节id
- parentId – 父级Span的spanId
- spanId – 在某个trace中唯一标识span的8字节id
- shared – 如果为true,则表明需要从其他tracer上共享span信息
- extra – 在某个trace中相关的额外数据集
还有继承自SamplingFlags的两个属性
- sampled – 是否采样
- debug – 是否为调试,如果为true时,就算sampled为false,也表明该trace需要采样(即可以覆盖sampled的值)
TraceContext中还定义了两个接口Inj