一、gRPC入門
1. gRPC 簡介
⚫ gRPC 由 google 開發,是一款語言中立、平台中立、開源的遠程過程調用系統
⚫ gRPC 客戶端和服務端可以在多種環境中運行和交互,例如用 java 寫一個服務端,可以用 go 語言寫客戶端調用
2. gRPC 與 Protobuf 介紹
⚫ 微服務架構中,由於每個服務對應的代碼庫是獨立運行的,無法直接調用,彼此間的通信就是個大問題
⚫ gRPC 可以實現微服務,將大的項目拆分為多個小且獨立的業務模塊,也就是服務,各服務間使用高效的 protobuf 協議進行RPC 調用,gRPC 默認使用 protocol buffers,這是 google 開源的一套成熟的結構數據序列化機制(當然也可以使用其他數據格式如JSON)
⚫ 可以用 proto files 創建 gRPC 服務,用 message 類型來定義方法參數和返回類型
3. 安裝 gRPC 和 Protobuf
go get -u -v github.com/golang/protobuf/proto go get google.golang.org/grpc(無法使用,用如下命令代替) git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text go get -u github.com/golang/protobuf/{proto,protoc-gen-go} git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto cd $GOPATH/src/ go install google.golang.org/grpc go get github.com/golang/protobuf/protoc-gen-go go build github.com/golang/protobuf/protoc-gen-go 上面安裝好后,會在GOPATH/bin 下生成 protoc-gen-go.exe 但還需要一個 protoc.exe,windows 平台編譯受限,很難自己手動編譯,直接去網站下載一個,地址:https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0 ,同樣放到GOPATH/bin 下
二、Protobuf語法
1. 基本規范
⚫ 文件以.proto 做為文件后綴,除結構定義外的語句以分號結尾
⚫ 結構定義可以包含:message、service、enum
⚫ rpc 方法定義結尾的分號可有可無
⚫ Message 命名采用駝峰命名方式,字段命名采用小寫字母加下划線分隔方式
message SongServerRequest { required string song_name = 1; }
⚫ Enums 類型名采用駝峰命名方式,字段命名采用大寫字母加下划線分隔方式
⚫ Service 與 rpc 方法名統一采用駝峰式命名
enum Foo { FIRST_VALUE = 1; SECOND_VALUE = 2; }
2. 字段規則
⚫ 字段格式:限定修飾符 | 數據類型 | 字段名稱 | = | 字段編碼值 | [字段默認值]
⚫ 限定修飾符包含 required\optional\repeated
◼ Required: 表示是一個必須字段,必須相對於發送方,在發送消息之前必須設置該字段的值,對於接收方,必須能夠識別該字段的意思。發送之前沒有設置
required 字段或者無法識別 required 字段都會引發編解碼異常,導致消息被丟棄
◼ Optional:表示是一個可選字段,可選對於發送方,在發送消息時,可以有選擇性的設置或者不設置該字段的值。對於接收方,如果能夠識別可選字段就進行相應的處理,如果無法識別,則忽略該字段,消息中的其它字段正常處理。
---因為 optional 字段的特性,很多接口在升級版本中都把后來添加的字段都統一的設置為 optional 字段,這樣老的版本無需升級程序也可以正常的與新的軟件進行通信,只不過新的字段無法識別而已,因為並不是每個節點都需要新的功能,因此可以做到按需升級和平滑過渡
◼ Repeated:表示該字段可以包含 0~N 個元素。其特性和 optional 一樣,但是每一次可以包含多個值。可以看作是在傳遞一個數組的值
⚫ 數據類型
◼ Protobuf 定義了一套基本數據類型。幾乎都可以映射到 C++\Java 等語言的基礎數據類型

◼ N 表示打包的字節並不是固定。而是根據數據的大小或者長度
◼ 關於 fixed32 和 int32 的區別。fixed32 的打包效率比 int32 的效率高,但是使用的空間一般比 int32 多。因此一個屬於時間效率高,一個屬於空間效率高
⚫ 字段名稱
◼ 字段名稱的命名與 C、C++、Java 等語言的變量命名方式幾乎是相同的
◼ protobuf 建議字段的命名采用以下划線分割的駝峰式。例如 first_name 而不是firstName
⚫ 字段編碼值
◼ 有了該值,通信雙方才能互相識別對方的字段,相同的編碼值,其限定修飾符和數據類型必須相同,編碼值的取值范圍為 1~2^32(4294967296)
◼ 其中 1~15 的編碼時間和空間效率都是最高的,編碼值越大,其編碼的時間和空間效率就越低,所以建議把經常要傳遞的值把其字段編碼設置為 1-15 之間的值
◼ 1900~2000 編碼值為 Google protobuf 系統內部保留值,建議不要在自己的項目中使用
⚫ 字段默認值
◼ 當在傳遞數據時,對於 required 數據類型,如果用戶沒有設置值,則使用默認值傳遞到對端
3. service 如何定義
⚫ 如果想要將消息類型用在 RPC 系統中,可以在.proto 文件中定義一個 RPC 服務接口,protocol buffer 編譯器會根據所選擇的不同語言生成服務接口代碼
⚫ 例如,想要定義一個 RPC 服務並具有一個方法,該方法接收 SearchRequest 並返回一個 SearchResponse,此時可以在.proto 文件中進行如下定義:
service SearchService { rpc Search (SearchRequest) returns (SearchResponse) {} }
⚫ 生成的接口代碼作為客戶端與服務端的約定,服務端必須實現定義的所有接口方法,客戶端直接調用同名方法向服務端發起請求,比較麻煩的是,即便業務上不需要參數也必須指定一個請求消息,一般會定義一個空message
4. Message 如何定義
⚫ 一個 message 類型定義描述了一個請求或響應的消息格式,可以包含多種類型字段
⚫ 例如定義一個搜索請求的消息格式,每個請求包含查詢字符串、頁碼、每頁數目
⚫ 字段名用小寫,轉為 go 文件后自動變為大寫,message 就相當於結構體
syntax = "proto3"; message SearchRequest { string query = 1; // 查詢字符串 int32 page_number = 2; // 頁碼 int32 result_per_page = 3; // 每頁條數 }
⚫ 首行聲明使用的 protobuf 版本為 proto3
⚫ SearchRequest 定義了三個字段,每個字段聲明以分號結尾,.proto 文件支持雙斜線 // 添加單行注釋
5. 添加更多 Message 類型
⚫ 一個.proto 文件中可以定義多個消息類型,一般用於同時定義多個相關的消息,例如在同一個.proto 文件中同時定義搜索請求和響應消息
syntax = "proto3"; // SearchRequest 搜索請求 message SearchRequest { string query = 1; // 查詢字符串 int32 page_number = 2; // 頁碼 int32 result_per_page = 3; // 每頁條數 } // SearchResponse 搜索響應 message SearchResponse { ... }
6. 如何使用其他 Message
⚫ message 支持嵌套使用,作為另一 message 中的字段類型
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; }
7. Message 嵌套的使用
⚫ 支持嵌套消息,消息可以包含另一個消息作為其字段。也可以在消息內定義一個新的消息
⚫ 內部聲明的 message 類型名稱只可在內部直接使用
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
⚫ 另外,還可以多層嵌套
message Outer { message MiddleAA { message Inner { int64 ival = 1; bool booly = 2; } } message MiddleBB { message Inner { int32 ival = 1; bool booly = 2; } } }
8. proto3 的 Map 類型
⚫ proto3 支持 map 類型聲明
map<key_type, value_type> map_field = N; message Project {...} map<string, Project> projects = 1;
⚫ 鍵、值類型可以是內置的類型,也可以是自定義 message 類型
⚫ 字段不支持repeated 屬性
9. .proto 文件編譯
⚫ 通過定義好的.proto 文件生成Java, Python, C++, Go, Ruby, JavaNano, Objective-C, or C# 代碼,需要安裝編譯器 protoc
⚫ 當使用 protocol buffer 編譯器運行.proto 文件時,編譯器將生成所選語言的代碼,用於使用在.proto 文件中定義的消息類型、服務接口約定等。不同語言生成的代碼格式不同:
◼ C++: 每個.proto 文件生成一個.h 文件和一個.cc 文件,每個消息類型對應一個類
◼ Java: 生成一個.java 文件,同樣每個消息對應一個類,同時還有一個特殊的
Builder 類用於創建消息接口
◼ Python: 姿勢不太一樣,每個.proto 文件中的消息類型生成一個含有靜態描述符的模塊,該模塊與一個元類 metaclass 在運行時創建需要的 Python 數據訪問類
◼ Go: 生成一個.pb.go 文件,每個消息類型對應一個結構體
◼ Ruby: 生成一個.rb 文件的 Ruby 模塊,包含所有消息類型
◼ JavaNano: 類似 Java,但不包含 Builder 類
◼ Objective-C: 每個.proto 文件生成一個 pbobjc.h 和一個 pbobjc.m 文件
◼ C#: 生成.cs 文件包含,每個消息類型對應一個類
10. import 導入定義
⚫ 可以使用 import 語句導入使用其它描述文件中聲明的類型
⚫ protobuf 接口文件可以像 C 語言的 h 文件一個,分離為多個,在需要的時候通過import 導入需要對文件。其行為和 C 語言的#include 或者 java 的 import 的行為大致相同,例如 import "others.proto";
⚫ protocol buffer 編譯器會在 -I / --proto_path 參數指定的目錄中查找導入的文件,如果沒有指定該參數,默認在當前目錄中查找
11.包的使用
⚫ 在.proto 文件中使用 package 聲明包名,避免命名沖突
syntax = "proto3"; package foo.bar; message Open {...}
⚫ 在其他的消息格式定義中可以使用包名+消息名的方式來使用類型,如
message Foo { ... foo.bar.Open open = 1; ... }
⚫ 在不同的語言中,包名定義對編譯后生成的代碼的影響不同
◼ C++ 中:對應 C++命名空間,例如 Open 會在命名空間 foo::bar 中
◼ Java 中:package 會作為 Java 包名,除非指定了 option jave_package 選項
◼ Python 中:package 被忽略
◼ Go 中:默認使用 package 名作為包名,除非指定了 option go_package 選項
◼ JavaNano 中:同 Java
◼ C# 中:package 會轉換為駝峰式命名空間,如 Foo.Bar,除非指定了 option csharp_namespace 選項