【Go微服務(wù)】一文帶你玩轉(zhuǎn)ProtoBuf
前言
在網(wǎng)絡(luò)通信和通用數(shù)據(jù)交換等應(yīng)用場(chǎng)景中經(jīng)常使用的技術(shù)是 JSON 或 XML,在微服務(wù)架構(gòu)中通常使用另外一個(gè)數(shù)據(jù)交換的協(xié)議的工具ProtoBuf。
ProtoBuf也是我們做微服務(wù)開(kāi)發(fā),進(jìn)行Go進(jìn)階實(shí)戰(zhàn)中,必知必會(huì)的知道點(diǎn)。
今天就開(kāi)始第一章內(nèi)容:《一文帶你玩轉(zhuǎn)ProtoBuf》
5分鐘入門(mén)
1.1 簡(jiǎn)介
你可能不知道ProtoBuf,但一定知道json或者xml,從一定意義上來(lái)說(shuō)他們的作用是一樣的。
ProtoBuf全稱:protocol buffers,直譯過(guò)來(lái)是:“協(xié)議緩沖區(qū)”,是一種與語(yǔ)言無(wú)關(guān)、與平臺(tái)無(wú)關(guān)的可擴(kuò)展機(jī)制,用于序列化結(jié)構(gòu)化數(shù)據(jù)。
和json\xml最大的區(qū)別是:json\xml都是基于文本格式,ProtoBuf是二進(jìn)制格式。
ProtoBuf相比于json\XML,更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單。
我們只需要定義一次數(shù)據(jù)結(jié)構(gòu),就可以使用ProtoBuf生成源代碼,輕松搞定在各種數(shù)據(jù)流和各種語(yǔ)言中寫(xiě)入、讀取結(jié)構(gòu)化數(shù)據(jù)。
1.2 安裝
建議大家使用主流版本v3,這是官網(wǎng)下載地址:https://github.com/protocolbuffers/ProtoBuf/releases
注意,不同的電腦系統(tǒng)安裝包是不一樣的:
- Windows 64位 點(diǎn)這里下載
- Windows 32位 點(diǎn)這里下載
- Mac Intel 64位 點(diǎn)這里下載
- Mac ARM 64位 點(diǎn)這里下載
- Linux 64位 點(diǎn)這里下載
(公眾號(hào)無(wú)法跳轉(zhuǎn)到外鏈,點(diǎn)擊文末的閱讀原文可以跳轉(zhuǎn)到下載地址。)
小技巧:Mac查看自己的芯片類型點(diǎn)擊左上角的蘋(píng)果圖標(biāo),再點(diǎn)擊關(guān)于本機(jī),就可以查看了。
比如,我的處理器芯片是intel的,下載安裝包之后是這樣的:
bin目錄下的protoc是ProtoBuf的工具集,下文會(huì)重點(diǎn)介紹它的使用。
注意:我們需要將下載得到的可執(zhí)行文件protoc所在的 bin 目錄加到我們電腦的環(huán)境變量中。
Mac安裝小技巧
如果你的Mac安裝了brew,安裝ProtoBuf就更簡(jiǎn)單了,我們使用brew install ProtoBuf就可以了
1.3 編譯go語(yǔ)言的工具包
這個(gè)protoc可以將proto文件編譯為任何語(yǔ)言的文件,想要編譯為go語(yǔ)言的,還需要下載另外一個(gè)可執(zhí)行文件
命令是這樣的:
1.4 編寫(xiě)proto代碼
下面就編寫(xiě)一個(gè)非常簡(jiǎn)單,但是五臟齊全的proto代碼,我們?cè)俑鶕?jù)這段代碼生成pb.go文件。
1.5 生成go代碼
生成go代碼,非常簡(jiǎn)單,使用下面的命令就可以了。
切換到.proto文件所在目錄
指定proto源文件,自動(dòng)生成代碼。
執(zhí)行上面的命令后,我們?cè)陧?xiàng)目中就自動(dòng)生成了一個(gè).pb.go的文件
入門(mén)ProtoBuf就是這么的簡(jiǎn)單:通過(guò)這幾步我們就完成了ProtoBuf的下載、安裝、編寫(xiě)了一個(gè)proto文件,并生成了能用Go語(yǔ)言讀寫(xiě)ProtoBuf的源代碼。
我們?cè)偕钊肓私庖幌聀robuf的用法:
10分鐘進(jìn)階
下面再帶大家深入了解一下ProtoBuf的知識(shí)點(diǎn),避免在開(kāi)發(fā)中踩坑。
小技巧:寫(xiě)proto和寫(xiě)go最大的區(qū)別是需要在結(jié)尾添加分號(hào)的;,在開(kāi)發(fā)過(guò)程中給自己提個(gè)醒:如果是寫(xiě)proto需要加分號(hào),如果是寫(xiě)go不需要加分號(hào)。
以我們上面的proto入門(mén)代碼舉例:
1.1 關(guān)鍵字
- syntax:是必須寫(xiě)的,而且要定義在第一行;目前proto3是主流,不寫(xiě)默認(rèn)使用proto2
- package:定義我們proto文件的包名
- option go_package:定義生成的pb.go的包名,我們通常在proto文件中定義。如果不在proto文件中定義,也可以在使用protoc生成代碼時(shí)指定pb.go文件的包名
message:非常重要,用于定義消息結(jié)構(gòu)體,不用著急,下文會(huì)重點(diǎn)講解
細(xì)心的小伙伴一定注意到了 message 消息體中有一個(gè) “repeated” 關(guān)鍵字,這在我們寫(xiě)Go的時(shí)候是沒(méi)有的。
這是干什么用的呢?下面來(lái)詳細(xì)解答一下:
1.2 數(shù)組類型
關(guān)于數(shù)組類型,和Java、Go、PHP等語(yǔ)言中,定義數(shù)據(jù)類型不一樣。
在ProtoBuf消息中定義數(shù)組類型,是通過(guò)在字段前面增加repeated關(guān)鍵詞實(shí)現(xiàn),標(biāo)記當(dāng)前字段是一個(gè)數(shù)組。
只要使用repeated標(biāo)記類型定義,就表示數(shù)組類型。
我們來(lái)舉兩個(gè)例子:
(1)整數(shù)數(shù)組:
下面定義的arrays表示int32類型的數(shù)組
(2)字符串?dāng)?shù)組
下面定義的names表示字符串?dāng)?shù)組
repeated搞懂了,message又是干嘛用的呢?
1.3 消息
消息(message),在ProtoBuf中指的就是我們要定義的數(shù)據(jù)結(jié)構(gòu)。類似于Go中定義結(jié)構(gòu)體。
message關(guān)鍵詞用法也非常簡(jiǎn)單:
(1) 語(yǔ)法
例子:
定義了一個(gè)Request消息,這個(gè)消息有3個(gè)字段,query是字符串類型,page和limit是int32類型。
1.4 字段類型
ProtoBuf支持多種數(shù)據(jù)類型,例如:string、int32、double、float等等,我整理了一份ProtoBuf和go語(yǔ)言的數(shù)據(jù)類型映射表
.proto Type | Go Type | 使用技巧 |
double | float64 | 沒(méi)特殊技巧,記住float對(duì)應(yīng)go的float32,double對(duì)應(yīng)go的float64就可以了 |
float | float32 | 沒(méi)特殊技巧,記住float對(duì)應(yīng)go的float32,double對(duì)應(yīng)go的float64就可以了 |
int32 | int32 | 使用變長(zhǎng)編碼,對(duì)于負(fù)值的效率很低,如果你的域有可能有負(fù)值,請(qǐng)使用sint64替代 |
uint32 | uint32 | 使用變長(zhǎng)編碼 |
uint64 | uint64 | 使用變長(zhǎng)編碼 |
sint32 | int32 | 使用變長(zhǎng)編碼,這些編碼在負(fù)值時(shí)比int32高效的多 |
sint64 | int64 | 使用變長(zhǎng)編碼,有符號(hào)的整型值。編碼時(shí)比通常的int64高效。 |
fixed32 | uint32 | 總是4個(gè)字節(jié),如果數(shù)值都比228大的話,這個(gè)類型會(huì)比uint32高效。 |
fixed64 | uint64 | 總是8個(gè)字節(jié),如果數(shù)值都比256大的話,這個(gè)類型會(huì)比uint64高效。 |
sfixed32 | int32 | 總是4個(gè)字節(jié) |
sfixed64 | int64 | 總是8個(gè)字節(jié) |
bool | bool | 嚴(yán)格對(duì)應(yīng),玩不出其他花樣來(lái) |
string | string | 一個(gè)字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 |
bytes | []byte | 可以包含任意順序的字節(jié)數(shù)組 |
1.5 分配標(biāo)識(shí)號(hào)
細(xì)心的小伙伴可能又有疑問(wèn)了,上面消息體中的 string query = 1; 這個(gè)1是什么呢?
這些數(shù)字是“分配表示號(hào)”:在消息定義中,每個(gè)字段后面都有一個(gè)唯一的數(shù)字,這個(gè)就是標(biāo)識(shí)號(hào)。
這些標(biāo)識(shí)號(hào)的作用是:用來(lái)在消息的二進(jìn)制格式中識(shí)別各個(gè)字段的,一旦開(kāi)始使用就不能夠再改變。
注意:分配標(biāo)識(shí)號(hào)在每個(gè)消息內(nèi)唯一,不同的消息體是可以擁有相同的標(biāo)識(shí)號(hào)的。
小技巧:[1,15]之內(nèi)的標(biāo)識(shí)號(hào)在編碼的時(shí)候會(huì)占用一個(gè)字節(jié)。[16,2047]之內(nèi)的標(biāo)識(shí)號(hào)則占用2個(gè)字節(jié)。所以應(yīng)該為那些頻繁出現(xiàn)的消息元素保留 [1,15]之內(nèi)的標(biāo)識(shí)號(hào)。
1.5.1 保留標(biāo)識(shí)號(hào)(Reserved)
小技巧:要為將來(lái)有可能添加的、頻繁出現(xiàn)的字段預(yù)留一些標(biāo)識(shí)號(hào)。
我們想保留一些標(biāo)識(shí)號(hào),留給以后用,可以使用下面語(yǔ)法:
如果使用了這些保留的標(biāo)識(shí)號(hào),protocol buffer編譯器無(wú)法編譯通過(guò),將會(huì)輸出警告信息。
1.6 將消息編譯成各種語(yǔ)言版本的類庫(kù)
編譯器命令格式:
OPTION是命令的選項(xiàng), PROTO_FILES是我們要編譯的proto消息定義文件,支持多個(gè)。
常用的OPTION選項(xiàng):
因?yàn)殚_(kāi)篇我們就用Go舉了例子,下面再用Java舉個(gè)例子吧:
在當(dāng)前目錄導(dǎo)出java版本的代碼,編譯hello.proto消息,執(zhí)行效果如下:
下載再帶小伙伴們了解一下ProtoBuf的進(jìn)階知識(shí)點(diǎn)吧:枚舉類型、消息嵌套和Map類型。
1.7 枚舉類型
寫(xiě)Java的同學(xué)枚舉一定用的很溜,但是寫(xiě)Go的同學(xué)可能有點(diǎn)懵了,Go是不直接支持枚舉的,并沒(méi)有Enum關(guān)鍵字。
關(guān)注我,后續(xù)會(huì)詳解Go枚舉相關(guān)的知識(shí)點(diǎn),在這篇文章中不做重點(diǎn)介紹。
使用枚舉的場(chǎng)景是這樣的:
當(dāng)定義一個(gè)消息類型的時(shí)候,可能想為一個(gè)字段指定“預(yù)定義值”中的其中一個(gè)值,這時(shí)候我們就可以通過(guò)枚舉實(shí)現(xiàn),比如這種:
運(yùn)行效果如下:
在實(shí)際開(kāi)發(fā)中,我們需要定義很多的proto,我們?nèi)绾巫龅较⒌膹?fù)用呢?
答案就是:“消息嵌套”
1.8 消息嵌套
我們?cè)陂_(kāi)發(fā)Java和PHP時(shí),經(jīng)常嵌套使用類,也可以使用其他類作為自己的成員屬性類型;在開(kāi)發(fā)Go時(shí)經(jīng)常嵌套使用結(jié)構(gòu)體。
在ProtoBuf中同樣支持消息嵌套,可以在一個(gè)消息中嵌套另外一個(gè)消息,字段類型可以是另外一個(gè)消息類型。
我們來(lái)看下面3個(gè)經(jīng)典示例:
1.8.1 引用其他消息類型的用法
1.8.2 消息嵌套
類似類嵌套一樣,消息也可以嵌套,比如這樣:
1.8.3 import導(dǎo)入其他proto文件定義的消息
我們?cè)趯?shí)際開(kāi)發(fā)中,通常要定義很多消息,如果都寫(xiě)在一個(gè)proto文件,是不方便維護(hù)的。
小技巧:將消息定義寫(xiě)在不同的proto文件中,在需要的時(shí)候可以通過(guò)import導(dǎo)入其他proto文件定義的消息。
例子:
創(chuàng)建文件: article.proto
創(chuàng)建文件: list_article.proto
執(zhí)行效果如下,我們順利生成了.pb.go文件:
1.9 map類型
我們?cè)贕o語(yǔ)言開(kāi)發(fā)中,最常用的就是切片類型和map類型了。
切片類型在ProtoBuf中對(duì)應(yīng)的就是repeated類型,前面我們已經(jīng)介紹過(guò)了。
再重點(diǎn)介紹一下map類型,ProtoBuf也是支持map類型的:
1.9.1 map語(yǔ)法
語(yǔ)法非常簡(jiǎn)單和通用,但是有幾個(gè)問(wèn)題需要我們注意:
- key_type可以是任何整數(shù)或字符串類型(除浮點(diǎn)類型和字節(jié)之外的任何標(biāo)量類型)。
- 注意:枚舉不是有效的key_type。
- value_type 可以是除另一個(gè)映射之外的任何類型。
- Map 字段不能使用repeated關(guān)鍵字修飾。
1.9.2 map的例子
我們舉個(gè)典型的例子:學(xué)生的學(xué)科和分?jǐn)?shù)就適合用map定義:
運(yùn)行效果如下:
再?gòu)?qiáng)調(diào)一下:
注意:Map 字段是不能使用repeated關(guān)鍵字修飾。
至此我們已經(jīng)掌握了ProtoBuf的所有知識(shí)點(diǎn),是不是非常簡(jiǎn)單清晰呢?
下面我們?cè)贕o項(xiàng)目中實(shí)戰(zhàn)應(yīng)用一下ProtoBuf,從ProtoBuf中讀取數(shù)據(jù),并且轉(zhuǎn)換為我們常用的結(jié)構(gòu)體
5分鐘實(shí)戰(zhàn)
1. 首先我們定義proto文件
我創(chuàng)建了一個(gè)demo目錄,創(chuàng)建了名為study_info.proto的文件
2. 生成代碼
使用命令生成pb.go文件:
3.編寫(xiě)go文件
編寫(xiě)go文件,讀取ProtoBuf中定義的字段,進(jìn)行賦值,取值,轉(zhuǎn)成結(jié)構(gòu)體等操作:
proto編碼和解碼的操作和json是非常像的,都使用“Marshal”和“Unmarshal”關(guān)鍵字。
運(yùn)行結(jié)果如下:
本文總結(jié)
ProtoBuf作為開(kāi)發(fā)微服務(wù)必選的數(shù)據(jù)交換協(xié)議,基于二進(jìn)制傳輸,比json/xml更小,速度更快,使用也非常的簡(jiǎn)單。
通過(guò)這篇文章,我們不僅學(xué)會(huì)了ProtoBuf的入門(mén)操作,還使用Go語(yǔ)言基于ProtoBuf編碼解碼了數(shù)據(jù),進(jìn)行了實(shí)戰(zhàn)。
進(jìn)階部分帶大家了解了ProtoBuf如何定義消息、ProtoBuf和Go數(shù)據(jù)類型的映射、枚舉類型如何使用、通過(guò)消息嵌套復(fù)用代碼、使用map類型時(shí)需要注意的問(wèn)題和小技巧。
本文轉(zhuǎn)載自微信公眾號(hào)「 程序員升級(jí)打怪之旅」,作者「王中陽(yáng)Go」,可以通過(guò)以下二維碼關(guān)注。
轉(zhuǎn)載本文請(qǐng)聯(lián)系「 程序員升級(jí)打怪之旅」公眾號(hào)。