Go 使用 gRPC 入門

甚麼是gRPC

gRPC 是Google所開源的一個RPC(Remote Procedure Calls)系統,RPC就如名字所說是一個讓我們能對遠端程式/程序的呼叫如同在本地端電腦呼叫程式一樣的系統,這樣的系統解決了分布式系統中Server間程式的調用問題。而server間互相溝通的方式還有如WebSocket, RestfulAPI等,而相較於這些方法,gRPC提供了更輕量更快速的呼叫返回。gRPC主要使用了Protocal Buffers作為介面描述語言(IDL),適合用在高性能,需要快速響應的場景。gPRC提供了四種形式的傳輸,包括Client傳一個請求,Server回一個響應、Client傳多個請求(streaming),Server回一個響應、Client傳一個請求,Server streaming多個響應、及Client Server均已streaming方式交互 這四種形式,本文以最基本的一對一的為例子來實現一個簡單的加法器(Client傳送兩個數字到Server的Add函數,然後返回相加的結果給Client)。

安裝protoc

不同於WS 或API,gRPC需要安裝protobuf才能進行繼續的開發,這是因為我們首先會編寫.proto (protocal buffers檔案)來定義需要傳輸的資料(message)及使用的程式(service),需要將我們編寫的.proto轉換成不同程式語言的Stub,Stub會提供介面及函數來供開發的程式呼叫,而安裝的protobuf CLI能幫我們進行此轉換。

此連結(https://github.com/protocolbuffers/protobuf/releases)包含了protoc的source code及不同系統的編譯完成的版本,因為我們在windows上開發,下載win64的版本後解壓縮即可使用,如下圖:

記得要去設定環境變數,於系統環境變數中的Path新增此protoc bin檔的路徑(如筆者為例, 新增 C:\Program Files\protoc-21.5-win64\bin),以讓console輸入時能找到,完成後,打開console輸入 protoc –version,返回libprotoc x.x.x 表示成功囉。

接著,為了讓protoc能編譯出go 的程式(因為我們以go來開發),需要安裝go 的套件如下:

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

寫我們程式要用的.Proto

我們的應用很簡單:client傳送兩個數字,並呼叫server提供的Add函數,server收到後執行Add函數並傳回結果給client。因此我們定義一個Request,包含兩個Int32的變數,一個Result,包含一個Int32的變數,並定義一個名為Add的Service,.proto檔案詳如下:

syntax = "proto3";  // 定義要使用的 protocol buffer 版本

package adder;  
option go_package = ".;adder";  // for go 定義路徑

service AddService {
  rpc Add(AdderRequest) returns (AdderResult) {}
}


message AdderRequest {
  int32 a = 1;
  int32 b = 2;
}

message AdderResult {
  int32 result = 1;
}

其中syntax =”proto3″ 讓protoc知道編譯的版本。完成後我們存為adder.proto檔。

接著,我們於adder.proto檔案的同一個資料夾執行$ protoc –go_out=. –go_opt=paths=source_relative –go-grpc_out=. –go-grpc_opt=paths=source_relative adder.proto,會發現產生了adder.pb.go及adder_grpc.pb.go兩個go的檔案,有興趣可以看看這兩個檔案的內容,基本上就是將我們上述的定義轉換成了gRPC的go程式碼。這兩個程式基本上我們不會去改動它,但會呼叫它的功能及物件定義。

開發Server

在Server中我們要實作Add這個func,並建立一個listen tcp的port來等待client的呼叫,直接上code比較清楚:

package main

import (
	"context"
	"fmt"
	"net"
	"os"

	adder "grpc/adder"   //此為 adder.pb.go 包的路徑

	"google.golang.org/grpc"
)

type Server struct {
	adder.UnimplementedAddServiceServer
}

//實作Add這個被呼叫的函數
func (*Server) Add(ctx context.Context, req *adder.AdderRequest) (*adder.AdderResult, error) {
	fmt.Printf("input data: %v \n", req)

	a := req.GetA()
	b := req.GetB()

	result := &adder.AdderResult{
		Result: a + b,
	}

	return result, nil
}

func main() {

	fmt.Println("starting gRPC server")
	listen, err := net.Listen("tcp", "localhost:50081")
	if err != nil {
		fmt.Printf("Listen 失敗, error= %v", err)
	}

	grpcServer := grpc.NewServer()
	adder.RegisterAddServiceServer(grpcServer, &Server{})
	if err := grpcServer.Serve(listen); err != nil {
		fmt.Printf("serve 失敗, err=%v", err)
		os.Exit(1)
	}

}

開發Client

Client重點就是建立與Server的連線,然後呼叫Add的函數了,看code如何寫來達成我們的目的囉。

package main

import (
	"context"
	"fmt"

	adder "grpc/adder"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {

	fmt.Println("starting grpc client")
	conn, err := grpc.Dial("localhost:50081", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("Dial 失敗, error=%v", err)
	}

	defer conn.Close()

	client := adder.NewAddServiceClient(conn)

	req := &adder.AdderRequest{
		A: 9,
		B: 109,
	}
	result, err := client.Add(context.Background(), req)//如同呼叫本地函數一樣
	if err != nil {
		fmt.Printf("Add 失敗, error=%v", err)
	}
	fmt.Printf("result is : %v \n", result)
	fmt.Println("end client")

}

以上就是一個很簡單的Go的 gRPC入門的程式,若有任何問題歡迎留言討論囉。