新聞中心
前言
在網(wǎng)絡(luò)通信和通用數(shù)據(jù)交換等應(yīng)用場景中經(jīng)常使用的技術(shù)是 JSON 或 XML,在微服務(wù)架構(gòu)中通常使用另外一個數(shù)據(jù)交換的協(xié)議的工具ProtoBuf。

在陽西等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計制作、網(wǎng)站設(shè)計 網(wǎng)站設(shè)計制作定制開發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站建設(shè),成都全網(wǎng)營銷,外貿(mào)網(wǎng)站制作,陽西網(wǎng)站建設(shè)費用合理。
ProtoBuf也是我們做微服務(wù)開發(fā),進行Go進階實戰(zhàn)中,必知必會的知道點。
今天就開始第一章內(nèi)容:《一文帶你玩轉(zhuǎn)ProtoBuf》
5分鐘入門
1.1 簡介
你可能不知道ProtoBuf,但一定知道json或者xml,從一定意義上來說他們的作用是一樣的。
ProtoBuf全稱:protocol buffers,直譯過來是:“協(xié)議緩沖區(qū)”,是一種與語言無關(guān)、與平臺無關(guān)的可擴展機制,用于序列化結(jié)構(gòu)化數(shù)據(jù)。
和json\xml最大的區(qū)別是:json\xml都是基于文本格式,ProtoBuf是二進制格式。
ProtoBuf相比于json\XML,更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡單。
我們只需要定義一次數(shù)據(jù)結(jié)構(gòu),就可以使用ProtoBuf生成源代碼,輕松搞定在各種數(shù)據(jù)流和各種語言中寫入、讀取結(jié)構(gòu)化數(shù)據(jù)。
1.2 安裝
建議大家使用主流版本v3,這是官網(wǎng)下載地址:https://github.com/protocolbuffers/ProtoBuf/releases
注意,不同的電腦系統(tǒng)安裝包是不一樣的:
- Windows 64位 點這里下載
- Windows 32位 點這里下載
- Mac Intel 64位 點這里下載
- Mac ARM 64位 點這里下載
- Linux 64位 點這里下載
(公眾號無法跳轉(zhuǎn)到外鏈,點擊文末的閱讀原文可以跳轉(zhuǎn)到下載地址。)
小技巧:Mac查看自己的芯片類型點擊左上角的蘋果圖標(biāo),再點擊關(guān)于本機,就可以查看了。
比如,我的處理器芯片是intel的,下載安裝包之后是這樣的:
bin目錄下的protoc是ProtoBuf的工具集,下文會重點介紹它的使用。
注意:我們需要將下載得到的可執(zhí)行文件protoc所在的 bin 目錄加到我們電腦的環(huán)境變量中。
Mac安裝小技巧
如果你的Mac安裝了brew,安裝ProtoBuf就更簡單了,我們使用brew install ProtoBuf就可以了
1.3 編譯go語言的工具包
這個protoc可以將proto文件編譯為任何語言的文件,想要編譯為go語言的,還需要下載另外一個可執(zhí)行文件
命令是這樣的:
go install google.golang.org/ProtoBuf/cmd/protoc-gen-go@latest
1.4 編寫proto代碼
下面就編寫一個非常簡單,但是五臟齊全的proto代碼,我們再根據(jù)這段代碼生成pb.go文件。
syntax = "proto3";
package hello;
option go_package = "./;hello";
message Say{
int64 id = 1;
string hello = 2;
repeated string word = 3;
}
1.5 生成go代碼
生成go代碼,非常簡單,使用下面的命令就可以了。
切換到.proto文件所在目錄
cd proto/demo/
指定proto源文件,自動生成代碼。
protoc --go_out=. hello.proto
執(zhí)行上面的命令后,我們在項目中就自動生成了一個.pb.go的文件
入門ProtoBuf就是這么的簡單:通過這幾步我們就完成了ProtoBuf的下載、安裝、編寫了一個proto文件,并生成了能用Go語言讀寫ProtoBuf的源代碼。
我們再深入了解一下probuf的用法:
10分鐘進階
下面再帶大家深入了解一下ProtoBuf的知識點,避免在開發(fā)中踩坑。
小技巧:寫proto和寫go最大的區(qū)別是需要在結(jié)尾添加分號的;,在開發(fā)過程中給自己提個醒:如果是寫proto需要加分號,如果是寫go不需要加分號。
以我們上面的proto入門代碼舉例:
1.1 關(guān)鍵字
- syntax:是必須寫的,而且要定義在第一行;目前proto3是主流,不寫默認(rèn)使用proto2
- package:定義我們proto文件的包名
- option go_package:定義生成的pb.go的包名,我們通常在proto文件中定義。如果不在proto文件中定義,也可以在使用protoc生成代碼時指定pb.go文件的包名
message:非常重要,用于定義消息結(jié)構(gòu)體,不用著急,下文會重點講解
細(xì)心的小伙伴一定注意到了 message 消息體中有一個 “repeated” 關(guān)鍵字,這在我們寫Go的時候是沒有的。
這是干什么用的呢?下面來詳細(xì)解答一下:
1.2 數(shù)組類型
關(guān)于數(shù)組類型,和Java、Go、PHP等語言中,定義數(shù)據(jù)類型不一樣。
在ProtoBuf消息中定義數(shù)組類型,是通過在字段前面增加repeated關(guān)鍵詞實現(xiàn),標(biāo)記當(dāng)前字段是一個數(shù)組。
只要使用repeated標(biāo)記類型定義,就表示數(shù)組類型。
我們來舉兩個例子:
(1)整數(shù)數(shù)組:
下面定義的arrays表示int32類型的數(shù)組
message Msg {
repeated int32 arrays = 1;
}
(2)字符串?dāng)?shù)組
下面定義的names表示字符串?dāng)?shù)組
message Msg {
repeated string names = 1;
}repeated搞懂了,message又是干嘛用的呢?
1.3 消息
消息(message),在ProtoBuf中指的就是我們要定義的數(shù)據(jù)結(jié)構(gòu)。類似于Go中定義結(jié)構(gòu)體。
message關(guān)鍵詞用法也非常簡單:
(1) 語法
syntax = "proto3";
message 消息名 {
消息體
}
例子:
syntax = "proto3";
message Request {
string query = 1;
int32 page = 2;
int32 limit = 3;
}
定義了一個Request消息,這個消息有3個字段,query是字符串類型,page和limit是int32類型。
1.4 字段類型
ProtoBuf支持多種數(shù)據(jù)類型,例如:string、int32、double、float等等,我整理了一份ProtoBuf和go語言的數(shù)據(jù)類型映射表
|
.proto Type |
Go Type |
使用技巧 |
|
double |
float64 |
沒特殊技巧,記住float對應(yīng)go的float32,double對應(yīng)go的float64就可以了 |
|
float |
float32 |
沒特殊技巧,記住float對應(yīng)go的float32,double對應(yīng)go的float64就可以了 |
|
int32 |
int32 |
使用變長編碼,對于負(fù)值的效率很低,如果你的域有可能有負(fù)值,請使用sint64替代 |
|
uint32 |
uint32 |
使用變長編碼 |
|
uint64 |
uint64 |
使用變長編碼 |
|
sint32 |
int32 |
使用變長編碼,這些編碼在負(fù)值時比int32高效的多 |
|
sint64 |
int64 |
使用變長編碼,有符號的整型值。編碼時比通常的int64高效。 |
|
fixed32 |
uint32 |
總是4個字節(jié),如果數(shù)值都比228大的話,這個類型會比uint32高效。 |
|
fixed64 |
uint64 |
總是8個字節(jié),如果數(shù)值都比256大的話,這個類型會比uint64高效。 |
|
sfixed32 |
int32 |
總是4個字節(jié) |
|
sfixed64 |
int64 |
總是8個字節(jié) |
|
bool |
bool |
嚴(yán)格對應(yīng),玩不出其他花樣來 |
|
string |
string |
一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 |
|
bytes |
[]byte |
可以包含任意順序的字節(jié)數(shù)組 |
1.5 分配標(biāo)識號
細(xì)心的小伙伴可能又有疑問了,上面消息體中的 string query = 1; 這個1是什么呢?
這些數(shù)字是“分配表示號”:在消息定義中,每個字段后面都有一個唯一的數(shù)字,這個就是標(biāo)識號。
這些標(biāo)識號的作用是:用來在消息的二進制格式中識別各個字段的,一旦開始使用就不能夠再改變。
注意:分配標(biāo)識號在每個消息內(nèi)唯一,不同的消息體是可以擁有相同的標(biāo)識號的。
小技巧:[1,15]之內(nèi)的標(biāo)識號在編碼的時候會占用一個字節(jié)。[16,2047]之內(nèi)的標(biāo)識號則占用2個字節(jié)。所以應(yīng)該為那些頻繁出現(xiàn)的消息元素保留 [1,15]之內(nèi)的標(biāo)識號。
1.5.1 保留標(biāo)識號(Reserved)
小技巧:要為將來有可能添加的、頻繁出現(xiàn)的字段預(yù)留一些標(biāo)識號。
我們想保留一些標(biāo)識號,留給以后用,可以使用下面語法:
message Test {
reserved 2, 5, 7 to 10; // 保留2,5,7到10這些標(biāo)識號
}如果使用了這些保留的標(biāo)識號,protocol buffer編譯器無法編譯通過,將會輸出警告信息。
1.6 將消息編譯成各種語言版本的類庫
編譯器命令格式:
protoc [OPTION] PROTO_FILES
OPTION是命令的選項, PROTO_FILES是我們要編譯的proto消息定義文件,支持多個。
常用的OPTION選項:
--go_out=OUT_DIR 指定代碼生成目錄,生成 Go 代碼
--cpp_out=OUT_DIR 指定代碼生成目錄,生成 C++ 代碼
--csharp_out=OUT_DIR 指定代碼生成目錄,生成 C# 代碼
--java_out=OUT_DIR 指定代碼生成目錄,生成 java 代碼
--js_out=OUT_DIR 指定代碼生成目錄,生成 javascript 代碼
--objc_out=OUT_DIR 指定代碼生成目錄,生成 Objective C 代碼
--php_out=OUT_DIR 指定代碼生成目錄,生成 php 代碼
--python_out=OUT_DIR 指定代碼生成目錄,生成 python 代碼
--ruby_out=OUT_DIR 指定代碼生成目錄,生成 ruby 代碼
因為開篇我們就用Go舉了例子,下面再用Java舉個例子吧:
protoc --java_out=. hello.proto
在當(dāng)前目錄導(dǎo)出java版本的代碼,編譯hello.proto消息,執(zhí)行效果如下:
下載再帶小伙伴們了解一下ProtoBuf的進階知識點吧:枚舉類型、消息嵌套和Map類型。
1.7 枚舉類型
寫Java的同學(xué)枚舉一定用的很溜,但是寫Go的同學(xué)可能有點懵了,Go是不直接支持枚舉的,并沒有Enum關(guān)鍵字。
關(guān)注我,后續(xù)會詳解Go枚舉相關(guān)的知識點,在這篇文章中不做重點介紹。
使用枚舉的場景是這樣的:
當(dāng)定義一個消息類型的時候,可能想為一個字段指定“預(yù)定義值”中的其中一個值,這時候我們就可以通過枚舉實現(xiàn),比如這種:
syntax = "proto3";//指定版本信息,非注釋的第一行
enum SexType //枚舉消息類型,使用enum關(guān)鍵詞定義,一個性別類型的枚舉類型
{
UNKONW = 0; //proto3版本中,首成員必須為0,成員不應(yīng)有相同的值
MALE = 1; //1男
FEMALE = 2; //2女 0未知
}
// 定義一個用戶消息
message UserInfo
{
string name = 1; // 姓名字段
SexType sex = 2; // 性別字段,使用SexType枚舉類型
}
運行效果如下:
在實際開發(fā)中,我們需要定義很多的proto,我們?nèi)绾巫龅较⒌膹?fù)用呢?
答案就是:“消息嵌套”
1.8 消息嵌套
我們在開發(fā)Java和PHP時,經(jīng)常嵌套使用類,也可以使用其他類作為自己的成員屬性類型;在開發(fā)Go時經(jīng)常嵌套使用結(jié)構(gòu)體。
在ProtoBuf中同樣支持消息嵌套,可以在一個消息中嵌套另外一個消息,字段類型可以是另外一個消息類型。
我們來看下面3個經(jīng)典示例:
1.8.1 引用其他消息類型的用法
// 定義Article消息
message Article {
string url = 1;
string title = 2;
repeated string tags = 3; // 字符串?dāng)?shù)組類型
}
// 定義ListArticle消息
message ListArticle {
// 引用上面定義的Article消息類型,作為results字段的類型
repeated Article articles = 1; // repeated關(guān)鍵詞標(biāo)記,說明articles字段是一個數(shù)組
}
1.8.2 消息嵌套
類似類嵌套一樣,消息也可以嵌套,比如這樣:
message ListArticle {
// 嵌套消息定義
message Article {
string url = 1;
string title = 2;
repeated string tags = 3;
}
// 引用嵌套的消息定義
repeated Article articles = 1;
}
1.8.3 import導(dǎo)入其他proto文件定義的消息
我們在實際開發(fā)中,通常要定義很多消息,如果都寫在一個proto文件,是不方便維護的。
小技巧:將消息定義寫在不同的proto文件中,在需要的時候可以通過import導(dǎo)入其他proto文件定義的消息。
例子:
創(chuàng)建文件: article.proto
syntax = "proto3";
package nesting;
option go_package = "./;article";
message Article {
string url = 1;
string title = 2;
repeated string tags = 3; // 字符串?dāng)?shù)組類型
}
創(chuàng)建文件: list_article.proto
syntax = "proto3";
// 導(dǎo)入Article消息定義
import "article.proto";
package nesting;
option go_package = "./;article";
// 定義ListArticle消息
message ListArticle {
// 使用導(dǎo)入的Result消息
repeated Article articles = 1;
}
執(zhí)行效果如下,我們順利生成了.pb.go文件:
1.9 map類型
我們在Go語言開發(fā)中,最常用的就是切片類型和map類型了。
切片類型在ProtoBuf中對應(yīng)的就是repeated類型,前面我們已經(jīng)介紹過了。
再重點介紹一下map類型,ProtoBuf也是支持map類型的:
1.9.1 map語法
mapmap_field = N;
語法非常簡單和通用,但是有幾個問題需要我們注意:
- key_type可以是任何整數(shù)或字符串類型(除浮點類型和字節(jié)之外的任何標(biāo)量類型)。
- 注意:枚舉不是有效的key_type。
- value_type 可以是除另一個映射之外的任何類型。
- Map 字段不能使用repeated關(guān)鍵字修飾。
1.9.2 map的例子
我們舉個典型的例子:學(xué)生的學(xué)科和分?jǐn)?shù)就適合用map定義:
syntax = "proto3";
package map;
option go_package = "./;score";
message Student{
int64 id = 1; //id
string name = 2; //學(xué)生姓名
mapscore = 3; //學(xué)科 分?jǐn)?shù)的map
}
運行效果如下:
再強調(diào)一下:
注意:Map 字段是不能使用repeated關(guān)鍵字修飾。
至此我們已經(jīng)掌握了ProtoBuf的所有知識點,是不是非常簡單清晰呢?
下面我們在Go項目中實戰(zhàn)應(yīng)用一下ProtoBuf,從ProtoBuf中讀取數(shù)據(jù),并且轉(zhuǎn)換為我們常用的結(jié)構(gòu)體
5分鐘實戰(zhàn)
1. 首先我們定義proto文件
我創(chuàng)建了一個demo目錄,創(chuàng)建了名為study_info.proto的文件
syntax = "proto3";
package demo;
option go_package = "./;study";
message StudyInfo {
int64 id = 1; //id
string name = 2; //學(xué)習(xí)的科目名稱
int32 duration = 3; //學(xué)習(xí)的時長 單位秒
mapscore = 4; //學(xué)習(xí)的分?jǐn)?shù)
}
2. 生成代碼
使用命令生成pb.go文件:
protoc --go_out=. study_info.proto
3.編寫go文件
編寫go文件,讀取ProtoBuf中定義的字段,進行賦值,取值,轉(zhuǎn)成結(jié)構(gòu)體等操作:
proto編碼和解碼的操作和json是非常像的,都使用“Marshal”和“Unmarshal”關(guān)鍵字。
package main
import (
"fmt"
"google.golang.org/ProtoBuf/proto"
study "juejin/ProtoBuf/proto/demo"
)
func main() {
// 初始化proto中的消息
studyInfo := &study.StudyInfo{}
//常規(guī)賦值
studyInfo.Id = 1
studyInfo.Name = "學(xué)習(xí)ProtoBuf"
studyInfo.Duration = 180
//在go中聲明實例化map賦值給ProtoBuf消息中定義的map
score := make(map[string]int32)
score["實戰(zhàn)"] = 100
studyInfo.Score = score
//用字符串的方式:打印ProtoBuf消息
fmt.Printf("字符串輸出結(jié)果:%v\n", studyInfo.String())
//轉(zhuǎn)成二進制文件
marshal, err := proto.Marshal(studyInfo)
if err != nil {
return
}
fmt.Printf("Marshal轉(zhuǎn)成二進制結(jié)果:%v\n", marshal)
//將二進制文件轉(zhuǎn)成結(jié)構(gòu)體
newStudyInfo := study.StudyInfo{}
err = proto.Unmarshal(marshal, &newStudyInfo)
if err != nil {
return
}
fmt.Printf("二進制轉(zhuǎn)成結(jié)構(gòu)體的結(jié)果:%v\n", &newStudyInfo)
}
運行結(jié)果如下:
本文總結(jié)
ProtoBuf作為開發(fā)微服務(wù)必選的數(shù)據(jù)交換協(xié)議,基于二進制傳輸,比json/xml更小,速度更快,使用也非常的簡單。
通過這篇文章,我們不僅學(xué)會了ProtoBuf的入門操作,還使用Go語言基于ProtoBuf編碼解碼了數(shù)據(jù),進行了實戰(zhàn)。
進階部分帶大家了解了ProtoBuf如何定義消息、ProtoBuf和Go數(shù)據(jù)類型的映射、枚舉類型如何使用、通過消息嵌套復(fù)用代碼、使用map類型時需要注意的問題和小技巧。
本文轉(zhuǎn)載自微信公眾號「 程序員升級打怪之旅」,作者「王中陽Go」,可以通過以下二維碼關(guān)注。
轉(zhuǎn)載本文請聯(lián)系「 程序員升級打怪之旅」公眾號。
網(wǎng)頁標(biāo)題:【Go微服務(wù)】一文帶你玩轉(zhuǎn)ProtoBuf
URL鏈接:http://m.fisionsoft.com.cn/article/cooegig.html


咨詢
建站咨詢
