Appdash原始碼閱讀——Annotations與Event
annotations
Annotations 用於儲存 Span 攜帶資訊,除了 Span 自身部分資訊={TraceID、SpanID 和 ParentID},通過 Span 儲存 Span 自身資訊和 Annotations 攜帶資訊。
這裡要說明的一點,我覺得 Appdash 並沒有把 Span 自身資訊和其他資訊區分好,但是 OpenTracing 官方的 basictracer-go 庫的 spanImpl 區分比較好;因為前者 Span 的 OperationName 應該是屬於 Span 本身的,但是在 Appdash 中被儲存到 Annotation["Name"] 中;
type Annotations []Annotation
type Annotation struct {
Key string
Value []byte
}
// 用於校驗Annotation的Key值是否在已註冊事件中是關鍵資訊
func (a Annotation) Important() bool {
... // 遍歷獲取所有已註冊事件列表,校驗每個事件是否為重要事件,並對獲取到的重要事件
// 通過event.Important方法獲取關鍵字列表keys
// 遍歷keys並與a.Key值比較,如果相同,返回true;否則返回false
}
func (as Annotations) String() string{
... // 序列化Annotations的鍵值對列表
}
func (as Annotations) schemas() []string {
... // Annotations的所有key,並返回
// 注意一點:Appdash內部的五種Event資料key都是帶有字首"_schema:"。內部標識
// 其他都是外部自定義註冊事件值
}
func (as Annotations) get(key string) []byte {
... // 在Annotations中找到鍵為key的value值,並返回
}
func (as Annotations) StringMap() map[string]string {
... // 把Annotations列表轉換成map結構資料後,返回
}
// 這裡有個疑問,為何Annotations不直接採用map[string][]byte結構儲存,而是使用slice列表結構儲存?
// 答案能想到的就是,map的相同key是會做覆蓋處理,而slice結構是做append追加處理。
// 但是前面所看到的通過key,獲取value,並沒有考慮重複,所以還是有疑問。
func (as Annotations) wire() (w []*wire.CollectPacket_Annotation) {
... // 遍歷Annotations列表,把攜帶資訊轉換為protobuffer協議傳輸的資料資訊
// 注意一點:這裡是使用的Annotations的快照儲存,防止資料動態修改。這裡是否有必要使用快照,後面再看.
}
func annotationFromWire(as []*wire.CollectPacket_Annotations) Annotations {
... // 從protobuffer協議資料流中把網路資料轉換為Annotations結構資料儲存。
}
event
// Span攜帶資訊資料Annotations中,有一些是事件資訊。
// Appdash內部自身定義了五種Event
// Schema方法用於返回事件的Key值。這個Key值代表事件名稱
type Event interface{
Schema() string
}
// 在事件列表中,哪些事件是值得關注的事件或者metrics
// Important方法返回一個列表值,這個後面再看
type ImportantEvent interface {
Important() []string
}
// 由於Event事件資料是儲存在Span的Annotations資料中,而Annotations並沒有顯式指明哪些是Event列表資料,所以就存在一對解析方法。類似於encoding/decoding
type EventMarshaler interface {
MarshalEvent() (Annotations, error)
}
type EventUnmarshaler interface{
UnmarshalEvent(Annotations) (Event, error)
}
// 標準化使用Marshal方法,把Event資料轉換成Annotations
// 如果傳入的引數不支援MarshalEvent,則使用預設的方法去Marshal event資料
// 簡單的Event可以不用MarshalEvent,直接使用預設的
// 複雜的Event資料可以自定義實現MarshalEvent和UnmarshalEvent方法序列化和反序列化資料為指定的資料型別Annotations
// Appdash內部的五類Event,型別簡單,使用預設的序列化就行了
func MarshalEvent(e Event) (Annotations, error) {
// 先嚐試校驗Event是否實現了MarshalEventer介面,如果是的話,直接序列化
if v, ok := e.(EventMarshaler); ok {
as, err := v.MarshalEvent()
...
as = append(as, Annotation{Key: SchemaPrefix + e.Schema()}
return as, nil
}
// 否則,使用預設的序列化方法
var as Annotations
flattenValue("", reflect.ValueOf(e), func(k, v string) {
as = append(as, Annotation{Key: k, Value: []byte(v)})
})
as = append(as, Annotation{Key: SchemaPrefix+e.Schema()})
return as, nil
}
MarshalEvent 方法對於 Annotations 的 Event 儲存理解很重要;它是解答為何 Span 的 Annotations 是 Slice 儲存 key-value,而不是使用 Map[string][] byte 結構儲存?的關鍵。
針對這個問題,我還提了一個 issue,最後自己解答了, issue:206 。 主要原因是作者希望 Annotations 儲存 Event 事件時,希望與該事件相關的資訊儲存在一起連續,這樣取資料時,可以以_schema:xxx
為邊界,獲取與該事件相關的所有資訊。這也就是為何對於事件要加一個字首的原因。類似於分隔符。如果事件內部的連續資訊使用了_schema:xxx
字首,則會導致事件資訊的分裂。
在 MarshalEvent 方法中,都是先進行事件其他資訊的序列化,然後再在這個單元結尾處新增一個_schema:xxx
, 表示這個事件型別名稱和結束符,且這個事件的 value 是空值。
另外一點,預設序列化方法是使用的 flattenValue,它會採用遞迴演算法對傳入的 event 值進行操作,把這個事件的所有屬性和子屬性全部新增到 Annotations 中。
把 Annotations 中的所關注的事件,反序列化給傳入的 event 值。
func UnmarshalEvent(as Annotations, e Event) error {
aSchemas := as.schemas()
// 獲取Annotations所有的事件key列表, 並通過遍歷與傳入的Event值比較,如果找不到,返回錯誤
// 和MarshalEvent類似
// 先嚐試校驗Event是否支援反序列化,如果不支援,則使用預設的反序列化方法
unflattenValue("", reflect.ValueOf(&e), reflect.TypeOf(&e), mapToKVs(as.StringMap()))
return nil
}
對於 unflattenValue 方法是耗時操作,因為它是全域性 Annotations 檢索。我覺得最理想的方法是繼續對 Annotations 的儲存進行規範化,例如:把 Annotations 儲存資料進行分類,分為 Event、Log 等型別。
內部 Event
Appdash 內部已存在五種 Event,分別是 SpanNameEvent,logEvent,msgEvent,timespanEvent 和 Timespan。它們都是先 Event 介面:Schema 方法
// _schema:name
type SpanNameEvent struct {Name string }
func (SpanNameEvent) Schema() string { return "name"}
// 由此可以看到Span的OperationName儲存在Event中,且最終通過MarshalEvent方法序列化儲存在Annotations中
func SpanName(name string) Event {
return SpanNameEvent{Name: name}
}
// _schema:msg
type msgEvent struct { Msg string}
func (msgEvent) Schema() string { return "msg" }
func Msg(msg string) Event {
return msgEvent{Msg: msg}
}
// _schema:timespan
type timespanEvent struct {
S, E time.Time
}
func (timespanEvent) Schema() string {
return "timespan"
}
func (ev timespanEvent) Start() time.Time {
return ev.S
}
func (ev timespanEvent) End() time.Time {
return ev.E
}
// _schema:Timespan
type Timespan struct {
S time.Time `trace: "Span.Start"`
E time.Time `trace: "Span.End"`
}
func (Timespan) Schema() string { return "Timespan"}
func (s Timespan) Start() time.Time {return s.S}
func (s Timespan) End() time.Time {return s.E}
上面的timespanEvent與Timespan兩種事件的不同,我目前還不理解,::TODO
// _schema:log
type logEvent struct {
Msg string
Time time.Time
}
func Log(msg string) Event {
return logEvent{Msg: msg, Time: time.Now()}
}
func LogWithTimestamp(msg string, timestamp time.Time) Event {
return logEvent{
Msg: msg,
Time: timestamp,
}
}
func (logEvent) Schema() string {
return "log"
}
func (e *logEvent) Timestamp() time.Time {
return e.Time
}
本章小結:
Appdash 是早於 OpenTracing 標準出現的,所以它並沒有遵循 OpenTracing 標準,後面也逐漸支援,但是也只是部分支援。雖然該 trace 的 log 部分使用了 basictracer-go,但是其實可以取代不要它,它只是一個擴充套件。見 issue 207。
對於 Span 的 Annotations,儲存資料包含了所有的 Event 列表,Event 與 Annotations 的資料轉換 (序列化和反序列化), 通過 MarshalEvent 和 UnmarshalEvent 方法實現。
文中也解釋了 Annotations 為何不使用 Map[string][] byte 儲存,而要用 slice 結構儲存資料。
這部分 Annotations 與 Event 的序列化和反序列化涉及到大量的 reflect,在後面會有相應的分析。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Appdash原始碼閱讀——Recorder與CollectorAPP原始碼
- Appdash原始碼閱讀——reflectAPP原始碼
- Appdash原始碼閱讀——RecentStore和LimitStoreAPP原始碼MIT
- Appdash原始碼閱讀——Store儲存APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- Appdash原始碼閱讀——部分opentracing支援APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- basictracer-go原始碼閱讀——event&propagationGo原始碼
- 閱讀原始碼---與高手對話原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 閱讀原始碼的意義與方法原始碼
- ReactorKit原始碼閱讀React原始碼
- AQS原始碼閱讀AQS原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- NGINX原始碼閱讀Nginx原始碼
- Mux 原始碼閱讀UX原始碼
- HashMap原始碼閱讀HashMap原始碼
- fuzz原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- express 原始碼閱讀Express原始碼
- muduo原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- Laravel 原始碼閱讀 - QueueLaravel原始碼
- Vollery原始碼閱讀(—)原始碼
- 使用OpenGrok閱讀原始碼原始碼
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 原始碼閱讀技巧篇原始碼
- 如何閱讀框架原始碼框架原始碼
- 再談原始碼閱讀原始碼