美文网首页
GoLang Redis存储结构体方式对比

GoLang Redis存储结构体方式对比

作者: wp_nine | 来源:发表于2019-05-08 21:49 被阅读0次

    Redis 作为一个非关系数据库,以key-value 的方式存储数据,在后台开发时常被用于处理缓存。在golang 中的结构体的存储也是经常需要接触到,以下是简单结构和数据结构对几种存储的方式的对比。

    简单结构

    1.hash类型方式

    
    func DoHashStore(conn redis.Conn)  {
        //以hash类型保存
        conn.Do("hmset",redis.Args{"struct1"}.AddFlat(testStruct)...)
        //获取缓存
        value, _ := redis.Values(conn.Do("hgetall",  "struct1"))
        //将values转成结构体
        object := &TestStruct{}
        redis.ScanStruct(value, object)
    }
    
    

    利用redis库自带的Args 和 AddFlat对结构体进行转换。然后以hash类型存储。该方式实现简单,但存在最大的问题是不支持数组结构(如:结构体中内嵌结构体、数组等)。

    2.Gob Encoding方式

       func DoGobEncodingStore(conn redis.Conn)  {
        //将数据进行gob序列化
        var buffer bytes.Buffer
        ecoder := gob.NewEncoder(&buffer)
        ecoder.Encode(testStruct)
        //reids缓存数据
        conn.Do("set","struct2",buffer.Bytes())
        //redis读取缓存
        rebytes,_ := redis.Bytes(conn.Do("get","struct2"))
        //进行gob序列化
        reader := bytes.NewReader(rebytes)
        dec := gob.NewDecoder(reader)
        object := &TestStruct{}
        dec.Decode(object)
    }
    

    该方式利用gob.NewEncoder 对结构体进行,该方式可支持复杂的数据结构,但实现相对比在代码实现上稍微复杂。

    3.JSON Encoding 方式

    func DoJsonEncodingStore(conn redis.Conn)  {
        //json序列化
        datas,_ := json.Marshal(testStruct)
        //缓存数据
        conn.Do("set","struct3",datas)
        //读取数据
        rebytes,_ := redis.Bytes(conn.Do("get","struct3"))
        //json反序列化
        object := &TestStruct{}
        json.Unmarshal(rebytes,object)
    }
    

    该方式同gob差不多,区别是将结构体转换成json格式,实现也相对简单。另外采用json序列化,在后台开发提供数据时,不一定要对数据进行json反序列化,可直接以json格式传递到前端。

    4.Redis连接

    var Conn = ConnectRedis()
    
    /**测试服务连接
     */
    func ConnectRedis() redis.Conn {
        conn, err := redis.Dial("tcp", "192.168.238.131:6379")
        if err != nil {
            fmt.Println("连接失败", err)
            return nil
        }
    
        return conn
    }
    

    5.测试数据

    var testStruct = provider.CreateTestData(1111)
    
    func CreateTestData(id int) *TestStruct {
        return &TestStruct{
            Id:    id,
            Name:  "测试姓名",
            Sex:   "男",
            Desc:  "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc1: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc2: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc3: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc4: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc5: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc6: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc7: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
            Desc8: "描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
        }
    }
    
    type TestStruct struct {
        Id    int    `redis:"id" json:"id"`
        Name  string `redis:"name" json:"name"`
        Sex   string `redis:"sex" json:"sex"`
        Desc  string `redis:"desc" json:"desc"`
        Desc1 string `redis:"desc1" json:"desc1"`
        Desc2 string `redis:"desc2" json:"desc2"`
        Desc3 string `redis:"desc3" json:"desc3"`
        Desc4 string `redis:"desc4" json:"desc4"`
        Desc5 string `redis:"desc5" json:"desc5"`
        Desc6 string `redis:"desc6" json:"desc6"`
        Desc7 string `redis:"desc7" json:"desc7"`
        Desc8 string `redis:"desc8" json:"desc8"`
    }
    
    

    6.测试代码

    当前采用简单的结构来测试3种方式的性能,对数据进行数据以下的性能测试,代码如下:

    package main
    
    import (
        "GoTest/redis/provider"
        "testing"
    )
    
    const COUNT = 100000
    
    func BenchmarkDoHash(t *testing.B)  {
        for i:=0;i<COUNT;i++{
            DoHashStore(provider.Conn)
        }
    }
    func BenchmarkDoEncodingStore(t *testing.B)  {
        for i:=0;i<COUNT;i++ {
            DoGobEncodingStore(provider.Conn)
        }
    }
    func BenchmarkDoJsonEncodingStore(t *testing.B)  {
        for i:=0;i<COUNT;i++ {
            DoJsonEncodingStore(provider.Conn)
        }
    }
    

    每个方法各执行 10000次(数据较少三个对比不明显),执行命令
    go test -bench=. -cpu=2 -benchmem -memprofile mem.out -cpuprofile cpu.out
    得到以下结果

    goos: darwin
    goarch: amd64
    pkg: GoTest/redis/test1
    BenchmarkDoHash-2                              1        106160710769 ns/op      540865952 B/op   9400087 allocs/op
    BenchmarkDoEncodingStore-2                     1        113206439360 ns/op      1824194888 B/op 30100636 allocs/op
    BenchmarkDoJsonEncodingStore-2                 1        110593207287 ns/op      381486840 B/op   2101649 allocs/op
    PASS
    ok      GoTest/redis/test1      330.135s
    

    从上面的数据可知,在简单结构体的存储中
    执行效率 hash类型 > JSON Encoding > Gob Encoding
    内存占用 JSON Encoding < hash类型 < Gob Encoding

    接下来我们再来分析导致其结果的原因,执行以下命令:
    go tool pprof -svg cpu.out > cpu.svg
    go tool pprof -svg mem.out > mem.svg
    利用 pprof 分析样本的集合,并生成可视报告(cpu.svg 和 mem.svg),然后先用浏览器打开cpu.svg,查看其执行效率的瓶颈,见下图

    简单结构_cpu_svg.png

    根据其采样对比,主要有三个因素影响:1.redis 缓存读写 2.序列化Encode 3.反序列化Decode(严格讲Hash不是序列化,但是现把它的转换过程也归为一类)

    redis 缓存读写对比

    hash类型(9.12s) < JSON(10.57s) < Gob(14.19s)

    以上结果个人估计可能是主要是受内容大小影响,hash的长度在三个中肯定上是最短的,它不需要额外的序列化内容,所以它是最快的,而gob序列化后是1115字节,json序列化后1158字节。

    序列化对比

    Encode: JSON(低于0.42s) < Hash(0.45s) < Gob(0.47s)
    Decode: Hash(低于0.42s) < JSON(1.80s) < Gob(2.61s)

    根据以上对比,Encode中JSON的优秀是最好的,这有点出乎我的意料,竟然比AddFlat该方法还快,估计AddFlag在切片的处理上不够好。Decode的话最快是Hash,因为它只是对结构体的反射过程并赋值,不需要做数据的解析。

    接着再用浏览器打开mem.svg,分析其内存占用,见下图:


    简单结构_mem_svg.png

    根据分析,也是主要是原来的三个因素影响(1.redis 缓存读写 2.序列化Encode 3.反序列化Decode)

    对比结果

    读写: JSON(0.11GB) <Gob(0.12GB) < Hash(0.21GB)
    Encode: JSON(0.11GB) < Hash(0.17GB) < Gob(0.49GB)
    Decode: JSON(0.10GB) = Hash(0.10GB) < Gob(0.96GB)

    Json在序列化和反序列化算法上内存处理是最好的,而Hash结构的内存占用高主要原因是Redis自身读写的原因。

    数组结构

    由于Hash类型不支持复杂的结构体,因为现在只对比Gob和JSON两种方式

    1.测试数据

    var testComplexStruct =provider.CreateComplexData(2000)
    
    func CreateComplexData(count int) []TestStruct{
        data := make([]TestStruct,0,count)
        for i := 0;i<count;i++{
            data = append(data,*CreateTestData(i))
        }
        return data
    }
    

    构造了一个长度1000的数组作为测试数据

    func DoComplexJSONStore(conn redis.Conn){
        //序列化数组
        datas,_ := json.Marshal(testComplexStruct)
        //缓存数据
        conn.Do("set","complex2",datas)
        //读取数据
        rebytes,_ := redis.Bytes(conn.Do("get","complex2"))
        //json反序列化
        var object []provider.TestStruct
        json.Unmarshal(rebytes,&object)
    
    }
    
    
    func DoComplexGobEncodingStore(conn redis.Conn)  {
        //序列化数组
        var buffer bytes.Buffer
        ecoder := gob.NewEncoder(&buffer)
        ecoder.Encode(testComplexStruct)
        //缓存数据
        conn.Do("set","complex3",buffer.Bytes())
        //读取数据
        rebytes,_ := redis.Bytes(conn.Do("get","complex3"))
        //反序列化
        reader := bytes.NewReader(rebytes)
        dec := gob.NewDecoder(reader)
        var object []provider.TestStruct
        dec.Decode(&object)
        
    }
    

    3.测试代码

    package main
    
    import (
        "GoTest/redis/provider"
        "testing"
    )
    
    const COMPLEX_COUNT = 100
    func BenchmarkDoComplexGobEncoding(t *testing.B)  {
        for i:=0;i<COMPLEX_COUNT;i++ {
            DoComplexGobEncodingStore(provider.Conn)
        }
    }
    func BenchmarkDoComplexJsonEncoding(t *testing.B)  {
        for i:=0;i<COMPLEX_COUNT;i++ {
            DoComplexJSONStore(provider.Conn)
        }
    }
    

    每个各执行100次redis缓存的读写

    4.执行命令

    执行命令 go test -bench=. -cpu=2 -benchmem -memprofile mem.out -cpuprofile

    cpu.out
    goos: darwin
    goarch: amd64
    pkg: redis_struct_test/test2
    BenchmarkDoComplexGobEncoding-2                1        4310092054 ns/op        2017807576 B/op  2234344 allocs/op
    BenchmarkDoComplexJsonEncoding-2               1        7997610854 ns/op        1350396552 B/op  2405649 allocs/op
    PASS
    ok      redis_struct_test/test2 12.441s
    

    从结果上看,JSON的内存占用还是比Gob有优势,但在性能上Gob已经远远甩开了JSON。先看下面cpuprofile文件进行分析:

    WechatIMG309.png

    根据样本分析,Gob的Encode和Decode的数组结构体的算法处理会比JSON效率高出很多,并且在数组格式的序列化后,内容也比json格式的短,才导致redis读写速度 JSON也比Gob慢。

    再看看其内存采样对比


    WechatIMG310.png

    虽然Gob在序列化后的数据已经比JSON小,但是JSON的在Decode和Encode算法中的内存处理依然比Gob要好

    总结:

    1.hash存储方式在简单结构体效率最高,但不支持复杂的结构体
    2.json在内存的占用是最少的,在简单结构体效率比gob要高。另外还有优点是业务处理中json不一定需要反序列化处理,可直接传递给前端。
    3.Gob虽然在内存占用上没有优势,但在数组结构上优势已经远远超过了JSON

    参考:

    https://studygolang.com/articles/13252
    http://ju.outofmemory.cn/entry/77882

    代码:

    https://github.com/wpnine/redis_struct_test

    以上只是个人的测试总结,如果有哪里不足或不对的地方希望路过的大神能帮忙小弟指出,学习学习,在此先道谢。

    相关文章

      网友评论

          本文标题:GoLang Redis存储结构体方式对比

          本文链接:https://www.haomeiwen.com/subject/bvfboqtx.html