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ù)端的交互模式如下:

首先我們需要定義 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 文件:

然后在 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é)果輸出了:

當(dāng)然你也可以在服務(wù)端的方法中,加上一些日志來(lái)驗(yàn)證一下。
最后,這個(gè)小的 demo 的目錄結(jié)構(gòu)就是這樣的:

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