突然間,好像對(duì)ProtoBuf有點(diǎn)陌生的感覺(jué),特此復(fù)習(xí)一下八回。
背景知識(shí)
Protocol Buffers可以理解為更快、更簡(jiǎn)單驾诈、更小的JSON或者XML缠诅,區(qū)別在于Protocol Buffers是二進(jìn)制格式,而JSON和XML是文本格式乍迄。
Protobuf經(jīng)序列化后以二進(jìn)制數(shù)據(jù)流形式存儲(chǔ)管引,這個(gè)數(shù)據(jù)流是一系列key-Value對(duì)。Key用來(lái)標(biāo)識(shí)具體的Field闯两,在解包的時(shí)候汉匙,Protobuf根據(jù) Key 就可以知道相應(yīng)的 Value 應(yīng)該對(duì)應(yīng)于消息中的哪一個(gè) Field。
Key 的定義如下:
(field_number << 3) | wire_type
Key由兩部分組成生蚁。第一部分是 field_number噩翠,比如消息 tutorial .Person中 field name 的 field_number 為 1。第二部分為 wire_type邦投。表示 Value 的傳輸類(lèi)型伤锚。Wire Type 可能的類(lèi)型如下表所示:
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimi | string, bytes, embedded messages, packed repeated fields |
3 | Start group | Groups (deprecated) |
4 | End group | Groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
Protobuf的二進(jìn)制使用Varint編碼。Varint 是一種緊湊的表示數(shù)字的方法志衣。它用一個(gè)或多個(gè)字節(jié)來(lái)表示一個(gè)數(shù)字屯援,值越小的數(shù)字使用越少的字節(jié)數(shù)。這能減少用來(lái)表示數(shù)字的字節(jié)數(shù)念脯。
Varint 中的每個(gè) byte 的最高位 bit 有特殊的含義狞洋,如果該位為 1,表示后續(xù)的 byte 也是該數(shù)字的一部分绿店,如果該位為 0吉懊,則結(jié)束庐橙。其他的 7 個(gè) bit 都用來(lái)表示數(shù)字。因此小于 128 的數(shù)字都可以用一個(gè) byte 表示借嗽。大于 128 的數(shù)字态鳖,比如下面demo中的2000 就是用兩個(gè)字節(jié)來(lái)表示。
Demo Code
Demo Code 主要對(duì)比了一下恶导,用Gzip 壓縮序列化的Json數(shù)據(jù)與Protobuf 數(shù)據(jù)的速度和大小對(duì)比浆竭。可以看到惨寿,Protobuf 最大的一個(gè)優(yōu)點(diǎn)果然是在數(shù)據(jù)壓縮方面邦泄。那么數(shù)據(jù)是如何存取的呢?
手工解析pb數(shù)據(jù)
下面是一段Json數(shù)據(jù):
NSDictionary *userInfoDic = @{@"userName":@"vedon",
@"age":@(2000),
@"mobileNumber":@"1501849235x",
@"address":@"廣州市平云廣場(chǎng)",
@"numberOfFriends":@(2000),
};
它對(duì)應(yīng)的的pb 數(shù)據(jù)為:
<0a057665 646f6e10 1b1a0b31 35303138 34393233 35782215 e5b9bfe5 b79ee5b8 82e5b9b3 e4ba91e5 b9bfe59c ba2802>
解析userName
Key :
0a = 0000 1010
=> 0001 010
=> field_num = 0001 ,type = 010
=> field_num = 1,type = 2
Value:
05 = 0000 0101
=> 000 0101 (去掉最高位)
=> 5
讀取5個(gè)字符:
userName = 76 65 64 6f 6e => v e d o n
解析age
Key :
10 = 0001 0000
=> 001 0000 (去掉最高位)
=> field_num = 0010 ,type = 000
=> field_num = 2 ,type = 0
Value
1b = 0001 1011
=> 27
age = 27 .
如果age = 2000 ,2000超過(guò)一個(gè)字節(jié)表示的大小了裂垦,它會(huì)怎么表示呢顺囊?
改一下代碼,把年齡改成2000 缸废,發(fā)現(xiàn)除了key是不變外,value 從1b變成d00f
d00f = 1101 0000 | 0000 1111
=> 101 0000 | 000 1111 (little-endian)
=> 0111 1101 0000 = 2000
age = 2000
解析mobileNumber
Key:
1a = 0001 1010
=> 001 1010 (去掉最高位)
=> field_num = 0011 ,type = 010
=> field_num = 3 ,type = 2
Value:
0b = 0000 1011
=> 11
讀取11個(gè)字符:
31 35 30 31 38 34 39 32 33 35 78 => 1 5 0 1 8 4 9 2 3 5 x
優(yōu)點(diǎn)
- 序列化速度快
- 體積小
- 兼容性好
- Protocol Buffers的編譯器驶社,可以生成更容易在編程中使用的數(shù)據(jù)訪問(wèn)代碼
缺點(diǎn)
- 缺乏自描述企量,可讀性差
- 適用于內(nèi)部服務(wù)和存儲(chǔ)
JSON and Protobuf
Protobuf 相比 JSON 的優(yōu)勢(shì)在于它本身帶有嚴(yán)格的 schema validation,一份 .proto 文件既作為 input/output 的入口亡电,又作為規(guī)定數(shù)據(jù)格式的文檔届巩。例如:后端給一份.proto 文件,通過(guò)官方提供的工具份乒,生成對(duì)應(yīng)的oc 類(lèi)恕汇,在跨語(yǔ)言多人協(xié)作的項(xiàng)目上用起來(lái)不能更爽。
Protobuf 是 schema + data format或辖,schema 是我們可以看見(jiàn)的 proto 文件里的定義瘾英,data format 是它序列化成二進(jìn)制數(shù)據(jù)的格式。
最終選擇的時(shí)候還有個(gè)很關(guān)鍵的因素颂暇,就是公司整個(gè)大環(huán)境的技術(shù)選型缺谴,如果你所依賴(lài)的其他組件用 JSON 的較多,那么也不要刻意用 Protobuf 耳鸯。如果一個(gè)大型項(xiàng)目在一開(kāi)始就用了 Protobuf 作為數(shù)據(jù)格式的約定湿蛔,后期在數(shù)據(jù)一致性上可能出現(xiàn)很多麻煩就可以避免掉呢。
詳細(xì)的性能分析县爬,網(wǎng)上已經(jīng)有很詳細(xì)的了阳啥。這里推薦一篇文章:Beating JSON performance with Protobuf