zoukankan      html  css  js  c++  java
  • 详解PROTOCOL BUFFERS

    
    

    1. 前言

    Protocal Buffers是google推出的一种序列化协议。由于它的编码和解码的速度,已经编码后的大小控制的较好,因此它常常被用在RPC调用中,传递参数和结果。比如gRPC

    Protocal Buffers的实现非常简单,本文将对比JSON协议,来聊聊Protocol Buffers的实现以及它高性能的秘密

    2. 正篇

    2.1 减少传输量(字段名和定界符)

    汽车类在Golang中的定义

    type Car struct {
        Age   int32   `json:"age"`
        Color string  `json:"color"`
        Price float32 `json:"price"`
    }
    

    JSON字符串表示

    {
        "age": 10,
        "color": "red",
        "price": 15.2568983
    }
    

    1)”{” 、”}”、”[“, “]”、 双引号、”,” 、”:” 是为了把字段与字段之间,以及字段的名称和值分隔开。它们不是必须的。
    2)字段的名称”age”、”color”、”price”也不是必须的。
    如果发送方和接收方都对对象的定义是明晰的,那么字段的名称也不要传递

    Protocol Buffers对象定义

    message Car {
        int32 age = 1;
        string color = 2;
        double price = 3;
    }
    

    每个字段都有一个编号,比如在例子中,age是1,color是2,price是3
    接收方只要拿到编号,就可以知道需要解析的是哪个字段,它对应的名字甚至是字段值的长度

    下图是对Protocol buffers编码的说明 图1

    image_1d7gtlrraorc1duvncp11m238s23.png-21.9kB

    Protocol buffers有点TLV的意思(type-length-value)

    FieldInfo 包含了存储field_number(字段编号), data_type表示字段类型

    TypeMeaningUsed For
    0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
    1 64-bit fixed64, sfixed64, double
    2 Length-delimited string, bytes, embedded messages, packed repeated fields
    3 Start group groups (deprecated)
    4 End group groups (deprecated)
    5 32-bit fixed32, sfixed32, float
    • 对于 64-bit 32-bit得到类data_type,也就得到了长度
    • 对于 Varint 可以在解析的过程得到value
    • 对于 类似Length-delimited 稍微有点特殊,有额外的字段length表示value字节的长度

    注 Varint是对整型的变长表示,它与ES中使用的整型压缩算法是完全一致的。参见我的文章VINT–针对INT型的压缩格式
    由于Protocol Buffers 有type和length信息的存在,因此无需字段名称和JSON中的”{“等定界符

    2.2 减少传输量(整型和浮点数)

    由于JSON属于文本型协议,因此它传输的数据都是字符

    • 对于较大的整数,var int32 age = 123456789 传输时会变成”123456789″ 需要消耗9个字节
    • 对于浮点数,如果出现小数部分 var float32 price = 15.2568983
      传输时,会变成”15.2568983″

    Protocol Buffers中,int32按Varint存储,平均开销不到3个字节,而float32按照固定4字节存储,这样一来就比JSON少了不少

    2.3字段可选

    Protocol Buffers中允许指定某个字段是optional(可选的)。如果该字段没有值,则编码时,这个字段不会占用任何字节。

    在一些语言的JSON库包中,如果解码时,该字段在JSON字符串中不存在,则会直接报错。

    2.4 解码时的优势

    2.4.1 跳过数据结构

    JSON 是一个没有 header 的格式。因为没有 header,JSON 需要扫描每个字节才可以定位到所需的字段上。中间可能要扫过很多不需要处理的字段。

    message PbTestWriteObject {
      repeated string field1 = 1;
      message Field2 {
        repeated string field1 = 1;
        repeated string field2 = 2;
        repeated string field3 = 3;
      }
      Field2 field2 = 2;
      string field3 = 3;
    }
    message PbTestReadObject {
      string field3 = 3;
    }
    

    消息用 PbTestWriteObject 来编码,然后用 PbTestReadObject 来解码。field1 和 field2 的内容应该被跳过。

    这是一个非常极端的例子,回顾图1中的示例,在Protocol Buffers中除了Varint类型,其余类型,都能直接得到长度信息,因此可以直接跳过不需要解析的字节,效率大大提高

    2.4.2 字符串的处理

    对于string类型的数据,JSON一般而言还需要支持unicode和UTF8 2种编码
    对于Golang,string本身就是UTF8编码的字节,因此在解码时,直接做memcopy就行

    3. 总结

    编解码数字的时候,JSON 仍然是非常慢的。Jsoniter 把这个差距从 10 倍缩小到了 3 倍多一些。

    JSON 最差的情况是下面几种:

    • 跳过非常长的字符串:和字符串长度线性相关。
    • 解码 double 字段:Protobuf 优势明显,是 Jsoniter的 3.27 倍,是 Jackson 的 13.75 倍。
    • 编码 double 字段:如果不能接受只保留 6 位小数,Protobuf 是 Jackson 的 12.71 倍。如果接受精度损失,Protobuf 是 Jsoniter 的 1.96 倍。
    • 解码整数:Protobuf 是 Jsoniter 的 2.64 倍,是 Jackson 的 8.51 倍。

    如果你的生产环境中的 JSON 没有那么多的 double 字段,都是字符串占大头,那么基本上来说替换成 Protobuf 也就是仅仅比 Jsoniter 提高一点点,肯定在 2 倍之内。如果不幸的话,没准 Protobuf 还要更慢一点。

    Protocol Buffers在极端场景下对JSON的速度优势,可以达到5倍左右,但是它本身与Gzip等比较,不算是一种压缩算法。它可以被表述为更为紧凑的序列化协议。对于针对它序列化的结果,再使用其它压缩算法进行一步压缩。

    4. 代码参考

    对于不同类型字段的序列化(编码)主要在
    table_marshal.go 中的typeMarshaler函数

    针对 32-bit 的编码

    func appendFixedS32Ptr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
        p := ptr.getInt32Ptr()
        if p == nil {
            return b, nil
        }
        b = appendVarint(b, wiretag)
        b = appendFixed32(b, uint32(*p))
        return b, nil
    }
    

    针对 string 的编码

    func appendStringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
        v := *ptr.toString()
        b = appendVarint(b, wiretag) //
        b = appendVarint(b, uint64(len(v)))
        b = append(b, v...)
        return b, nil
    }
    

    参考资料

      1. Protocol Buffers-encoding
      2. wikipedia–Protocol_Buffers
      3. 陶文-Protobuf 有没有比 JSON 快 5 倍?
  • 相关阅读:
    poj 3253 Fence Repair (优先队列,哈弗曼)
    容斥原理 (转载)
    poj 1088 滑雪 DP(dfs的记忆化搜索)
    饭卡 01背包 + 贪心
    N分之一 竖式除法模拟
    poj2325 大数除法+贪心
    优先队列重载运算符< 以及初始化列表
    POJ 2718 Smallest Difference(贪心 or next_permutation暴力枚举)
    HASH算法
    字符串匹配算法——KMP算法
  • 原文地址:https://www.cnblogs.com/sunsky303/p/10670674.html
Copyright © 2011-2022 走看看