造輪子系列之Protobuf

作為一個(gè)程序猿,對(duì)造輪子這事情可以說(shuō)是情有獨(dú)鐘,幾乎程序猿內(nèi)心都存在一個(gè)夢(mèng)想是去將開(kāi)源的技術(shù)都實(shí)現(xiàn)一遍,所有從本篇開(kāi)始,我會(huì)開(kāi)一個(gè)造輪子系列。

前言

首先,看看這個(gè),想必大家對(duì)下面這種簡(jiǎn)歷看得比較多了吧?

精通JAVA,Python,熟練掌握C++

精通Redis,Memcached,Mysql

精通Nginx配置,模塊開(kāi)發(fā)

精通Kafka,ActiveMQ 等消息隊(duì)列

精通常用數(shù)據(jù)結(jié)構(gòu)和算法

精通網(wǎng)絡(luò)編程,多線程編程技術(shù),高性能服務(wù)器技術(shù)

精通tcp/ip協(xié)議棧,熟悉內(nèi)核網(wǎng)絡(luò)子系統(tǒng)代碼

精通nginx代碼及模塊開(kāi)發(fā)

上面每一條都涉及好多輪子,每一個(gè)都是精通,如果真能做到。那這個(gè)人可以說(shuō)是碼農(nóng)中的戰(zhàn)斗機(jī)。

那我們現(xiàn)在目標(biāo)就是去做這個(gè)戰(zhàn)斗機(jī)。而這個(gè)方法,就是自己去造輪子,造的目的不是為了在項(xiàng)目中使用自己造的輪子,而是為了去了解輪子的構(gòu)造,然后自己動(dòng)手去體會(huì)造輪子的過(guò)程。

后端的輪子們

說(shuō)起后端的輪子們,大家都可以說(shuō)出一大串來(lái),我們大致來(lái)數(shù)一數(shù)啊。

抗在最前面的:LVS,F(xiàn)5,HAProxy這類負(fù)載均衡

接下來(lái)有Nginx,Apache,Lighttpd這類Http服務(wù)

http服務(wù)后則是各種容器,部署著我們的業(yè)務(wù)邏輯

存儲(chǔ)這邊有Redis,Memcached這一類KV存儲(chǔ)器和緩存系統(tǒng)

如果是多機(jī)部署,肯定還有Kafka,ActiveMQ這種負(fù)責(zé)解耦的消息隊(duì)列

為了實(shí)現(xiàn)集群通信,肯定少不了Thrift這種RPC框架和Protobuf這種序列化技術(shù)

再高端點(diǎn),到了分布式領(lǐng)域了,就是更多的輪子了。。zookeeper、raft等等

還有大數(shù)據(jù)系列hadoop。spark。。。。。

本文先開(kāi)始我們的第一個(gè)輪子,服務(wù)器通信需要用的數(shù)據(jù)序列化反序列技術(shù):protobuf。

基礎(chǔ)輪子:protobuf

講基礎(chǔ)前,先附上一張極客時(shí)間中的一個(gè)技術(shù)需要從哪些角度來(lái)講的圖片,本文也會(huì)盡可能從這些個(gè)方面來(lái)講。

應(yīng)用角度

1. 問(wèn)題:”干什么用“

2. 技術(shù)規(guī)范:”怎么用“

3. 最佳實(shí)踐:”怎么能用好“

4. 市場(chǎng)應(yīng)用趨勢(shì):“誰(shuí)用,用在哪”

設(shè)計(jì)角度

1. 目標(biāo):“做到什么”

2. 實(shí)現(xiàn)原理:“怎么做到”

3. 優(yōu)劣局限:“做得怎么樣”

4. 演進(jìn)趨勢(shì):“未來(lái)如何”

正文

Protocol buffers

從應(yīng)用角度看protobuf是干什么用的?

序列化數(shù)據(jù)用的?什么時(shí)候需要序列化?當(dāng)數(shù)據(jù)需要存儲(chǔ)或者網(wǎng)絡(luò)傳輸?shù)臅r(shí)候。為什么呢?

在存儲(chǔ)或者傳輸?shù)臅r(shí)候,我們能看到都是一些二進(jìn)制數(shù)據(jù),即010101……的bit。

假設(shè)我們看的一個(gè)對(duì)象是:

Struct myData {

Int a;

Int b;

}

data = myData {

a:1,

b:2,

}

那我們?cè)诰W(wǎng)絡(luò)上收到是一個(gè)字節(jié)流,我們?yōu)榱四軌驈淖止?jié)流中恢復(fù)出數(shù)據(jù) data,我們要做的工作是:

正確識(shí)別出data在字節(jié)流中開(kāi)始和結(jié)束的位置

識(shí)別出a的值,識(shí)別出b的值

一個(gè)可能的字節(jié)流協(xié)議就是:

剛開(kāi)始是8bit標(biāo)明后續(xù)數(shù)據(jù)是哪個(gè)結(jié)構(gòu),然后是兩個(gè)4字節(jié)表示a和b。

注意?。。。?!上面做出上面這個(gè)假設(shè),有幾點(diǎn)是我們默認(rèn)的:

我們認(rèn)為字節(jié)流開(kāi)始先是8bit標(biāo)明是哪個(gè)數(shù)據(jù)結(jié)構(gòu),此處是myData(ps:不同結(jié)構(gòu)之間編號(hào)不同)

最多能夠支持2^8種結(jié)構(gòu)

通訊雙方都需要拿到myData的定義文件

以下是一個(gè)上面實(shí)現(xiàn)的示例代碼:

可以看到在go中很容易就實(shí)現(xiàn)了我們的一個(gè)數(shù)據(jù)結(jié)構(gòu)的序列化反序列化。

設(shè)計(jì)角度,做到什么?

上面我們只是實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)序列化方法,下面我們來(lái)看如果要實(shí)現(xiàn)一個(gè)生產(chǎn)環(huán)境中的序列化協(xié)議,需要做到哪幾點(diǎn)。

通用性:語(yǔ)言、平臺(tái)無(wú)關(guān)

高性能:序列化和反序列化都要快

高壓縮:序列化后數(shù)據(jù)盡可能小,小就意味著網(wǎng)絡(luò)傳輸數(shù)據(jù)少

兼容性:數(shù)據(jù)結(jié)構(gòu)改變了,也能夠支持新老版本

下面我們帶著這些目標(biāo)來(lái)從應(yīng)用角度來(lái)看下”怎么用“

這個(gè)就是官方文檔了https://developers.google.com/protocol-buffers/docs/gotutorial,里面有詳細(xì)的說(shuō)明,另外我自己給了一份使用示例:https://github.com/zhuanxuhit/go-in-practice/tree/master/wheel/protobuf/v2

講完使用下面就是設(shè)計(jì)角度:如何做到的?

首先我們看前文我們自己實(shí)現(xiàn)的簡(jiǎn)易序列化、反序列方法,我們對(duì)每個(gè)結(jié)構(gòu)進(jìn)行編碼,然后在頭部寫(xiě)上該結(jié)構(gòu)是啥,然后后面就是結(jié)構(gòu)中每個(gè)字段的具體值,接著我們寫(xiě)了序列化器的目標(biāo)是:簡(jiǎn)易、高效、兼容,下面我們從這幾個(gè)方面來(lái)看protobuf有什么改進(jìn)的地方。

先來(lái)個(gè)小插曲,protobuf全稱是Protocol buffers,其中buffers點(diǎn)名了使用上非常重要的一個(gè)點(diǎn),即我們?cè)诜葱蛄谢囊欢味M(jìn)制數(shù)據(jù)的時(shí)候,我們要將其先讀入到buffer中,然后再識(shí)別出單個(gè)數(shù)據(jù)結(jié)構(gòu)的開(kāi)頭和結(jié)尾,最后才能正確的反序列化出來(lái)。

前面我們?cè)O(shè)計(jì)的時(shí)候,還在頭部對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行了編碼,那為了能夠做到更高效,數(shù)據(jù)更小,我們是否可以把這個(gè)頭也去掉呢?

當(dāng)然是可以的,于是我們的結(jié)構(gòu)就變?yōu)榱酥挥袑?duì)應(yīng)的字段值了,這么做的一個(gè)前提是:!!我們必須清楚知道我們識(shí)別出來(lái)二進(jìn)制數(shù)據(jù),其對(duì)應(yīng)的具體是哪個(gè)數(shù)據(jù)結(jié)構(gòu)。?。?/p>

現(xiàn)在我們?nèi)サ袅私Y(jié)構(gòu)描述,那怎么能夠做到更小呢?譬如同樣是int64,1 和 1<<32沒(méi)必要都用8字節(jié)來(lái)表示,譬如我們可以先對(duì)數(shù)據(jù)類型做一個(gè)編碼,然后緊跟著后續(xù)使用的bit,再跟著真正的數(shù)據(jù)。

每個(gè)部分分別用幾個(gè)bit來(lái)表示呢?

數(shù)據(jù)類型:根據(jù)支持的類型進(jìn)行編碼,如果總共能支持16種類型,那就是4bit

后續(xù)有效字節(jié):這個(gè)比較難辦,由于我們不確定數(shù)據(jù)大小,我們就無(wú)法固定bit來(lái)表示。

那一個(gè)解決方法就是:我們?nèi)サ粲行ё止?jié)的字段,我們把這個(gè)是否有更多數(shù)據(jù)信息編碼進(jìn)數(shù)據(jù)本身中,示意圖如下:

解決了編碼int類型的字段后,如果遇到string類型呢?這種類型首先也是數(shù)據(jù)類型描述,接著應(yīng)該要是編碼后續(xù)有效字節(jié),這是一個(gè)int,這可以采用上面的方法來(lái)編碼,再跟著就是有效數(shù)據(jù)了。

以上就是protobuf在編碼數(shù)據(jù)時(shí)采用編碼方式的主要思想,具體可以看https://developers.google.com/protocol-buffers/docs/encoding。

目前protobuf支持的數(shù)據(jù)類型

上面有個(gè)主意的對(duì)于有符號(hào)數(shù),我們要單獨(dú)處理下,因?yàn)橛蟹?hào)數(shù)的最高位是通過(guò)0,1來(lái)表示正負(fù)的,但是上面編碼中最高位卻用來(lái)表示是否有后續(xù)數(shù)據(jù)了,所以我們要通過(guò)ZigZag 編碼將有符號(hào)轉(zhuǎn)換為無(wú)符號(hào)。

原理很簡(jiǎn)單,就是通過(guò)下面的編碼方式:

解決了編碼后,我們來(lái)看最后一個(gè)問(wèn)題:兼容性。

如果我們改變了數(shù)據(jù)結(jié)構(gòu):新增或者刪除了字段怎么辦???

這個(gè)也好解決,那我們就給所有字段加上編號(hào),通過(guò)字段來(lái)表示這個(gè)數(shù)據(jù)是結(jié)構(gòu)體中哪個(gè)字段,protobuf在設(shè)計(jì)上編碼方式如下:

圖片來(lái)自:高效的數(shù)據(jù)壓縮編碼方式 Protobuf

從上圖中tag的編碼,我們可以發(fā)現(xiàn),如果field_num > 16的話,tag編碼出來(lái)會(huì)使用超過(guò)1字節(jié),所有對(duì)于我們經(jīng)常使用的字段,建議將其編碼到0-15,減少tag位數(shù)。

實(shí)現(xiàn)原理小結(jié)

下面我們對(duì)上面介紹的實(shí)現(xiàn)原理做個(gè)小結(jié)

高效:變長(zhǎng)編碼,非自描述

兼容:對(duì)filed進(jìn)行編碼

下面我們?cè)購(gòu)膽?yīng)用角度講下 protobuf 的最佳實(shí)踐和市場(chǎng)應(yīng)用趨勢(shì)。

protobuf剛開(kāi)始設(shè)計(jì)出來(lái)主要是為了解決接口兼容性問(wèn)題,目前是主要用在內(nèi)部服務(wù)之間RPC調(diào)用和傳遞數(shù)據(jù),目前時(shí)候用protobuf作為序列化的rpc框架有g(shù)RPC,這也是后續(xù)我們會(huì)介紹的。

最后我們從設(shè)計(jì)角度來(lái)看下protobuf的優(yōu)劣局限和演進(jìn)趨勢(shì)。

優(yōu)點(diǎn)

protobuf最大的優(yōu)點(diǎn)就是前后兼容性,已經(jīng)部署的使用老數(shù)據(jù)格式的服務(wù),即使接口升級(jí)了也可以繼續(xù)使用,然后就是性能,當(dāng)然是快了,具體可以看 序列化 / 反序列化性能

缺點(diǎn)

相比較json來(lái)說(shuō),可讀性差,特別是在調(diào)試階段,相比較json我們無(wú)法清晰的知道輸入和輸出。

最后

總結(jié)下本文

Protobuf設(shè)計(jì)之初主要是為了解決兼容性問(wèn)題,實(shí)現(xiàn)上是對(duì)每個(gè)字段進(jìn)行編號(hào),當(dāng)遇到不存在的字段時(shí),則忽略掉。

Protobuf為了能夠做到高性能,在編碼時(shí)采用了Tag - Value (Tag - Length - Value)的方式,使序列化后的數(shù)據(jù)更緊湊

Protobuf為了能夠做到高性能,丟棄了自描述信息,即我們只拿到數(shù)據(jù),而沒(méi)有拿到proto文件,我們是無(wú)法反序列數(shù)據(jù)的

Protobuf提供了一套編譯工具,能夠生成不同語(yǔ)言的數(shù)據(jù)序列化、反序列化方法,極大的提高了易用性

預(yù)告

下一篇我們會(huì)介紹grpc,來(lái)看下rpc框架中是怎么使用protobuf的。

參考

高效的數(shù)據(jù)壓縮編碼方式 Protobuf

官方文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 翻譯查閱外網(wǎng)資料過(guò)程中遇到的比較優(yōu)秀的文章和資料,一是作為技術(shù)參考以便日后查閱,二是訓(xùn)練英文能力。此文翻譯自 Pr...
    401閱讀 69,090評(píng)論 1 39
  • 之前在網(wǎng)絡(luò)通信和通用數(shù)據(jù)交換等應(yīng)用場(chǎng)景中經(jīng)常使用的技術(shù)是 JSON 或 XML,而在最近的開(kāi)發(fā)中接觸到了 Goog...
    401閱讀 503,001評(píng)論 25 379
  • 前言 之前一直忙于移動(dòng)端日志SDK Trojan的開(kāi)源工作,已十分穩(wěn)定地運(yùn)行在餓了么團(tuán)隊(duì)App中,集成了日志加密和...
    水木飛雪閱讀 25,276評(píng)論 16 96
  • 背景 學(xué)過(guò)java的都使用過(guò)RMI框架(remote method invocation),遠(yuǎn)程方法調(diào)用,比如A,...
    二月_春風(fēng)閱讀 12,292評(píng)論 0 13
  • 簡(jiǎn)介 protoBuf是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語(yǔ)言,獨(dú)立于平臺(tái)。google 提供了多種語(yǔ)言...
    ssochi閱讀 3,056評(píng)論 0 2

友情鏈接更多精彩內(nèi)容