Protocol Buffers工作原理
这里记录一下学习与使用Protocol Buffer的笔记,优点缺点如何使用这里不再叙述,重点关注与理解Protocol Buffers的工作原理,其大概实现。
我们经常使用Protocol Buffer进行序列化与反序列化。理解Protocol Buffer的工作原理,就要理解序列化与反序列化。
- 序列化:将数据结构或对象转换为二进制串的过程;
- 反序列化:序列化的逆过程;
如何实现呢?核心有两点:编码 + 存储。数据在计算机间通过网络进行传输时,传输的是比特流,只有0和1,并没有你所定义的各种类对象等,你如果想将一个类对象传输到对方,怎么办呢?字符是用过ASCII码编码的,这里也可以设计一套编码方案来对类似类对象这种数据进行编码,只要对方收到后能正确的解码就可以了。编码后还要确定编码后的数据存储方式,这样字节流才是有意义的字节流,这样才能知道读取的字节流有什么含义,代表什么。
好了,我们看一下Protocol Buffer是如何编码和存储的。
Protocol Buffer是如何编码的
Varint编码
Varint编码是一种变长的编码方式,核心思想是对数值越小的数字,用越少的字节数表示,这样可以减少数字的字节数,进行数据压缩。
举个例子:对int
数据类型,一般需要4个字节来表示,而实际上,对与数值较小的数字而言,无需这么多字节,00000000 00000000 00000000 01111111 | 127
,只需要一个字节就能表示,前面3个字节意义不大,浪费了许多空间。
当然,这种编码并不是所有情况下都会变小,当数值非常大时,所需的字节会增多,但因为大多数情况下数值小的数字远比数值大的多,所以整体看来,数据是被压缩了的。
具体的,Varint编码时,对每个字节的最高位赋予特殊含义:
- 1:表示后序的字节也是该数字的一部分;
- 0:表示这是最后一个字节,且剩余7bit都用来表示数字(所以Varint解码时,如果读到最高位为0的字节时,就表示已经是Varing的最后一个字节);
因为每个字节的最高位都被占用,用来表示特殊的含义,所以,当数值非常大时,原有的字节数就不够用了,所以编码时要增加字节数。
可以参考下图加深理解:
编码示例:
解码示例:
还有一个问题:就是负数时怎么办?计算机中数值用补码形式表示和存储(负数在计算机中往往用最位1来表示负数,0表示正数,负数的补码最高位为1),那按Varint编码方式所有的负数都需要增加一个字节表示,这是不能被接受的,解决方法便是下面要讲的zigzag编码。
Zigzag编码
Zigzag编码是一种变长的编码方式。zigzag按绝对值升序排列,将整数hash成递增的数值序列,哈希函数为h(n)=(n<<1)^(n>>31)
,对应地long型(64)位的哈希函数为h(n)=(n<<1)^(n>>63)
。
n | hex | h(n) | zigzag(hex) |
---|---|---|---|
0 | 00 00 00 00 | 00 00 00 00 | 00 |
-1 | ff ff ff ff | 00 00 00 01 | 01 |
1 | 00 00 00 01 | 00 00 00 02 | 02 |
-2 | ff ff ff fe | 00 00 00 03 | 03 |
2 | 00 00 00 02 | 00 00 00 04 | 04 |
... | ... | ... | ... |
可以看到,zigzag编码将正数和负数重新映射为递增的无符号数,其主要目的就是使绝对值小的数值映射为小的无符号数,已方便后序压缩字节。
zigzag编码还有很多细节,比如为了保证编码的唯一可译性,需对哈希值进行前缀码编码,这里不再细述。
解码为编码的逆,首先将zigzag编码还原成哈希值,然后用哈希函数h
的逆h’(n)=(n>>>1)^-(n&1)
得到原始整数值。
这里只重点讲Varint编码和Zigzag编码,像string、double等类型这里不再描述,可参考文后的参考文档。
T-L-V的数据存储方式
T:tag,L:length,V:value
。以标识-长度-字段值
表示单个数据,最终将所有数据拼接成一个字节流,从而实现数据存储的功能。
其中Length可选存储,如存储Varint编码数据就不需要存储Length,因为可根据每个字节最高bit位来判断这个字节是不是该数据段的最后一个字节。
这里重点说一下Tag
。Tag用来标识字段,通过Tag能获知这段字节流是属于什么类型数据的,其定义为:Tag = (field_number << 3) | wire_type
。这样,解包时就可根据tag将value对应消息中的字段。
Tag占用一个字节的长度(如果标识号超过了16,则多占用一个字节的位置,原因是field_number左移了3位,编码方式为Varint&Zigzag,编码的时候一个字节不够用了)。
message ChannelDataAck {
bytes uuid = 1; //这里的1 、2就是field_number
uint32 result = 2;
}
Tag(字段标识号)在序列化和反序列化过程中非常重要。举一个应用中非常常见的例子,在需要对原有结构进行增减字段的时候,同样一个结构体定义,新版本代码中对其增加了一个字段,那当新版本代码序列化后给原有旧版本反序列化解析的时候,因为旧的没有那个新增的字段,所以在解析时只解析自己有的字段,没有的不进行解析,这样旧的代码依旧能从新字节流中解析出旧数据结构。那旧的数据结构的数据解析为新数据结构时,因为没有新字段的数据,解析为新数据时该字段置为默认值。这样就能保证兼容性,对协议升级较为友好。
可以看到通过T-L-V
的数据存储方式,能够较好的解决字段不完全匹配时的如何解析的问题。
序列化 & 反序列化过程
序列化过程如下:
- 判断每个字段是否有设置值,有值才进行编码.
- 根据字段标识号&数据类型将字段值通过不同的编码方式进行编码.
反序列化过程如下:
- 解析从输入流读入的二进制字节数据流.
- 将解析出来的数据按照指定的格式读取到
C++、Rust
等对应的结构类型中.
到这里,基本把Protocol Buffers的工作原理简单梳理了一遍,其他技术细节待以后再深究。
Protocol Buffers工作原理的更多相关文章
- Protocol Buffers序列化原理
1. 使用Varint编码数据,越小的数据,用少的字节编码.如小于128的用一个字节编码,大于128的用多个字节编码.同时,每个字节最高为1或者0表示是否为数字的一部分. 2. 由于负数的补码表示很大 ...
- protocol buffers生成go代码原理
本文描述了protocol buffers使用.proto文件生成pb.go文件的过程 编译器 编译器需要插件来编译环境,使用如下方式安装插件:go get github.com/golang/pro ...
- protocol buffers的编码原理
protocol buffers使用二进制传输格式传递消息,因此相比于xml,json来说要轻便很多. 示例:假设定义了一个Message message Test1 { required int32 ...
- Hadoop基础-MapReduce的工作原理第二弹
Hadoop基础-MapReduce的工作原理第二弹 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Split(切片) 1>.MapReduce处理的单位(切片) 想必 ...
- Protocol Buffers官方文档(开发指南)
本文是对官方文档的翻译,然后截取了一篇非常优秀的文章片段来帮助理解,本人英文水平有限,基本都是直译,如果有不理解的地方请参考英文官方文档,参考的文章链接在文章末尾 protocol buffers简介 ...
- Servlet的生命周期及工作原理
Servlet生命周期分为三个阶段: 1,初始化阶段 调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在 ...
- HTTP协议请求响应过程和HTTPS工作原理
HTTP协议 HTTP协议主要应用是在服务器和客户端之间,客户端接受超文本. 服务器按照一定规则,发送到客户端(一般是浏览器)的传送通信协议.与之类似的还有文件传送协议(file transfer p ...
- HTTPS工作原理
HTTPS是什么 HTTPS全称为Hypertext Transfer Protocol over Secure Socket Layer,及以安全为目标的HTTP通道,简单说就是HTTP的安全版本. ...
- TODO:浅谈pm2基本工作原理
TODO:浅谈pm2基本工作原理 要谈Node.js pm2的工作原理,需要先来了解撒旦(Satan)和上帝(God)的关系. 撒旦(Satan),主要指<圣经>中的堕天使(也称堕天使撒旦 ...
随机推荐
- Springboot邮件发送思路分析
毕业设计里需要邮件发送,所以学习,总的来讲,我考虑以下几点, 代码量少,代码简单.配置少,一看就懂,使用 JavaMail 太麻烦了. 异步执行,添加员工之后会发送入职邮件, 多线程处理,设计里有一个 ...
- NEON中的L可以避免溢出
在做加法时,比如两个255x255的数值相加,那么正确结果将是130050,对一个最大值为65565的unsigned short是会溢出的,但是如果使用L命令时,则不会产生溢出.这说明L命令,不是先 ...
- [hdu3507 Print Article]斜率优化dp入门
题意:需要打印n个正整数,1个数要么单独打印要么和前面一个数一起打印,1次打印1组数的代价为这组数的和的平方加上常数M.求最小代价. 思路:如果令dp[i]为打印前i个数的最小代价,那么有 dp[i] ...
- [hdu5411 CRB and Puzzle]DP,矩阵快速幂
题意:给一个有向图,从任意点开始,最多走m步,求形成的图案总数. 思路:令dp[i][j]表示走j步最后到达i的方法数,则dp[i][j]=∑dp[k][j-1],其中k表示可以直接到达i的点,答案= ...
- 从`ArrayList`中了解Java的迭代器
目录 什么是迭代器 迭代器的设计意义 ArrayList对迭代器的实现 增强for循环和迭代器 参考链接 什么是迭代器 Java中的迭代器--Iterator是一个位于java.util包下的接口,这 ...
- Python中内置函数
python提供了很多的内置函数,这些内置的函数在某些情况下,可以起到很大的作用,而不需要专门去 写函数实现XX功能,直接使用内置函数就可以实现,下面分别来学习内置函数的使用和案例代码. abs(), ...
- 将mat文件中的数据按要求保存到txt文档中(批处理)
之前有个老朋友,让帮忙将一个mat中的数据重新保存到txt中,由于数据比较多需要用到批处理,之前弄过很多次,但每次一到要用的时候总是忘记怎么写了,现在记录一下,免得后面老是需要上网搜.这里先说一个比较 ...
- 对background: url("~assets/img/common/collect.svg") 0 0/14px 14px 的理解
需求:给收藏数字前面通过::before伪元素添加图标 相关代码: .goods-info .collect { position: relative; } .goods-info .collect: ...
- 【Leetcode】164. Maximum Gap 【基数排序】
Given an unsorted array, find the maximum difference between the successive elements in its sorted f ...
- MySQL zip解压 安装过程和配置
MYSQL官网下载地址:https://dev.mysql.com/downloads/mysql/ 1.下载mysql-5.7.19-winx64.zip,解压到指定的文件夹, 例如:E:\so ...