Appdash原始碼閱讀——reflect
我們在Appdash 原始碼閱讀——Annotations 與 Event一節中,瞭解到 Appdash 產品的 Span 資訊儲存包括兩部分 SpanID 和 Annotations,因為 Appdash 出生比 OpenTracing 標準早,所以沒有遵循後者的標準。Span 除了自身資訊,其他資訊全部是儲存在 Annotations 中,它是 slice 結構,且裡面是 key-value 儲存方式。這裡面存放了 event 事件,為了實現 Event 和 Annotations 的資料儲存轉換,需要提供序列化和反序列化操作。這就引入了 reflect。
這節我們來看看 Appdash 的反射操作。順便了解了解 reflect
校驗兩個變數是否相等,取決於兩部分是否相等。1. 變數型別是否相等;2. 變數值是否相等
reflect
- 在把 event 序列化為 Annotations 時,如果 event 沒有提供 MarshalEvent 方法,則就使用預設的序列化方法
flattenValue
。 - 在把 Annotations 返回序列化到 event 時,如果 event 沒有提供 UnmarshalEvent 方法,則就使用預設的反序列化方法
unflattenValue
.
flattenValue
如:e: Event
type Event interface {
Schema() string // log, msg, timespan Timespan and soon
}
flattenValue("", reflect.ValueOf(e), func(k, v string) {
as = append(as, Annotation{Key: k, Value: []byte(v)})
})
// 遞迴操作, 為了更加理解這個falttenValue方法的作用,舉下面這個例子便於理解。
如:
type ClientEvent struct {
Request RequestInfo `trace:"Client.Request"`
Response ResponseInfo `trace:"Client.Response"`
ClientSend time.Time `trace:"Client.Send"`
ClientRecv time.Time `trace:"Client.Recv"`
}
type RequestInfo struct {
Method string
URI string
Proto string
Headers map[string]string
Host string
RemoteAddr string
ContentLength int64
}
type ResponseInfo struct {
Headers map[string]string
ContentLength int64
StatusCode int
}
最終解析結果:key: value = {
"Client.Request.Headers.Connection": "close",
"Client.Request.Headers.Accept": "application/json",
"Client.Request.Headers.Authorization": "REDACTED",
"Client.Request.Proto": "HTTP/1.1",
"Client.Request.RemoteAddr": "127.0.0.1",
"Client.Request.Host": "example.com",
"Client.Request.ContentLength": "0",
"Client.Request.Method": "GET",
"Client.Request.URI": "/foo",
"Client.Response.StatusCode": "200",
"Client.Response.ContentLength": "0",
"Client.Send": "0001-01-01T00:00:00Z",
"Client.Recv": "0001-01-01T00:00:00Z",
}
由上可以看到,如果struct含有trace tag標籤,則使用tag:trace作為字首;否則直接使用欄位名作為字首,且以'.'分割;
// 如果這裡的prefix改為prefixKey,更易於理解。
func flattenValue(prefix string, v reflect.Value, f func(k, v string)) {
// 複雜型別處理,包括time.Time,time.Duration和fmt.Stringer三種型別。
switch o:=v.Interface().(type) {
case time.Time:
f(prefix, o.Format(time.RFC3339Nano))
return
}
case time.Duration:
ms := float64(o.Nanoseconds()) / float64(time.Millisecond)
f(prefix, strconv.FormatFloat(ms, 'f', -1, 64)
return
case fmt.Stringer:
f(prefix, o.String())
return
}
// 其他為reflect.Kind的列舉型別
switch v.Kind() {
case reflect.Ptr:
// 指標型別,去指標
flattenValue(prefix, v.Elem(), f)
case reflect.Bool:
// 原子型別
f(prefix, strconv.FormatBool(v.Bool())
case reflect.Float32, reflect.Float64:
// 原子型別
f(prefix, strconv.FormatFloat(v.Float(), 'f', -1, 64))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// 原子型別
f(prefix, strconv.FormatInt(v.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// 原子型別
f(prefix, strconv.FormatUint(v.Uint(), 10))
case reflect.String:
// 原子型別
f(prefix, v.String())
case reflect.Struct:
// 複雜型別
// 因為做reflect操作比較耗時,所以作者採用的快取type結構方式
// var cachedFieldNames map[reflect.Type]map[int]string
// 我們看到解析過程中,使用了structTag
// 查詢tag為"trace"的標籤,並作為prefix追加字首,否則直接使用欄位名作為字首。
// nest使用'.'連線和追加字首
for i, name := range fieldNames(v) {
falttenValue(nest(prefix, name), v.Field(i), f)
}
case reflect.Map:
// 複雜型別
// 這裡對於map的處理有點難理解.
// map結構的key可能為複雜型別,value也可能為複雜型別;
// 這裡是先把key作為複雜型別處理完成後,再處理value型別,先遞迴key型別,同時保留value型別稍後遞迴。直到為原子型別或者time.Time、time.Duration或者fmt.Stringer型別。
for _, key := range v.MapKeys() {
flattenValue("", key, func(_, k string) {
flattenValue(nest(prefix, k), v.MapIndex(key), f)
})
}
case reflect.Slice, reflect.Array:
// 對於列表型別,直接把列表索引號加字首作為key
// 比如: Client.Request.XXXX.0
// Client.Request.XXXX.1
// Client.Request.XXXX....
// Client.Request.XXXX.(v.Len())
for i:=0; i < v.Len(); i++{
flattenValue(nest(prefix, strconv.Itoa(i)), v.Index(i), f)
}
default:
f(prefix, fmt.Sprintf("%+v", v.Interface())
}
}
結合上面的例項和 flattenValue 序列化,以及理解 struct 和 map 的解析,則 event 序列化為 Annotations 就理解明白了。
其中:對於 map 的解析過程有點生澀難懂, 大家可以仔細想想。
unflattenValue
在正式介紹unflattenValue
方法之前,需要了解一些輔助方法,例如:mapToKVs
, kvsByKey
, structFieldsByName
, parseValue
和 parseValueToPtr
。
因為 Annotations 是 slice 資料型別,而 Span 攜帶資訊一般都是 map 結構的,所以存在一個資料轉換方法mapToKVs
:
func mapToKVs(m map[string]string) *[][2]string {
var kvs [][2]string // slice{[key, value], ...}
for k, v := range m {
kvs = append(kvs, [2]string{k, v})
}
// 把key作為排序欄位進行列表排序
sort.Sort(kvsByKey(kvs))
return &kvs
}
// 把key作為排序欄位進行列表排序
type kvsByKey [][2]string
func (v kvsByKey) Len() int { return len(v) }
func (v kvsByKey) Less(i, j int) { return v[i][0] < v[j][0] }
func (v kvsByKey) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
給定一個型別和字串,轉換成指定的資料型別值方法parseValue
和parseValueToPtr
:
func parseValueToPtr(as reflect.Type, s string) (reflect.Value, error) {
// 複雜型別:time.Time和time.Duration
switch [2]string{as.PkgPath(), as.Name()} {
case [2]string{"time", "Time"}:
// 把時間型別和時間字串構建一個時間型別值,且時間格式只支援RFC3339Nano。
t, err := time.Parse(time.RFC3339Nano, s)
return reflect.ValueOf(&t), nil
case [2]string{"time", "Duration}:
// 把time.Duration和字串值構建成一個時間大小值
usec, err := strconv.ParseFloat(s, 64)
d := time.Duration(usec *float64(time.Millisecond))
return reflect.ValueOf(&d), nil
}
// reflect.Kind型別
switch as.Kind() {
case reflect.Ptr:
// 去掉型別指標
return parseValueToPtr(as.Elem(), s)
case reflect.Bool:
vv, _ := strconv.ParseBool(s)
return reflect.ValueOf(&vv), nil
case reflect.Float32, reflect.Float64:
vv, _ := strconv.ParseFloat(s, 32)
switch as.Kind() {
...// 對於float32和float64的處理
}
case reflect.Int, reflect.Int8, ..., reflect.Int64:
vv, _ := strconv.ParseInt(s, 10, 64)
switch as.Kind() {
...// 對於Int,Int8, ..., Int64的處理
}
case reflect.Uint, ..., reflect.Uint64:
... // 同上處理
case reflect.String:
return reflect.ValueOf(&s), nil
}
return reflect.Value{}, nil
}
// 如果as是指標型別,則直接返回;否則,去指標
func parseValue(as reflect.Type, s string) (reflect.Value, error) {
vp, err := parseValueToPtr(as, s)
if as.Kind() == reflect.Ptr {
return vp, nil
}
return vp.Elem(), nil
}
接下來,詳細看unflattenValue
方法, 它是反序列化把 Annotations 的相關資料存放到 event 型別值中。
func unflattenValue(prefix string, v reflect.Value, t reflect.Type, kv *[][2]string) error {
... // 如果kv非有序排列,則直接panic。
... // 因為map的處理比較特殊,所以直接過濾
if v.IsValid() {
// 時間型別處理
treatAsValue := false
switch v.Interface().(type) {
case time.Time:
treatAsValue = true
}
if treatAsValue {
// 如果是time.Time型別,則使用上面提供的parseValue方法解析成event資料
vv, err := parseValue(v.Type(), (*kv)[0][1])
if vv.Type().AssignableTo(v.Type()) {
v.Set(vv)
}
*kv = (*kv)[1:]
return nil
}
}
// 其他為reflect.Kind型別
switch t.Kind() {
case reflect.Ptr:
// 去指標
return unflattenValue(prefix, v, t.Elem(), kv)
case reflect.Interface:
// 去指標
return unflattenValue(prefix, v.Elem(), v.Type(), kv)
case reflect.Bool, reflect.Float32, ... , reflect.Int, ... reflect.String:
// 解析原子型別
vv, err := parseValue(v.Type(), (*kv)[0][1])
v.Set(vv)
*kv = (*kv)[1:]
case reflect.Struct:
// 對於struct中的所有欄位都需要排序, 然後再賦值
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var vtfs []reflect.StructField
for i:=0; i < t.NumField(); i++{
f := t.Field(i)
vtfs = append(vtfs, f)
}
sort.Sort(structFieldsByName(vtfs)) // struct中的所有欄位排序,
// 然後再依次取出kv中的資料處理
for _, vtf := range vtfs {
vf := v.FieldByIndex(vtf.Index)
if vf.IsValid() {
// 形成字首, 如果不匹配,則直接跳過kv第一個欄位
fieldPrefix := nest(prefix, fieldName(vtf))
unflattenValue(fieldPrefix, vf, vtf.Type, kv)
}
}
case reflect.Map:
m := reflect.MakeMap(t)
keyPrefix := prefix + "."
found := 0
for _, kvv := range *kv {
key, val := kvv[0], kvv[1]
// 字首比較,如果當前形成的keyPrefix比較大,則kv需要跳過
if key < keyPrefix {
continue
}
// 字首比較,如果當前形成的keyPrefix小於最小key,同時還不是字首
// 則說明接下來的所有key都找不到了, 直接退出
if key > keyPrefix && !strings.HasPrefix(key, keyPrefix) {
break
}
// 找到相應的key了,並通過map反射寫入相應值
vv, _ := parseValue(t.Elem(), val)
m.SetMapIndex(reflect.ValueOf(strings.TrimPrefix(key, keyPrefix)), vv)
*kv = (*kv)[1:]
found++
}
// 完成之後再寫入到v中
if found >0 {
v.Set(m)
}
}
case reflect.Slice, reflect.Array:
// 思路:先把kv與keyPrefix字首匹配的資料全部放入到elem列表中
// 然後,再通過反射把資料寫入到v中
keyPrefix := prefix + "."
var elems []elem
maxI := 0
for _, kvv := range *kv{
key, val := kvv[0], kvv[1]
// 如果key不匹配keyPrefix字首,則說明在kv中無法找到struct,直接退出
if !strings.HasPrefix(key, keyPrefix) {
break
}
// 找到每個struct中資欄位的索引,則val就是這個索引對應的欄位值
i, err := strconv.Atoi(strings.TrimPrefix(key, keyPrefix))
elems = append(elems, elem{i, val})
if i > maxI {
maxI = i
}
*kv = (*kv)[1:]
}
if v.Kind() == reflect.Slice {
v.Set(reflect.MakeSlice(t, maxI+1, maxI+1))
}
for _, e:= range elems {
vv, err := parseValue(t.Elem(), e.s)
v.Index(e.i).Set(vv)
}
}
由上可以看出,flattenValue
和unflattenValue
兩個方法都是圍繞 event 與 annotations 的資料轉換進行序列化和反序列化的,其中在序列化時,可以看到有些方法已經序列化完成統一的 key 列表,並儲存在記憶體中,這個是一個很好的優化點。其他的除了 map 的序列化有點生澀,其他都 ok。這裡面用到的 sort.Sort 實現比較多。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Appdash原始碼閱讀——RecentStore和LimitStoreAPP原始碼MIT
- Appdash原始碼閱讀——Annotations與EventAPP原始碼
- Appdash原始碼閱讀——Recorder與CollectorAPP原始碼
- Appdash原始碼閱讀——Store儲存APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- Appdash原始碼閱讀——部分opentracing支援APP原始碼
- Appdash原始碼閱讀——Tracer&SpanAPP原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼閱讀】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 原始碼包閱讀原始碼
- 原始碼閱讀技巧篇原始碼
- 如何閱讀框架原始碼框架原始碼
- 再談原始碼閱讀原始碼
- Laravel 原始碼閱讀 - EloquentLaravel原始碼
- 如何閱讀jdk原始碼?JDK原始碼
- express 原始碼閱讀(全)Express原始碼