Skip to content

流量存储

hueng edited this page Apr 16, 2020 · 8 revisions

流量存储

背景

为了方便检索,线上的流量会经过json.Marshal存入ES,传统方式json.Marshal会存在以下问题:

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

  • 示例
type Session struct {
    Request []byte
}
s := &Session{Request: []byte{104, 101, 108, 108, 111, 26}} // string(s.Request) == "hello"

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

json.Marshal会对[]byte字段进行base64编码,「如上示例:base64("hello") == "aGVsbG8="」,这样在ES里存储的时候就无法全文检索了,至少原生不支持base64解码再分词。

参考:encodeByteSlice

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

  • 示例
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"}
  • 问题

json.Marshal会对string字段做utf8合法性校验,对于非法字符会转换成\ufffd,如上面示例,我们没法区分不合法的utf8字符239240

方案

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

1、编码

  • 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),
    })
}
  • 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.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、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]
Clone this wiki locally