proto vs gzip proto in golang

我们知道proto是基于二进制编码的,比json格式的编码要节省大量空间,那么,如果对于proto编码后的结果再进行gzip压缩,是否能产生更多空间的节省呢?gzip压缩是否具有幂等性呢?本文讨论了在golang中对这两个问题的探索和研究

gzip压缩proto编码结果

准备条件

proto定义:

// 共识规则
message ReviewRule {
  string id = 1;
  string name = 2;
  string desc = 3;
  string operator = 4;
  string tx_id = 5;
  repeated string participants = 6;
  ReviewType review_type = 7;
  google.protobuf.Struct vote = 8;
}

// 共识投票类型
enum ReviewType {
  // 全体成员投票通过
  All = 0;
  // 任意成员投票通过
  OneOf = 1;
  // 指定成员投票通过
  Designation = 2;
  // 比例成员投票通过,如30%, 50%, 70%
  Scale = 3;
}

在进行这个测试时,主要研究属性对象是ReviewRule的6,7,8,会根据长度大小生成固定长度的uuid字符串(随机),然后对ReviewRule做proto编码和proto编码后的gzip压缩。同时比对gzip解压缩后和原proto编码的字节长度是否一致,确保压缩和解压缩是对proto编码的结果无影响的。

结果比对

单位:字节Byte

随机长度 proto编码后 gzip写入 gzip压缩 gzip读取 gzip解压缩 gzip节省空间比率(%)
0 413 413 325 413 413 21.31
1 499 499 388 499 499 22.24
2 583 583 439 583 583 24.70
20 2099 2099 1240 2099 2099 40.92
200 17219 17219 8670 17219 17219 49.65
2000 168423 168423 82245 168423 168423 51.17
20000 1680423 1680423 816971 1680423 1680423 51.38

备注:gzip写入和gzip读取是为了保证在gzip处理过程中没有发生数据丢失或复写。

从上述表格中可以看到,gzip压缩后确实能在proto编码后再次降低使用的空间大小的,甚至数据量越大,压缩比越高,1.6G的数据大约可以降到800M不到,超过了50%。且解压缩后,数据大小仍然保持一致。

那么压缩的结果是否每次都能保持完全一致呢?

gzip压缩的幂等性

同样,在这个测试时,也是生成了长度为20000的随机参数ReviewRule。

结果比对

压缩次数 压缩结果base64编码结果数 压缩结果长度结果数
2 1 1
5 1 1
10 1 1
20 1 1
50 1 1
100 1 1

可以看到,对于相同的结果,压缩结果大小和base64编码后的结果不随压缩次数的增加而发生变化,因此可以推断gzip压缩是具有幂等性的,即压缩的结果每次都能保持完全一致。

附录

go版本:1.17.9
完整测试文件:

package v1

import (
    "bytes"
    "compress/gzip"
    "context"
    "encoding/base64"
    "fmt"
    "io"
    "testing"

    "github.com/google/uuid"
    "github.com/pkg/errors"
    "github.com/stretchr/testify/assert"
    "golang.org/x/sync/errgroup"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/known/structpb"
)

func newTestReviewRule() *ReviewRule {
    rr := new(ReviewRule)
    rr.Id = uuid.New().String()
    rr.Name = "ReviewRule"
    rr.Desc = `// Code generated by protoc-gen-go. DO NOT EDIT.
    // versions:
    //  protoc-gen-go v1.28.0
    //  protoc        v3.9.0
    // source: pkg/contracts/fabric/review/v1/review.proto`
    rr.Operator = "x509::CN=hlp,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=AR"
    rr.TxId = uuid.New().String()
    return rr
}

func fillRandomParams(rr *ReviewRule, length int) error {
    rr.Participants = genStringSlice(length)
    rr.ReviewType = ReviewType(length % (len(ReviewType_name) - 1))

    vote, err := structpb.NewStruct(map[string]interface{}{
        "vote": genInterfaceSlice(length),
    })
    if err != nil {
        return errors.Wrap(err, "structpb.NewStruct")
    }
    rr.Vote = vote

    return nil
}

func Test_proto_gzip(t *testing.T) {
    rr := newTestReviewRule()
    tests := []struct {
        name   string
        length int
    }{
        {"0", 0},
        {"1", 1},
        {"2", 2},
        {"20", 20},
        {"200", 200},
        {"2000", 2000},
        {"20000", 20000},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            assert.Nil(t, fillRandomParams(rr, tt.length))

            protoBytes, err := proto.Marshal(rr)
            assert.Nil(t, err)
            var buf bytes.Buffer
            zw := gzip.NewWriter(&buf)

            i, err := zw.Write(protoBytes)
            assert.Nil(t, err)
            assert.Nil(t, zw.Close())

            fmt.Printf("name[%s]: proto[%d], gzip wi[%d], gzip[%d], ratio[%.2f], ", tt.name, len(protoBytes), i, buf.Len(), float64(i-buf.Len())/float64(i)*100)
            zr, err := gzip.NewReader(&buf)
            assert.Nil(t, err)
            defer zr.Close()

            var buf_ bytes.Buffer
            i_, err := io.Copy(&buf_, zr)
            assert.Nil(t, err)

            fmt.Printf("gzip ri[%d], read[%d]\n", i_, buf_.Len())
        })
    }
}

func genStringSlice(length int) []string {
    s := make([]string, length)
    for i := 0; i < length; i++ {
        s = append(s, genRandomString())
    }

    return s
}

func genInterfaceSlice(length int) []interface{} {
    s := make([]interface{}, length)
    for i := 0; i < length; i++ {
        s = append(s, genRandomString())
    }

    return s
}

func genRandomString() string {
    return uuid.New().String()
}

func Test_idempotent_gzip(t *testing.T) {
    rr := newTestReviewRule()
    assert.Nil(t, fillRandomParams(rr, 20000))
    protoBytes, err := proto.Marshal(rr)
    assert.Nil(t, err)
    tests := []struct {
        name      string
        frequency int
    }{
        {"2", 2},
        {"5", 5},
        {"10", 10},
        {"20", 20},
        {"50", 50},
        {"100", 100},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            stream := make(chan []byte)
            eg, egCtx := errgroup.WithContext(context.Background())
            for i := 0; i < tt.frequency; i++ {
                eg.Go(func() error {
                    var buf bytes.Buffer
                    zw := gzip.NewWriter(&buf)
                    _, err = zw.Write(protoBytes)
                    if err != nil {
                        return errors.Wrap(err, "zw.Write")
                    }

                    if err = zw.Close(); err != nil {
                        return errors.Wrap(err, "zw.Close")
                    }

                    select {
                    case <-egCtx.Done():
                        return errors.New("eg context done")
                    case stream <- buf.Bytes():
                    }

                    return nil
                })
            }

            go func() {
                eg.Wait()
                close(stream)
            }()

            base64Set := make(map[string]interface{})
            lenSet := make(map[int]interface{})
            for gzipBytes := range stream {
                base64Set[base64.StdEncoding.EncodeToString(gzipBytes)] = struct{}{}
                lenSet[len(gzipBytes)] = struct{}{}
            }
            assert.Nil(t, eg.Wait())
            assert.Len(t, base64Set, 1)
            assert.Len(t, lenSet, 1)
        })
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容

  • Java IO知识图谱 1 OSI七层网络模型 上下层之间遵循的约定叫做"接口",同层之间遵循的约定叫做"协议"....
    allen锅阅读 924评论 0 1
  • gzip 使用deflate算法进行压缩。gzip 对于要压缩的文件,首先使用LZ77算法的一个变种进行压缩,对得...
    tracy_668阅读 8,676评论 3 8
  • HTTP 请求头 **Accept-Encoding** 会将客户端能够理解的内容编码方式——通常是某种压缩算法—...
    伯约同学阅读 383评论 0 0
  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,155评论 2 33
  • 系统管理与维护命令 date date(选项)(参数) | 选项 | 说明 | | :-------- | ...
    蓓蓓的万能男友阅读 3,877评论 0 5