Appdash原始碼閱讀——Tracer&Span
Appdash是基於basictracer-go擴充套件實現的,在此基礎上豐富了的Collector和Storage。以及增加了dashboard UI 一些簡單的查詢統計功能。
Appdash中的Trace和Span struct與basictracer-go中的traceImpl和spanImpl struct的關係,後面再看。 ::TODO
trace
basictracer-go的擴充套件實現中,雖然是形成了完整的呼叫鏈,但是如果想要通過dashboard去進行繪圖這條呼叫鏈, 則只能通過SpanRecorder的[]RawSpan,去通過演算法找到一條完整的鏈路,可能這個操作還非常耗時。
所以Appdash針對Dashboard的業務場景,定義了基於Trace的顯式鏈路結構:
// Appdash的Trace結構設計:採用了廣度遍歷思想,層次樹儲存。
// ChildOf和FollowsFrom理解:Sub列表為FollowsFrom關係,Trace和Sub列表為ChildOf關係
type Trace struct {
Span // 根部span
Sub []*Trace // child node
}
type Span struct {
ID SpanID // SpanID= {TraceID, SpanID, ParentID}
// Annotations攜帶了Span的所有資訊;
// 包括Tags,Baggage,Logs,OperationName等;
// 其中span.Log進行分類,Appdash自身已定義了:五種Log
// SpanNameEvent, logEvent, msgEvent, timespanEvent和Timespan
// 型別分別是:name, log, msg, timespan和TimeSpan
// 其中後兩者的不同後面再理解 ::TODO
// 對於Event也可以進行自定義實現, 但是Annotations不是直接的分類,需要對Annotations與Event進行資料轉換儲存才行
Annotations // []{key:values}
}
// 對於span.Logs的型別定義,在Appdash中可以擴充套件,只需要實現Event interface, 並通過RegisterEvent方法註冊自定義span logs事件
// Schema方法的返回值,作為Log的key型別值
// 需要說明的一點是:內部的五種型別Log,具有一定的標識字首:
// SchemaPrefix = "_schema:"
type Event interface {
Schema() string
}
func (t *Trace) String() string {
... // 序列化Trace資料
}
func (t *Trace) TreeString() string {
... // 列印樹資料,以樹結構的形式輸出。
}
// 從根節點開始遍歷遞迴查詢SpanID,返回Trace
// 可能大家有個疑問:為何返回Trace,而不是Span?
// 因為Trace型別是層次樹結構,所以每個節點都可以看成樹節點,等同看的話,這就是Trace。實際上是Span節點,但是我們通過任意的子節點,可以獲取到TraceID等呼叫鏈的全域性資訊。
func (t *Trace) FindSpan(spanID ID) *Trace {
...
}
func (t *Trace) TimespanEvent() (TimespanEvent, error) {
... // 它是在當前呼叫鏈的span節點上,找到span logs中五類的TimespanEvent事件並返回
// 這個過程在下節會詳細介紹
}
在Appdash span logs的分類與Span的Annotations的資料轉換與儲存比較複雜,我會單獨一節講解span log內部五類,並對資料轉換與儲存進行詳細介紹。
span
span作為呼叫鏈樹中的子節點,它的儲存結構如下(Span攜帶的annotations下節再詳細介紹):
type Span struct {
// SpanID在序列化時String,變成了"TraceID/SpanID/ParentID"
ID SpanID
Annotations
}
// 對span進行序列化,資訊包括:TraceID/SpanID/ParentID,以及span的所有攜帶資訊:Tags、Baggages、Sampled、OperationName和Logs等
func (s *Span) String() string {
... // 通過json序列化
}
// 返回Span的OperationName,它是通過Annotations的key為"Name"獲取。
func (s *Span) Name() string {
...
}
// SpanID管轄Span本身的資訊,與basictracer-go的設計雷同。表現在:
// basictracer-go中RawSpan包括了SpanContext,它並沒有把SpanID,TraceID、Sampled資訊放在Baggage中,而是單獨拎出來。
// 這裡的Appdash Span設計把TraceID、SpanID和ParentSpanID沒有放在Annotations資訊中獲取,也是單獨拎出來,更加清晰直觀,這個資訊是必須的,非攜帶資訊,而是Span自身資訊。
type SpanID struct {
Trace ID
Span ID
Parent ID
}
func (id SpanID) string {
... // 序列化SpanID為"TraceID/SpanID/ParentSpanID"
// 後面提到的tracesByIDSpan排序,則可以做到全域性Span排序,且這個排序結果是有層次的
// 做到TraceID有序,SpanID有序,且都是連續的,例如:
// TraceID_01/SpanID_01/0
// TraceID_01/SpanID_02/SpanID_01
// TraceID_01/SpanID_03/SpanID_02
// TraceID_02/SpanID_01/0
// TraceID_02/SpanID_02/SpanID_01
// ...
// 我們可以看到前面三個為一個整體Trace呼叫鏈,後面兩個為一個整體呼叫鏈,且全域性有序,trace中的Span有序,這個設計很棒!
}
// 這個猜測是用來序列化span資訊的,包括攜帶資訊Annotations
for (id SpanID) Format(s string, args ...interface{}) string{
args = append([]interface{}{id.String()}, args...)
return fmt.Sprintf(s, args...)
}
func (id SpanID) IsRoot() bool {
return id.Parent == 0 // 校驗該span是否為根節點
}
// 封裝SpanID,通過protobuffer協議網路傳輸資料流
func (id SpanID) wire() *wire.CollectPacket_SpanID {
return &wire.CollectPacket_SpanID {
Trace: (*uint64)(&id.Trace),
Span: (*uint64)(&id.Span),
Parent: (*uint64)(&id.Parent),
}
}
// 把protobuffer協議網路流轉換成SpanID資料
func spanIDFromWire(w *wire.CollectPacket_SpanID) SpanID {
return SpanID{
Trace: ID(*w.Trace),
Span: ID(*w.Span),
Parent: ID(*w.Parent),
}
}
// 生成根節點的SpanID資訊
func NewRootSpanID() SpanID {
return SpanID{
Trace: generateID(),
Span: generateID(),
}
}
// 生成子節點的SpanID資訊
func NewSpanID(parent SpanID) SpanID {
return SpanID {
Trace: parent.Trace,
Span: generateID(),
Parent: parent.Span,
}
}
// 如果這個樹型別結構是這樣的,大家怎麼看呢?
// ::TODO 後面再理解
type Span struct {
ParentID SpanID
SpanID SpanID
TraceID TraceID
Annotations []{key-values}
Sub []*Span
}
在trace程式碼中有個sort.Interface的介面實現型別, 內部是快排演算法。
// 排序演算法:只需要滿足三點,即可實現
// 1. 比較;2. 交換;3. 參與排序的元素數量。
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type tracesByIDSpan []*Trace
// 這個型別是對Span列表進行ID排序,從小到大排列。這個意義後面再看,因為Trace樹層次本身就是按時間排序的,看看這個ID uint64型別值的生成演算法.
// ::TODO
相關文章
- Appdash原始碼閱讀——reflectAPP原始碼
- Appdash原始碼閱讀——RecentStore和LimitStoreAPP原始碼MIT
- Appdash原始碼閱讀——部分opentracing支援APP原始碼
- Appdash原始碼閱讀——Annotations與EventAPP原始碼
- Appdash原始碼閱讀——Recorder與CollectorAPP原始碼
- Appdash原始碼閱讀——Store儲存APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- ReactorKit原始碼閱讀React原始碼
- Vollery原始碼閱讀(—)原始碼
- NGINX原始碼閱讀Nginx原始碼
- ThreadLocal原始碼閱讀thread原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- Runtime 原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- AmplifyImpostors原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- fuzz原始碼閱讀原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- AQS原始碼閱讀AQS原始碼
- Mux 原始碼閱讀UX原始碼
- ConcurrentHashMap原始碼閱讀HashMap原始碼
- HashMap原始碼閱讀HashMap原始碼
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 使用OpenGrok閱讀原始碼原始碼
- express 原始碼閱讀(全)Express原始碼
- Kingfisher原始碼閱讀(一)原始碼
- 如何閱讀框架原始碼框架原始碼
- 如何閱讀jdk原始碼?JDK原始碼