1. 程式人生 > >go微服務系列(四) - gRPC入門

go微服務系列(四) - gRPC入門

- [1. 前言](#head1) - [2. gRPC與Protobuf簡介](#head2) - [3. 安裝](#head3) - [4. 中間檔案演示](#head4) - [4.1 編寫中間檔案](#head5) - [4.2 執行protoc命令編譯成go中間檔案](#head6) - [5. 建立gRPC服務端](#head7) - [5.1 新建Product.protoc](#head8) - [5.2 執行protoc命令](#head9) - [5.3 實現RegisterProdServiceServer介面](#head10) - [5.4 準備工作完成,建立main函式將服務端跑起來](#head11) - [6. 建立gRPC客戶端](#head12) - [6.1 拷貝Product.pb.go到客戶端service資料夾下](#head13) - [6.2 編寫client的main函式](#head14) - [6.3 執行並顯示結果](#head15) ## 1. 前言 之前學習的go的微服務之間還是通過`REST API`的方式互相呼叫的,但既然要學習微服務,`gRPC`肯定是一個繞不過去的需要學習的技術, 所以就開搞吧 ## 2. gRPC與Protobuf簡介 `gRPC`是一款**語言中立**、**平臺中立**、開源的遠端過程呼叫系統 > 即:`gRPC`客戶端和服務端可以在多種環境中執行和互動,例如用`java`寫一個服務端,可以用go語言寫客戶端呼叫 微服務架構中,由於每個服務對應的程式碼庫是獨立執行的,無法直接呼叫,彼此間的通訊就是個大問題. gRPC可以實現將大的專案拆分為多個小且獨立的業務模組,也就是服務。各服務間使用高效的`protobuf`協議進行RPC呼叫,gRPC預設使用`protocol buffers`,這是google開源的一套成熟的結構資料序列化機制 > 當然也可以使用其他資料格式如JSON 可以用proto files建立gRPC服務,用message型別來定義方法引數和返回型別 ## 3. 安裝 - **第一步:下載grpc通用編譯器** 如下圖,解壓出來因平臺而異會是一個`protoc`或者`protoc.exe` > https://github.com/protocolbuffers/protobuf/releases ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn71b14sxj312l0g0di1.jpg) - **第二步:把下載的二進位制檔案路徑新增到環境變數中**(為了能全域性訪問protoc) - 這裡以為mac為例子 ```shell # 開啟存放環境變數的檔案 vim ~/.bash_profile # 新增如下,後面是路徑 alias protoc="/Users/emm/others/protoc-3.12.4-osx-x86_64/bin/protoc" # 重新整理環境變數 source ./.bash_profile ``` - **第三步: 安裝go專用的protoc的生成器** > go get github.com/golang/protobuf/protoc-gen-go 安裝後會在`GOPATH`目錄下生成可執行檔案,protobuf的編譯器外掛`protoc-gen-go`,等下執行`protoc`命令會自動呼叫這個外掛 ## 4. 中間檔案演示 ### 4.1 編寫中間檔案 這裡新建一個pbfiles資料夾用於存放`protoc`檔案 ```protobuf // 這個就是protobuf的中間檔案 // 指定的當前proto語法的版本,有2和3 syntax = "proto3"; // 指定等會檔案生成出來的package package service; // 定義request message ProductRequest{ int32 prod_id = 1; // 1代表順序 } // 定義response message ProductResponse{ int32 prod_stock = 1; // 1代表順序 } ``` ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn8eityngj30me0cp0uu.jpg) ### 4.2 執行protoc命令編譯成go中間檔案 然後執行以下的命令來生成`.go`結尾的檔案 - 下面的命令就是我們剛剛下的`protoc`包以及`protoc-gen-go`外掛的作用 ```shell # 編譯Product.proto之後輸出到service資料夾 protoc --go_out=../service Product.proto ``` 如下就在service資料夾自動生成了一個go檔案,並且它提示我們不要去修改它 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn8jpg8uzj31gk0k50wx.jpg) ## 5. 建立gRPC服務端 ### 5.1 新建Product.protoc 這個protoc檔案比上面的多出了一個service的定義和裡面的一個方法的定義 ```protobuf // 這個就是protobuf的中間檔案 // 指定的當前proto語法的版本,有2和3 syntax = "proto3"; // 指定等會檔案生成出來的package package service; // 定義request model message ProductRequest{ int32 prod_id = 1; // 1代表順序 } // 定義response model message ProductResponse{ int32 prod_stock = 1; // 1代表順序 } // 定義服務主體 service ProdService{ // 定義方法 rpc GetProductStock(ProductRequest) returns(ProductResponse); } ``` ### 5.2 執行protoc命令 **注意** - 這裡的protoc命令和之前的命令相比有點不一樣 ``` protoc --go_out=plugins=grpc:../service Product.proto ``` 然後還是會在service資料夾下生成一個`.go`的檔案 **有兩個比較需要注意的** 1. RegisterProdServiceServer > 後面需要在server中呼叫這個來註冊 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9mmp3j2j30nd0d776h.jpg) 2. ProdServiceServer的介面定義 > 我們需要繼承這個介面,即實現它所有的方法 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9nq5pd4j30p50ccmyx.jpg) ### 5.3 實現RegisterProdServiceServer介面 上面我們在`protoc`檔案中定義了一個`ProdService`中包含了一個`GetProductStock`的方法 這裡我們要實現自動生成的go檔案中的介面 ```go package service import "context" type ProdService struct { } func (ps *ProdService) GetProductStock(ctx context.Context, request *ProductRequest) (*ProductResponse, error) { return &ProductResponse{ProdStock: request.ProdId}, nil } ``` ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9q844nuj30zn0efjtz.jpg) ### 5.4 準備工作完成,建立main函式將服務端跑起來 前面的都是準備工作,這裡是真正把服務端跑起來的操作 **下面是服務端程式碼:** ```go package main import ( "gomicro-quickstart/grpc_demo/service" "google.golang.org/grpc" "log" "net" ) func main() { // 1. new一個grpc的server rpcServer := grpc.NewServer() // 2. 將剛剛我們新建的ProdService註冊進去 service.RegisterProdServiceServer(rpcServer, new(service.ProdService)) // 3. 新建一個listener,以tcp方式監聽8082埠 listener, err := net.Listen("tcp", ":8082") if err != nil { log.Fatal("服務監聽埠失敗", err) } // 4. 執行rpcServer,傳入listener _ = rpcServer.Serve(listener) } ``` **排坑**: - 如果遇見類似`undefined: grpc.SupportPackageIsVersion6`和`undefined: grpc.ClientConnInterface`的錯誤,可以修改go.mod將grpc版本改到1.27.0 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghn9v5g03mj30ni0aw0ua.jpg) ---- ## 6. 建立gRPC客戶端 - 新建一個`grpc_client`資料夾存放客戶端相關的 - 並在`grpc_client`資料夾下再新建一個`service`資料夾 ### 6.1 拷貝Product.pb.go到客戶端service資料夾下 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghna79yhtbj308g02owef.jpg) ### 6.2 編寫client的main函式 ```go package main import ( "context" "fmt" "gomicro-quickstart/grpc_client/service" "google.golang.org/grpc" "log" ) func main() { // 1. 新建連線,埠是服務端開放的8082埠 // 並且新增grpc.WithInsecure(),不然沒有證書會報錯 conn, err := grpc.Dial(":8082", grpc.WithInsecure()) if err != nil { log.Fatal(err) } // 退出時關閉連結 defer conn.Close() // 2. 呼叫Product.pb.go中的NewProdServiceClient方法 productServiceClient := service.NewProdServiceClient(conn) // 3. 直接像呼叫本地方法一樣呼叫GetProductStock方法 resp, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233}) if err != nil { log.Fatal("呼叫gRPC方法錯誤: ", err) } fmt.Println("呼叫gRPC方法成功,ProdStock = ", resp.ProdStock) } ``` ### 6.3 執行並顯示結果 - 先把服務端執行起來 - 再把客戶端執行起來 然後客戶端輸出正確的結果,第一個go的gRPC呼叫執行成功 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghnalvrgw4j31c10pzg