详解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
Protocol buffers
有点TLV的意思(type-length-value)
FieldInfo
包含了存储field_number
(字段编号), data_type
表示字段类型
Type | Meaning | Used 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
}
参考资料
详解PROTOCOL BUFFERS的更多相关文章
- Protocol Buffers编码详解,例子,图解
Protocol Buffers编码详解,例子,图解 本文不是让你掌握protobuf的使用,而是以超级细致的例子的方式分析protobuf的编码设计.通过此文你可以了解protobuf的数据压缩能力 ...
- 前端后台以及游戏中使用Google Protocol Buffer详解
前端后台以及游戏中使用Google Protocol Buffer详解 0.什么是protoBuf protoBuf是一种灵活高效的独立于语言平台的结构化数据表示方法,与XML相比,protoBuf更 ...
- Protocol Buffer技术详解(数据编码)
Protocol Buffer技术详解(数据编码) 之前已经发了三篇有关Protocol Buffer的技术博客,其中第一篇介绍了Protocol Buffer的语言规范,而后两篇则分别基于C++和J ...
- Protocol Buffer技术详解(Java实例)
Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...
- Protocol Buffer技术详解(C++实例)
Protocol Buffer技术详解(C++实例) 这篇Blog仍然是以Google的官方文档为主线,代码实例则完全取自于我们正在开发的一个Demo项目,通过前一段时间的尝试,感觉这种结合的方式比较 ...
- Protocol Buffer技术详解(语言规范)
Protocol Buffer技术详解(语言规范) 该系列Blog的内容主体主要源自于Protocol Buffer的官方文档,而代码示例则抽取于当前正在开发的一个公司内部项目的Demo.这样做的目的 ...
- Protocol Buffer详解
1.Protocol Buffer 概念 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 ...
- 开源点评:Protocol Buffers介绍
今天来介绍一下“Protocol Buffers”(下面简称protobuf)这个玩意儿.本来俺在构思“生产者/消费者模式 ”系列的下一个帖子:关于生产者和消费者之间的传输数据格式.因为里面扯到了pr ...
- (转)Linux 系统设置 : dmesg 命令详解
原文:https://blog.csdn.net/yexiangCSDN/article/details/80683246 https://www.cnblogs.com/duanxz/p/34770 ...
随机推荐
- 数据结构排序算法插入排序Java实现
public class InsertDemo { public static void main(String args[]) { int[] sort ={4,2,1,3,6,5,9,8,10,7 ...
- PL2303HX在Windows 10下面不装安装驱动的解决办法(Code:10)
Prolific在很早之前推出了一款名为PL2303HX的芯片, 用于USB转RS232, 这款芯片使用的范围非常广, 并且年代久远. 但是这款芯片因为用的特别多, 所以中国就有很多厂家生产了仿造的P ...
- nginx 详细配置
Nginx全局变量 Nginx中有很多的全局变量,可以通过$变量名来使用.下面列举一些常用的全局变量: 变量 说明 boxClass 需要执行动画的元素的 变量 说明 $args 请求中的参数,如ww ...
- mpvue——仿QQ【一】
前言 原生仿QQ https://github.com/wangyang0210/Imitate-QQ-For-Mini-Program 这个是当时学习小程序时,模仿的一个demo,只不过是纯页面没啥 ...
- Codeforces1153F Serval and Bonus Problem 【组合数】
题目分析: 我们思考正好被k个区间覆盖的情况,那么当前这个子段是不是把所有的点分成了两个部分,那么在两个部分之间相互连k条线,再对于剩下的分别连线就很好了?这个东西不难用组合数写出来. 然后我们要证明 ...
- jQuery与原生JS相互转化
前端发展很快,现代浏览器原生 API 已经足够好用.我们并不需要为了操作 DOM.Event 等再学习一下 jQuery 的 API.同时由于 React.Angular.Vue 等框架的流行,直接操 ...
- JDK源码分析(8) StringBuffer & StringBuilder
简介 StringBuffer与StringBuilder是两个常用的操作字符串的类.大家都知道,StringBuilder是线程不安全的,而StringBuffer是线程安全的.前者是JDK1.5加 ...
- Linux核心命令使用方法
一.Linux命令行常用快捷键 ctrl + c cancel 取消当前的操作 ctrl + l (小写字母L) clear(命令)清空当前屏幕 ctrl + d 退出当前用户 ctrl + r 查找 ...
- I2C(四)linux3.4(写代码)
title: I2C(四)linux3.4(写代码) date: 2019/1/29 17:18:42 toc: true --- I2C(四)linux3.4(写代码) 老师的参考代码 https: ...
- python之路(9)反射、包装类、动态模块导入
目录 反射 利用继承二次包装标准类 利用授权二次包装标准类 动态模块导入 反射 python提供自省的四个方法: hasattr(object,name) 判断object中有没有有个name字符串 ...