Skip to content

流量存储

hueng edited this page Apr 29, 2020 · 8 revisions

流量存储

一、背景

为了方便检索,线上的流量会经过json.Marshal存入ES,常见存储流量方式有下列两种:

方式一、用[]byte字段存储流量

问题:json.Marshal会对[]byte字段进行base64编码,这样在ES里存储的时候就无法全文检索了,至少原生不支持base64解码再分词。

参考:encodeByteSlice

type Session struct {
    Request []byte
}

s := &Session{Request: []byte{104, 101, 108, 108, 111, 26}}

res, _ := json.Marshal(s)
fmt.Println(string(res)) // {"Request":"aGVsbG8="}

// string(s.Request) == "hello"
// base64("hello") == "aGVsbG8="

方式二、用string字段存储流量

问题:json.Marshal会对string字段做utf8合法性校验,对于非法字符都会转换成\ufffd,没法区分。

参考:stringBytes

type Session struct {
    Request string
}
s := &Session{Request: string([]byte{104, 101, 108, 108, 111, 239, 240})}

res, _ := json.Marshal(s)
fmt.Println(string(res)) // {"Request":"hello\ufffd\ufffd"}

// 问题:没法区分不合法的utf8字符:239、240

二、方案

重写 string 的 json 序列化代码,对于不合法的 utf8 字符转码成了 \\x00格式。 注意有两个 \,因为这个是json转义了的,\x本身并不是被 json 支持。 如果原有的输入里就有 \ 转义的,把 \ 也要做 \x 的转义。

2.1、编码

2.1.1、重写MarshalJSON方法

新增函数EncodeAnyByteArray转义不合法的 utf8 字符。

type Session struct {
    Request []byte
}

func (s *Session) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        Session
        Request json.RawMessage
    }{
        Session: *s,
        Request:  EncodeAnyByteArray(s.Request),
    })
}

2.1.2、Marshal示例

合法的utf8字符不会经过base64编码方便检索,合法的utf8字符也不会出现信息丢失。

s := &Session{Request: []byte{104, 101, 108, 108, 111, 239, 240}}
res, _ := json.Marshal(s)
fmt.Println(string(res)) // {"Request":"hello\\xef\\xf0"}

2.2、解码

2.2.1、重写UnmarshalJSON方法

type Session struct {
    Request Raw
}

type Raw struct {
    Data []byte
}

func (r *Raw) UnmarshalJSON(data []byte) error {
    // step1: unquote string
    tmp, err := strconv.Unquote(string(data))
    if err != nil {
        return err
    }

    // step2: stripcslashes:(把 \x 解开)才能取到真正原始的 []byte,参考php stripcslashes方法
    r.Data = StripcSlashes([]byte(tmp))
    return nil
}

2.2.2、Unmarshal示例

合法的utf8字符不会经过base64编码方便检索,不合法的utf8字符也不会出现信息丢失。

str := `{"Request":"hello\\xef\\xf0"}` // 新方案json Marshal之后的字符串

var s Session
json.Unmarshal([]byte(str), &s)
fmt.Println(s.Request.Data) // 还原原始byte数组,[104 101 108 108 111 239 240]