最近在研究全鏈路跟蹤,因某些原因選用了 ZipKin 的 Brave 作為埋點工具,ZipKin不使用,本系列僅做 Brave(版本=5.6.0) 部分源碼解讀萨醒,其他內(nèi)容不涉及。
對全鏈路不熟悉的苇倡,先學(xué)習(xí) opentracing 的標(biāo)準(zhǔn)規(guī)范:https://opentracing-contrib.github.io/opentracing-specification-zh/specification.html
先來看一個簡單埋點Demo:
import brave.Span;
import brave.Tracer;
import brave.Tracer.SpanInScope;
import brave.Tracing;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
public class Atest {
public static void main(String[] args) {
Tracing tracing = Tracing.newBuilder().localServiceName("test")
.propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
.build();
Tracer tracer = tracing.tracer();
Span root = tracer.nextSpan().name("root").start();//創(chuàng)建跨度 root
SpanInScope scope = null;
try {
scope = tracer.withSpanInScope(root);//設(shè)置 root 跨度的作用域
//開始新的跨度 s1富纸。此處使用 currentTraceContext().get() 獲取到當(dāng)前作用域中的 TraceContext
//TraceContext 中包含著鏈路中的關(guān)鍵信息,如 TraceId, parentId, spanId 等
Span s1 = tracer.newChild(tracing.currentTraceContext().get()).name("s1").start();
System.out.println("被跟蹤的業(yè)務(wù)代碼...");
s1.finish();//結(jié)束跨度 s1
} catch (Exception e) {
root.error(e);//報錯處理
} finally {
scope.close();//結(jié)束作用域
}
root.finish();//結(jié)束跨度 root
}
}
執(zhí)行代碼旨椒,使用的是 Brave 默認(rèn)的 LoggerReporter晓褪,輸出的日志如下,默認(rèn)使用的是V2版本综慎,比起V1版本的日志涣仿,節(jié)省了很多日志內(nèi)容,更加簡潔易懂且提升性能示惊。
可見“被跟蹤的業(yè)務(wù)代碼...”先被打印出來好港,接著打印的是跨度 s1,它的父節(jié)點是“b236c6f732bca43f”米罚,spanId是“a41bf8dd8fba3f16”钧汹,耗時 138 微秒,serviceName是在Tracing設(shè)置的“test”录择,ip地址是“192.168.1.6”
最后打印的是跨度 root拔莱,一個完整簡單的鏈路跟蹤就完成了碗降。
被跟蹤的業(yè)務(wù)代碼...
一月 24, 2019 10:20:11 下午 brave.Tracing$LoggingReporter report
信息: {"traceId":"b236c6f732bca43f","parentId":"b236c6f732bca43f","id":"a41bf8dd8fba3f16","name":"s1","timestamp":1548339611584476,"duration":138,"localEndpoint":{"serviceName":"test","ipv4":"192.168.1.6"}}
一月 24, 2019 10:20:11 下午 brave.Tracing$LoggingReporter report
信息: {"traceId":"b236c6f732bca43f","id":"b236c6f732bca43f","name":"root","timestamp":1548339611569087,"duration":53153,"localEndpoint":{"serviceName":"test","ipv4":"192.168.1.6"}}
本節(jié)先看 Tracing 的代碼節(jié)選,Tracing 主要用于初始化鏈路跟蹤所需的各組件塘秦,使用了 Builder 的模式讼渊,可根據(jù)需求自由去創(chuàng)建合適的鏈路跟蹤特性。
譬如上面所使用的默認(rèn)的 LoggingReporter嗤形,可修改為上送到ZipKin的Reporter精偿。服務(wù)名,傳播模式等都可以通過Builder定制創(chuàng)建赋兵。
public abstract class Tracing implements Closeable {
//Tracing 內(nèi)部類 Builder見下方,許多組件都給了缺省定義
public static final class Builder {
//服務(wù)名與服務(wù)器Ip
String localServiceName = "unknown", localIp;
int localPort; // 服務(wù)端口
// reporter搔预,用于處理(常見上報給ZipKin或打印到本地)鏈路信息
Reporter<zipkin2.Span> spanReporter;
Clock clock;//用于計時
//采樣器霹期,用于定義采樣規(guī)則,默認(rèn)全樣采集
Sampler sampler = Sampler.ALWAYS_SAMPLE;
//用于獲取當(dāng)前 TraceContext 拯田,默認(rèn)使用了 InheritableThreadLocal历造,支持復(fù)制到異步線程
CurrentTraceContext currentTraceContext = CurrentTraceContext.Default.inheritable();
//顧名思義,traceId是否128bit船庇,是否支持Join一個跨度
boolean traceId128Bit = false, supportsJoin = true;
//傳播工廠吭产,用于定義傳播規(guī)則,如何注入與提取等鸭轮。
Propagation.Factory propagationFactory = B3Propagation.FACTORY;
//錯誤處理器
ErrorParser errorParser = new ErrorParser();
//span結(jié)束回調(diào)器
List<FinishedSpanHandler> finishedSpanHandlers = new ArrayList<>();
//執(zhí)行build方法創(chuàng)建 Tracing
public Tracing build() {
//根據(jù)不同的jdk獲取clock(Brave支持Jdk1.6+)
if (clock == null) clock = Platform.get().clock();
//根據(jù)不同的jdk獲取ip
if (localIp == null) localIp = Platform.get().linkLocalIp();
//默認(rèn)reporter就是在此處定義了
if (spanReporter == null) spanReporter = new LoggingReporter();
//將 Builder 傳入創(chuàng)建 Tracing臣淤,Default 是 Tracing一個內(nèi)部類,見下方代碼
return new Default(this);
}
}
static final class Default extends Tracing {
//代碼太長窃爷,見下方代碼塊邑蒋,單獨說Default
}
}
static final class Default extends Tracing {
final Tracer tracer;//Tracer 可以理解為鏈路對象,用于操作span
final Propagation.Factory propagationFactory;
final Propagation<String> stringPropagation;
final CurrentTraceContext currentTraceContext;
final Sampler sampler;
final Clock clock;
final ErrorParser errorParser;
final AtomicBoolean noop;
Default(Builder builder) {
//初始化過程先默認(rèn)從builder中獲取所需對象
this.clock = builder.clock;
this.errorParser = builder.errorParser;
this.propagationFactory = builder.propagationFactory;
this.stringPropagation = builder.propagationFactory.create(Propagation.KeyFactory.STRING);
this.currentTraceContext = builder.currentTraceContext;
this.sampler = builder.sampler;
this.noop = new AtomicBoolean();
List<FinishedSpanHandler> finishedSpanHandlers = builder.finishedSpanHandlers;
// If a Zipkin reporter is present, it is invoked after the user-supplied finished span handlers.
FinishedSpanHandler zipkinFirehose = FinishedSpanHandler.NOOP;
if (builder.spanReporter != Reporter.NOOP) {//若 reporter 不是空操作
zipkinFirehose = new ZipkinFinishedSpanHandler(builder.spanReporter, errorParser,
builder.localServiceName, builder.localIp, builder.localPort);
finishedSpanHandlers = new ArrayList<>(finishedSpanHandlers);
finishedSpanHandlers.add(zipkinFirehose);
}
// 將所有的 FinishedSpanHandler 歸集成一個 finishedSpanHandler
FinishedSpanHandler finishedSpanHandler =
FinishedSpanHandlers.noopAware(FinishedSpanHandlers.compose(finishedSpanHandlers), noop);
//創(chuàng)建一個 Tracer按厘,差不多也是將各種組件初始化到 Tracer 中
this.tracer = new Tracer(
builder.clock,
builder.propagationFactory,
finishedSpanHandler,
new PendingSpans(clock, zipkinFirehose, noop),
builder.sampler,
builder.currentTraceContext,
builder.traceId128Bit || propagationFactory.requires128BitTraceId(),
builder.supportsJoin && propagationFactory.supportsJoin(),
finishedSpanHandler.alwaysSampleLocal(),
noop
);
maybeSetCurrent();//確保Tracing 唯一
}
private void maybeSetCurrent() {
if (current != null) return;
synchronized (Tracing.class) {
if (current == null) current = this;
}
}
}
Tracing 的解讀基本完成医吊,看Tracing的定義,基本清楚知道整個鏈路跟蹤流程中會使用到的組件有哪些逮京,他們的作用也大概能得知卿堂。
下一章繼續(xù)講 Tracer。