go微服務系列(四) - gRPC入門
阿新 • • 發佈:2020-08-12
- [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