Golang筆記 6.3 RPC 編程之 gRPC

前言

我正在學習酷酷的 Golang,可點此查看帖子Golang學習筆記彙總

1 它是什麼

gRPC 是一個高性能、開源、通用的 RPC 框架。

A high performance, open-source universal RPC framework.

2 爲什麼是它!

gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

  • 可運行在任何環境的RPC框架
  • 可以插件式的增加 load balancing, tracing, health checking and authentication 這些支持
  • 也適用於分佈式計算的最後一英里

3 詳細介紹

先了解幾個基本概念,https://grpc.io/docs/guides/concepts/

服務的定義

gRPC 默認使用 protocol buffers 作爲接口描述語言,用它來描述服務端接口及消息格式。

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

gRPC 允許你定義四類服務方法:

  • 單次 RPC,即客戶端發送一個請求給服務端,從服務端獲取一個應答,就像一次普通的函數調用;

    rpc SayHello(HelloRequest) returns (HelloResponse){
    }
    
  • 服務端流式 RPC,即客戶端發送一個請求給服務端,可獲取一個數據流用來讀取一系列消息。客戶端從返回的數據流裏一直讀取直到沒有更多消息爲止;

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
    }
    
  • 客戶端流式 RPC,即客戶端用提供的一個數據流寫入併發送一系列消息給服務端。一旦客戶端完成消息寫入,就等待服務端讀取這些消息並返回應答;

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
    }
    
  • 雙向流式 RPC,即兩邊都可以分別通過一個讀寫數據流來發送一系列消息。這兩個數據流操作是相互獨立的,所以客戶端和服務端能按其希望的任意順序讀寫,例如:服務端可以在寫應答前等待所有的客戶端消息,或者它可以先讀一個消息再寫一個消息,或者是讀寫相結合的其他方式。每個數據流裏消息的順序會被保持;

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
    }
    

使用 API surface

gRPC提供了 protocol buffer 編譯器插件,可生成客戶端和服務器端代碼。 gRPC用戶通常在客戶端(stub)調用這些API,並在服務器端實現相應的API。

同步與異步

阻塞的同步RPC調用直到服務器收到響應爲止是最接近RPC所追求的過程調用抽象的近似方法。 另一方面,網絡本質上是異步的,並且在許多情況下能夠啓動RPC而不阻塞當前線程很有用。

gRPC編程都有同步和異步兩種形式。

RPC 生命週期

4 golang 中怎麼用

參考 https://grpc.io/docs/quickstart/go/

4.1 安裝

安裝 gRPC

$ go get -u google.golang.org/grpc

安裝 PROTOC 編譯器

3.7.1 版本安裝示例:

$ PROTOC_ZIP=protoc-3.7.1-linux-x86_64.zip
$ curl -OL https://github.com/google/protobuf/releases/download/v3.7.1/$PROTOC_ZIP
$ sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
$ rm -f $PROTOC_ZIP

3.10.1 版本安裝示例:

PROTOC_ZIP=protoc-3.10.1-linux-x86_64.zip
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.10.1/$PROTOC_ZIP
sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
rm -f $PROTOC_ZIP

安裝 protoc 的 go 插件

$ go get -u github.com/golang/protobuf/protoc-gen-go

protoc-gen-go 將會安裝在 $GOBIN, 默認是 $GOPATH/bin. 需要在 $PATH 找到編譯器。

$ export PATH=$PATH:$GOPATH/bin

4.2 示例 - helloworld

運行

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

開兩個終端,分別執行 server 和 client:

$ go run greeter_server/main.go
$ go run greeter_client/main.go

這樣執行之後,終端上就有日誌打印出來了:

# go run greeter_client/main.go
2019/11/04 12:21:47 Greeting: Hello world
# go run greeter_server/main.go
2019/11/04 12:21:47 Received: world

proto 文件分析

// 1 定義服務
service Greeter {
  // 2 定義服務中的方法
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 3 定義方法中的請求消息格式
message HelloRequest {
  string name = 1;
}

// 4 定義方法中的回覆消息格式
message HelloReply {
  string message = 1;
}

client 源碼操作分析

  // Step1. grpc 連接
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
  defer conn.Close()

  // Step2. 新建客戶端    c := pb.New(ServiceName)Client(conn)
  c := pb.NewGreeterClient(conn)

	// Step3. 準備 context 用於 grpc 請求
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()
  
  // Step4. 客戶端調用預先定義的方法,並傳入方法對應的請求  c.(MethodName)(ctx, &pb.(RequestMessageName))
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

server 源碼操作分析

  // Step1. 配置監聽的協議和端口
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
  }
  
  // Step2. 新建 grpc 服務端
  s := grpc.NewServer()
  
  // Step3. 將 pb消息處理 的實例進行註冊
  pb.RegisterGreeterServer(s, &server{})
  
  // Step4. 啓動監聽服務
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
  }

除了這4步常規操作之外,更重要的是定義 pb消息處理 的實例:

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.Name)
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

4.3 示例修改 - 增加一個新的方法

在 pb 中增加新的方法

service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

使用 protoc 自動更新代碼

protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld

在 greeter_server/main.go 中更新 server 代碼

func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}

在 greeter_client/main.go 中更新 client 代碼

r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())

運行結果

$ go run greeter_client/main.go
Greeting: Hello world
Greeting: Hello again world

5 小結

  1. 在 .proto 文件中定義服務
  2. 使用 protocol buffer 編譯器產生 服務端和客戶端 代碼
  3. 使用 Go gRPC API 爲你的服務編寫客戶端和服務端

本篇筆記演示瞭如何使用 gRPC 框架,包括 proto 文件定義服務,使用 pb 編譯器產生代碼,最後使用 gRPC API 編寫 client 和 server 代碼。

END


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章