Appdash原始碼閱讀——reflect

cdh0805010118發表於2018-07-12

我們在Appdash 原始碼閱讀——Annotations 與 Event一節中,瞭解到 Appdash 產品的 Span 資訊儲存包括兩部分 SpanID 和 Annotations,因為 Appdash 出生比 OpenTracing 標準早,所以沒有遵循後者的標準。Span 除了自身資訊,其他資訊全部是儲存在 Annotations 中,它是 slice 結構,且裡面是 key-value 儲存方式。這裡面存放了 event 事件,為了實現 Event 和 Annotations 的資料儲存轉換,需要提供序列化和反序列化操作。這就引入了 reflect。

這節我們來看看 Appdash 的反射操作。順便了解了解 reflect

校驗兩個變數是否相等,取決於兩部分是否相等。1. 變數型別是否相等;2. 變數值是否相等

reflect

  1. 在把 event 序列化為 Annotations 時,如果 event 沒有提供 MarshalEvent 方法,則就使用預設的序列化方法flattenValue
  2. 在把 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, parseValueparseValueToPtr

因為 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] }

給定一個型別和字串,轉換成指定的資料型別值方法parseValueparseValueToPtr

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)
        }
}

由上可以看出,flattenValueunflattenValue兩個方法都是圍繞 event 與 annotations 的資料轉換進行序列化和反序列化的,其中在序列化時,可以看到有些方法已經序列化完成統一的 key 列表,並儲存在記憶體中,這個是一個很好的優化點。其他的除了 map 的序列化有點生澀,其他都 ok。這裡面用到的 sort.Sort 實現比較多。

更多原創文章乾貨分享,請關注公眾號
  • Appdash原始碼閱讀——reflect
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章