使用gRPC從零開始構建Go微服務
開發環境準備
工欲善其事必先利其器,在構建Go微服務前準備好開發環境可以提供工作效率和工作的舒心度。
-
Go
RPC/">gRPC要求Go語言的版本不低於1.6,建議使用最新穩定版本。如果沒有安裝Go或Go的版本低於1.6,參考ofollow,noindex">Go安裝指南
$ go version
-
安裝gRPC
可以使用如下命令安裝gRCP:
$ go get -u google.golang.org/grpc
-
安裝 Protocol Buffers v3
安裝protoc編譯器的目的是生成服務程式碼,從https://github.com/google/protobuf/releases 下載已預編譯好的二進位制檔案是安裝protoc最簡單的方法。
1.1 解壓檔案
1.2 將二進位制檔案所在的目錄新增到環境變數 PATH 中。
安裝Go版本的protoc外掛
$ go get -u github.com/golang/protobuf/protoc-gen-go
預設編譯外掛protoc-gen-to安裝在 $GOPATH/bin 目錄下,該目錄需要新增到環境變數 PATH 中。
定義服務
在本文中定義一個產品服務ProductService,服務提供兩個簡單的基本功能
- 新增產品
- 刪除產品
- 根據產品Id查詢產品詳情
-
查詢所有產品詳情
ProductService.poto檔案的具體內容如下:
// protocol buffer 語法版本 syntax = "proto3"; // 產品服務定義 service ProductService { // 新增產品 rpc AddProduct (AddProductRequest) returns (AddProductResponse) { } // 刪除產品 rpc DeleteProduct (DeleteProductRequest) returns (EmptyResponse) { } // 根據產品Id查詢產品詳情 rpc QueryProductInfo (QueryProductRequest) returns (ProductInfoResponse) { } // 查詢所有產品詳情 rpc QueryProductsInfo (EmptyRequest) returns (ProductsInfoResponse) { } } // 請求/響應結構體定義 // 新增產品message message AddProductRequest { enum Classfication { FRUIT = 0; MEAT = 1; STAPLE = 2; TOILETRIES = 3; DRESS = 4; } string productName = 1; Classfication classification = 2; string manufacturerId = 3; double weight = 4; int64 productionDate = 5; } // 新增產品,服務端響應message message AddProductResponse { string productId = 1; string message = 2; } // 刪除產品message message DeleteProductRequest { string productId = 1; } message QueryProductRequest { string productId = 1; } // 單產品詳情message message ProductInfoResponse { string productName = 1; string productId = 2; string manufacturerId = 3; double weight = 4; int64 productionDate = 5; int64 importDate = 6; } message ProductsInfoResponse { repeated ProductInfoResponse infos = 1; } message EmptyRequest { } message EmptyResponse { }
一個方法不需要入參或沒有返回值時,在gRPC中使用空的message代替,參考stackoverflow
生成客戶端和服務端程式碼
服務定義檔案ProductService.poto在工程中的路徑為:src/grpc/servicedef/product/ProductService.poto,進入servicedef目錄,執行以下命令生成Go版本的客戶端和服務端程式碼:
$ protoc -I product/ ProductService.proto --go_out=plugins=grpc:product
命令執行完成後,會在product目錄下生成一個名為ProductService.pb.go的檔案,檔案的內容為Go版本的客戶端和服務端程式碼。
服務端實現
服務端需要完成兩項工作才能對外提供RPC服務:
- 實現ProductServiceServer介面,ProductServiceServer介面是protoc編譯器自動生成。在Go某個物件實現一個介面,只需要實現該介面的所有方法。
-
啟動gRPC Server用來處理客戶端請求。
服務端具體實現程式碼
package main import ( "log" "net" "time" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/reflection" pb "grpc/servicedef/product" "math/rand" "strconv" ) const ( port = ":5230" ) var dataBase = make(map[string]*Product, 10) type Product struct { ProductNamestring ProductIdstring ManufacturerId string Weightfloat64 ProductionDate int64 ImportDateint64 } type server struct{} func (s *server) AddProduct(ctx context.Context, request *pb.AddProductRequest) (*pb.AddProductResponse, error) { log.Printf("get request from client to add product,request is %s", request) productId := strconv.FormatInt(rand.Int63(), 10) product :=new (Product) product.ProductName = request.ProductName product.ProductId = productId product.ManufacturerId = request.ManufacturerId product.Weight = request.Weight product.ProductionDate = request.ProductionDate product.ImportDate = time.Now().UnixNano() dataBase[productId] = product return &pb.AddProductResponse{ProductId: productId, Message: "Add product success"}, nil } func (s *server) DeleteProduct(ctx context.Context, request *pb.DeleteProductRequest) (*pb.EmptyResponse, error) { log.Printf("get request from client to add product,request is %s", request) productId := request.ProductId delete(dataBase, productId) return nil, nil } func (s *server) QueryProductInfo(ctx context.Context, request *pb.QueryProductRequest) (*pb.ProductInfoResponse, error) { log.Printf("get request from client fro query product info,%v", request) productId := request.ProductId product := dataBase[productId] response:=new(pb.ProductInfoResponse) response.ProductName = product.ProductName response.ProductId = product.ProductId response.ManufacturerId = product.ManufacturerId response.Weight = product.Weight response.ProductionDate = product.ProductionDate response.ImportDate = product.ImportDate return response, nil } func (s *server) QueryProductsInfo(ctx context.Context, request *pb.EmptyRequest) (*pb.ProductsInfoResponse, error) { // 待實現 return nil, nil } func main() { log.Printf("begin to start rpc server") lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterProductServiceServer(s, &server{}) // Register reflection service on gRPC server. reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
客戶端實現
客戶端非常的簡單,就像gRPC介紹中一樣,可以像呼叫本地方法一樣呼叫遠端gRPC服務,一個詳細的例子如下:
package main import ( "log" "time" "golang.org/x/net/context" "google.golang.org/grpc" pb "grpc/servicedef/product" ) const ( address = "localhost:5230" ) func main(){ // 建立一個與服務端的連線. conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewProductServiceClient(conn) ctx, cancel := context.WithTimeout(context.Background(), time.Second) response,err := client.AddProduct(ctx,&pb.AddProductRequest{ProductName:"phone"}) if nil != err { log.Fatalf("add product failed, %v",err) } log.Printf("add product success,%s",response) productId:=response.ProductId queryResp,err :=client.QueryProductInfo(ctx,&pb.QueryProductRequest{ProductId: productId}) if nil !=err { log.Fatalf("query product info failed,%v",err) } log.Printf("Product info is %v",queryResp) defer cancel() }