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