最簡(jiǎn)單的 gRPC 教程— 1 初識(shí) gRPC

gRPC 是 Google 開(kāi)源的一個(gè)高性能的 RPC(Remote Procedure Call) 框架,它具有如下的優(yōu)點(diǎn):

  • 提供高效的進(jìn)程間通信。gRPC 沒(méi)有使用 XML 或者 JSON 這種文本格式,而是采用了基于 protocol buffers 的二進(jìn)制協(xié)議;同時(shí),gRPC 采用了 HTTP/2 做為通信協(xié)議,從而能夠快速的處理進(jìn)程間通信。
  • 簡(jiǎn)單且良好的服務(wù)接口和模式。gRPC 為程序開(kāi)發(fā)提供了一種契約優(yōu)先的方式,必須首先定義服務(wù)接口,才能處理實(shí)現(xiàn)細(xì)節(jié)。
  • 支持多語(yǔ)言。gRPC 是語(yǔ)言中立的,我們可以選擇任意一種編程語(yǔ)言,都能夠與 gRPC 客戶端或者服務(wù)端進(jìn)行交互。
  • 成熟并且已被廣泛使用。通過(guò)在 Google 的大量實(shí)戰(zhàn)測(cè)試,gRPC 已經(jīng)發(fā)展成熟。

下面通過(guò)一個(gè)簡(jiǎn)單的 demo 來(lái)初步了解 gRPC 的使用。

我們構(gòu)建一個(gè)商品服務(wù),命名為 ProductInfo,客戶端和服務(wù)端的交互模式如下:

Xnip2021-03-24_22-39-48.png

首先我們需要定義 protobuf:

syntax = "proto3";
package product;

service ProductInfo {
  //添加商品
  rpc addProduct(Product) returns (ProductId);
  //獲取商品
  rpc getProduct(ProductId) returns (Product);
}

message Product {
  string id = 1;
  string name = 2;
  string description = 3;
}

message ProductId {
  string value = 1;
}

我們定義了一個(gè) ProductInfo 服務(wù),其中有兩個(gè)方法,分別是添加商品和獲取商品,然后在 proto 文件所在的目錄下執(zhí)行命令 protoc --go_out=plugins=grpc:../product ProductInfo.proto。

如果沒(méi)有安裝 protoc,執(zhí)行命令 go get -u github.com/golang/protobuf/protoc-gen-go 進(jìn)行安裝。雖然 gRPC 支持多種語(yǔ)言,但是為了統(tǒng)一,我文章中的代碼都使用 Go。

執(zhí)行完之后,在 proto 文件同級(jí)目錄下會(huì)出現(xiàn)一個(gè) ProductInfo.pb.go 文件:

Xnip2021-03-24_23-22-02.png

然后在 product 文件夾下新建一個(gè) server 文件夾,然后新建一個(gè) main.go 文件,首先在文件中實(shí)現(xiàn) ProductInfo 服務(wù)的兩個(gè)方法的業(yè)務(wù)邏輯:

package main

import (
   "context"
   "github.com/gofrs/uuid"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/status"
   "grpc-demo/product"
)

type server struct {
   productMap map[string]*product.Product
}

//添加商品
func (s *server) AddProduct(ctx context.Context, req *product.Product) (resp *product.ProductId, err error) {
   resp = &product.ProductId{}
   out, err := uuid.NewV4()
   if err != nil {
      return resp, status.Errorf(codes.Internal, "err while generate the uuid ", err)
   }
   
   req.Id = out.String()
   if s.productMap == nil {
      s.productMap = make(map[string]*product.Product) 
   }
   
   s.productMap[req.Id] = req
   resp.Value = req.Id
   return
}

//獲取商品
func (s *server) GetProduct(ctx context.Context, req *product.ProductId) (resp *product.Product, err error) {
   if s.productMap == nil {
      s.productMap = make(map[string]*product.Product)
   }

   resp = s.productMap[req.Value]
   return
}

然后繼續(xù)在 main.go 文件中添加一個(gè) main 方法,建立一個(gè) gRPC 服務(wù)器:

func main() {
   listener, err := net.Listen("tcp", port)
   if err != nil {
      log.Println("net listen err ", err)
      return
   }

   s := grpc.NewServer()
   product.RegisterProductInfoServer(s, &server{})
   log.Println("start gRPC listen on port " + port)
   if err := s.Serve(listener); err != nil {
      log.Println("failed to serve...", err)
      return
   }
}

服務(wù)端的邏輯就到這里了,接下來(lái)再寫(xiě)一下客戶端的邏輯,建立一個(gè) client 文件夾,然后新建一個(gè) main.go 文件,內(nèi)容如下:

package main

import (
   "context"
   "google.golang.org/grpc"
   "grpc-demo/product"
   "log"
)

const (
   address = "localhost:50051"
)

func main() {
   conn, err := grpc.Dial(address, grpc.WithInsecure())
   if err != nil {
      log.Println("did not connect.", err)
      return
   }
   defer conn.Close()

   client := product.NewProductInfoClient(conn)
   ctx := context.Background()

   id := AddProduct(ctx, client)
   GetProduct(ctx, client, id)
}

// 添加一個(gè)測(cè)試的商品
func AddProduct(ctx context.Context, client product.ProductInfoClient) (id string) {
   aMac := &product.Product{Name: "Mac Book Pro 2019", Description: "From Apple Inc."}
   productId, err := client.AddProduct(ctx, aMac)
   if err != nil {
      log.Println("add product fail.", err)
      return
   }
   log.Println("add product success, id = ", productId.Value)
   return productId.Value
}

// 獲取一個(gè)商品
func GetProduct(ctx context.Context, client product.ProductInfoClient, id string) {
   p, err := client.GetProduct(ctx, &product.ProductId{Value: id})
   if err != nil {
      log.Println("get product err.", err)
      return
   }
   log.Printf("get prodcut success : %+v\n", p)
}

然后先啟動(dòng) server/main.go ,再啟動(dòng) client/main.go,這樣一次服務(wù)端和客戶端之間的連接便完成了,可以看到運(yùn)行的結(jié)果輸出了:

Xnip2021-03-26_00-10-59.png

當(dāng)然你也可以在服務(wù)端的方法中,加上一些日志來(lái)驗(yàn)證一下。

最后,這個(gè)小的 demo 的目錄結(jié)構(gòu)就是這樣的:

Xnip2021-03-26_00-14-46.png

項(xiàng)目的代碼在我的GitHub 上:https://github.com/roseduan/grpc-demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容