Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。
特点:语言无关、平台无关、可扩展序列化结构。
场景:数据存储和交换。
优点:定义简单、(反)序列化速度快、支持多种数据结构
编码优化
Varint 编码:不定长二进制整型编码

第一位的 0 或者 1 代表:是否是数字的尾部;剩下的 7 位用于记录数字的二进制。
例如,数字 299 在 int32 下是:
| 1
 | 00000000 00000000 00000001 00101011
 | 
编码后是:
优点很明显:节省了 2 个字节。
对于小于$2^{28}$的 int32 或者 int64,Varint 起到压缩效果。对于大数字,位数反而更多。但是,正常情况下小数字使用频率远远高于大数字。
Zigzag: 有符号数编码优化
原因:对于负数,是以补码形式存储(数大),占用位数多。
作用:将有符号整数映射为无符号整数,例如 -1 => 1;原本无符号的整数,变成之前的 2 倍,例如 1 => 2。

传输优化
protobuf 是以下面的形式传输的。优点如下:
- Field 之间没有区分符号,节省字节
- Key 是 filed_id

第 2 点比较难理解。比如一个 proto:
| 12
 3
 
 | message demo {number age = 1;
 }
 
 | 
对于 filed age 来说,field id 是 1。但是传输的时候不会传输 Key(也就是”age”),而是根据 field id 以及数据类型进行位运算,生成一个整数。规则如下:
| 1
 | (field_id << 3) | wire_type
 | 

消费端接收到数据流后,可以很快通过位运算解出 filed(id+value)。然后根据 id,再去读取 proto 定义文件(生产端和消费端共有),将其处理成对应的 key。
流程如上,优化了网络传输数据量,更多逻辑放在本地。
Node 中使用 protobuf
.proto 定义
person.proto:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | package person; syntax = "proto3";
 
 message PersonMessage {
 required string name = 1;
 optional string email = 2;
 
 enum SexType {
 UNKNOWN = 0;
 MALE = 1;
 FEMALE = 2;
 }
 required SexType sex = 3 [default = UNKNOWN];
 
 
 message LocationType {
 required string country = 1;
 }
 optional LocationType location = 4;
 }
 
 | 
student.proto :
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | syntax = "proto3";
 import "./person.proto";
 
 package student;
 
 message StudentMessage {
 required string school = 1;
 required person.PersonMessage teacher = 2;
 }
 
 | 
加载、验证、编/解码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 | const protobuf = require('protobufjs');
 main();
 
 function main() {
 const payload = {
 school: 'szu',
 teacher: {
 name: 'dongyuanxin',
 sex: 2,
 location: {
 country: 'zh-cn',
 },
 },
 };
 
 protobuf.load('./student.proto').then((root) => {
 const AwesomeMessage = root.lookupType('student.StudentMessage');
 
 
 let verified = AwesomeMessage.verify(payload);
 if (verified) {
 
 throw new Error(verified);
 }
 
 
 let message = AwesomeMessage.create(payload);
 console.log(message);
 
 
 let buffer = AwesomeMessage.encode(message).finish();
 console.log(buffer);
 
 let decoded = AwesomeMessage.decode(buffer);
 console.log(decoded);
 });
 }
 
 | 
参考链接