gRPC 簡介
最近公司想引進新技術 gRPC,從之前就有聽說過這個協定,似乎在 golang 後端開法也是一個趨勢,今天就來記錄一下所學到的筆記,再補上一個簡單的 golang 實作
簡介
gRPC 簡言之就是 google 開發的 RPC(Remote Procedure Calls),傳輸上是基於 HTTP/2 並使用一種叫做 Protocol Buffers 的格式來作為介面描述語言,這點跟 GraphQL 有點像,一樣要維護一份介面文件,不只可以作為文件也是執行程式必須的
主要會轉用 gRPC 我認為有幾個優點:
- 透過 HTTP/2 並且用 binary 傳輸內容,傳送以及解析成本都比傳統 HTTP API 跟 json 快上不少
- 透過 Protocol Buffers 維護統一介面,必須要有而不是隨心情而寫的文件
- 可以透過 Protocol Buffers 直接產生程式碼,coding 的時候就是呼叫一個 function,不用額外記 url path,或是一直重複寫 http request 的 function
- 支援雙向串流,不需要透過 WebSocket
- 可以指定本次請求的預期時間,超時可以透過 server 端進行逾時處理
gRPC 缺點:
- 生態系還不那麼完整,相比各個廠商都有提供 OpenAPI,gRPC 主要用在內部微服務溝通,也比較少第三方的各種方便 library 提供使用
- 瀏覽器支援不足,目前瀏覽器還沒有原生支援 gRPC 的使用,我認為這是很大的原因限制了 gRPC 的發展
- 沒有 Method 的分類,以及共同約定的 Header,像是驗證身份,或是語系的內容,都必須由雙方自行約定欄位並且自己實作機制
- 由 binary 傳輸,不經過解碼的話沒辦法直接讀內容
Protocol Buffers
Protocol Buffers 簡寫成 ProtoBuf 是 gRPC 的介面描述語言,除了用來作為統一的文件之外,也能用來產出 gRPC 的程式碼,一般檔案副檔名為 .proto
以下是一份範例檔
// test.proto
syntax = "proto3";
package test;
option go_package = "grpc.demo/test";
message TestAddReq{
int64 a = 1;
int64 b = 2;
}
message TestAddResp{
int64 sum = 1;
}
service Test{
rpc TestAdd(TestAddReq) returns (TestAddResp){};
}
開頭的 syntax
表明了 protobuf 的版本
其實寫起來還蠻像在寫 Swagger 的,定義介面跟傳參還有回傳
比較特別的是 option
這裡的 go_package 是用來定義 golang package 的路徑,可以理解到會根據實作的程式語言不同而有所不同
欄位後面的數字代表著他的 id,應該盡可能的將 id 縮減在 1~15 因為只會佔用 1 byte,其實我不太懂為什麼這個不能由 gRPC 編譯時再自行加入
基本型態 支援
建立基本 gRPC server
我們要建立一個新的 golang 專案來運行然後安裝所需的套件
先來抓一下 protobuf 的 必要工具
然後透過下面指令確認版本跟安裝 golang 的套件
protoc --version
go get github.com/golang/protobuf/protoc-gen-go
go get -u google.golang.org/grpc
現在我們的專案大概長下面這樣
├ proto/
│ └ test.proto
├ go.mod
├ go.sum
└ server.go
然後執行下面指令編譯
mkdir grpc
protoc --go_out=./grpc --go_opt=paths=source_relative \
--go-grpc_out=./grpc --go-grpc_opt=paths=source_relative \
./proto/*.proto
上面指令跑完之後的專案長這樣
├ grpc/proto/
│ ├ test_grpc.pb.go
│ └ test.pb.go
├ proto/
│ └ test.proto
├ go.mod
├ go.sum
└ server.go
生成的 code 就是幫你把 grpc 的 server 跟 client 基本架構還有介面寫好,我們只需要實作介面就好
下面是 server.go 的程式碼
package main
import (
"context"
"fmt"
pb "grpc_demo/server/grpc/proto"
"log"
"net"
"google.golang.org/grpc"
)
type TestGrpc struct {
pb.UnimplementedTestServer
}
func (*TestGrpc) TestAdd(ctx context.Context, req *pb.TestAddReq) (*pb.TestAddResp, error) {
return &pb.TestAddResp{
Sum: req.GetA() + req.GetB(),
}, nil
}
func main() {
lis, err := net.Listen("tcp", "localhost:5000")
if err != nil {
log.Fatalf("failed to listen: %v \n", err)
}
grpcServer := grpc.NewServer()
testGrpc := &TestGrpc{}
pb.RegisterTestServer(grpcServer, testGrpc)
fmt.Println("starting gRPC server on localhost:5000...")
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v \n", err)
}
}
我覺得特別要注意的是 import
的路徑,由於沒有一個比較容易識別的 root struct 所以路徑基本不會自動完成,需要自己補上
執行 go run server.go
簡單的 grpc server 就完成了
基本 Client 範例
接下來我們寫個 Client 來測試
準備專案環境
首先依照之前給的 test.proto
來建立一個新的 client 專案環境
├ proto/
│ └ test.proto
├ go.mod
└ client.go
然後抓依賴並且產生 gRPC code
go get github.com/golang/protobuf/protoc-gen-go
go get -u google.golang.org/grpc
mkdir grpc
protoc --go_out=./grpc --go_opt=paths=source_relative \
--go-grpc_out=./grpc --go-grpc_opt=paths=source_relative \
./proto/*.proto
下面是一個基本的 client 範例
// client.go
package main
import (
"context"
"fmt"
pb "grpc_demo/client/grpc/proto"
"log"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:5000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed to dial: %v", err)
}
defer conn.Close()
client := pb.NewTestClient(conn)
grpcTestAdd(client)
}
func grpcTestAdd(client pb.TestClient) {
fmt.Println("Staring gRPC request")
req := &pb.TestAddReq{
A: 5,
B: 7,
}
res, err := client.TestAdd(context.Background(), req)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Printf("Response: %v", res.GetSum())
}
直接執行就好了,可以發現在 gRPC Client 上調用請求是非常簡單的,實作都自動幫忙產好了
正常回傳且結果正確
至此就完成我們第一個 gRPC 實作了
結語
先簡介基本概念跟程式,之後會補上更多 gRPC 的用法,還有一些踩到的雷