[筆記] 實作分散式計分系統(三) : gRPC and Protobuf

R. H.
hobo engineer
Published in
6 min readFeb 7, 2020

在建立 gRPC Server 前,須先了解 Protobuf, 而其兩者都是 Google 的產物。
這邊並不會詳細介紹它們的詳細概念, 這類的筆記網上已有很多, 所以只做個簡介並紀錄如何使用它們搭建 gRPC Server。

Protobuf

Protobuf 是 Protocol Buffers的簡稱,用於描述一種輕便高效的結構化數據存儲格式,簡單說就是能以一份文件定義格式,然後能方便直接轉成各類語言的資料型態,而以本範例來說能直接轉譯成 golang struct,目前已能支援多種語言,詳細可參考官網

gRPC

https://grpc.io/docs/guides/

gRPC 的概念其實很直覺,就是透過 Protobuf 定義出來的資料結構,不只代表格式,同時也代表你伺服器的接口。最大好處其背後是以 http2 傳遞,比起一般走 http/https 的 Restful API 不僅傳輸效率上快上許多,也不用另外寫文件。Client 端與 Server 端只要遵照定義出來的資料格式傳遞/實作(可參考上圖),API 就能執行。

How to use Protobuf

Step1 :

安裝所需套件。

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

Step2:

創建資料夾並定義 .proto 文件。

pb  // 擺放 Protobuf 文件
└── game.proto

Step3:

message 用來定義 RPC function 之 Request/Response
這邊共定義了 4 個 function

GetScore : 獲取指定棒球比賽得分數
PutScore : 對指定比賽新增分數
GetGameList: 獲取所有棒球比賽清單
PostNewGame: 新增棒球比賽

並掛到 rpc Server - ServiceServer 上

語法文件

Step4 :

進入 /pb 資料夾,然後執行下列指令

protoc --go_out=plugins=grpc:. *.proto

可看到 ./pb 生成 game.pb.go

pb
├── game.pb.go
└── game.proto

How to use gRPC

Step1:

安裝所需套件

go get google.golang.org/grpc

Step2:

建立對應資料夾與 go file

├── pb
│ ├── game.pb.go
│ └── game.proto
├── conf
│ └── app.ini
├── setting
│ └── setting.go
├── server
│ └── serverNode.go
├── client
│ ├── client_demo.go

Step3:

編輯 conf/app.inisetting/setting.go
(這把 configuration 放到檔案裡比較好維護不是必須步驟,只是個人習慣把 configuration 放到檔案裡比較好維護)

這邊是以 “github.com/go-ini/ini” 套件將 conf/app.ini 轉為結構實體變數,能方便在後續使用到它們。

conf/app.ini
setting/setting.go

Step4:

這邊是本篇較重要的地方,首先宣告了 server struct{} ,並依照先前在 game.proto 宣告過方法與 message 將四個同名 function GetScore PutScore GetGameList PostNewGame 實作於 server struct{}

GetScore 為例,已於 game.proto 定義此 RPC function 所需的參數 - GetScoreRequest 與返回格式 - GetScoreReply

game.proto — GetScore

故在 GetScore function 中接收的參數需為 GetScoreRequest ,最後也以 GetScoreReply 返回。而若 GetScore 執行過程有錯,gRPC 可將 error 帶在第二個參數返回。

ServerNode — GetScore

再來直接看到完整的 serverNode.go ,連接資料庫的部份定義了 mongoDB() 並帶入 url 與 port 來去連接指定的 mongoDB ,在 GetScore PutScore GetGameList PostNewGame 都有調用到此方法。當然在一般 monolithic system 而言資料庫會保持連線,不會每執行一次服務就重新去連線一次,而這邊會這樣設計是因為本系統有 3 個 Replica Set Node,3 個 node 都能被 Read ,但只有 Primary Node 能執行寫入動作 (Write)。

所以在 GetScore GetGameList 方法中能讓使用者指定資料庫 node 去做讀取,而 PutScore PostNewGame 則連接於 conf/app.ini 內的預設 Primary Node)。

serverNode.go

最後則於 main function 啟動 gRPC Server 並監聽tcp port 50051

Step5:

完成 Server 端後再來是 Client 端的部份,這部份就簡單許多 。以新增棒球比賽 PostNewGame 為例,先透過 grpc.Dial() 去連接已啟動的 gRPC Server,在用回傳的 conn 去呼叫 PostNewGame ,當然別忘了要以 game.proto 定義的格式帶入參數 - PostNewGameRequest

client_demo.go

Step6:

最後來看下目前成果 ,在此前已先啟動 Replca Set 與 gRPC Server,接著再執行 client_demo.go 去新增一筆名為 “Test0109_3”的棒球比賽。若新增成功回傳的結果會是該筆資料的 objectID

當然如果不用其他 RPC Tool 的前提下,那每次 client 端都要透過以上這樣的 client_demo.go 去呼叫服務未免有些麻煩,所以下篇將介紹 Cobra,說明如何將其包裝成 CLI 來方便操作。

--

--