gRPC的簡介與實例詳解

什麼是gRPC

gRPC是什麼?可以用官網的一句話來概括:A high-performance, open-source universal RPC framework。

所謂RPC(remote procedure call 遠程過程調用)框架實際是提供了一套機制,使得應用程序之間可以進行通信,而且也遵從server/client模型。使用的時候客戶端調用server端提供的接口就像是調用本地的函數一樣。如下圖所示就是一個典型的RPC結構圖。


gRPC vs Restful API

gRPC和restful API都提供了一套通信機制,用於server/client模型通信,而且它們都使用http作爲底層的傳輸協議(嚴格地說,gRPC使用的http2.0,而restful api則不一定)。不過gRPC還是有些特有的優勢,如下:

gRPC可以通過protobuf來定義接口,從而可以有更加嚴格的接口約束條件。關於protobuf可以參見下期Protobuf簡明教程,另外,通過protobuf可以將數據序列化爲二進制編碼,這會大幅減少需要傳輸的數據量,從而大幅提高性能。

gRPC可以方便地支持流式通信(理論上通過http2.0就可以使用streaming模式, 但是通常web服務的restful api似乎很少這麼用,通常的流式數據應用如視頻流,一般都會使用專門的協議如HLS,RTMP等,這些就不是我們通常web服務了,而是有專門的服務器應用。)


使用場景

  • 需要對接口進行嚴格約束的情況,比如我們提供了一個公共的服務,很多人,甚至公司外部的人也可以訪問這個服務,這時對於接口我們希望有更加嚴格的約束,我們不希望客戶端給我們傳遞任意的數據,尤其是考慮到安全性的因素,我們通常需要對接口進行更加嚴格的約束。這時gRPC就可以通過protobuf來提供嚴格的接口約束。

  • 對於性能有更高的要求時。有時我們的服務需要傳遞大量的數據,而又希望不影響我們的性能,這個時候也可以考慮gRPC服務,因爲通過protobuf我們可以將數據壓縮編碼轉化爲二進制格式,通常傳遞的數據量要小得多,而且通過http2我們可以實現異步的請求,從而大大提高了通信效率。

但是,通常我們不會去單獨使用gRPC,而是將gRPC作爲一個部件進行使用,這是因爲在生產環境,我們面對大併發的情況下,需要使用分佈式系統來去處理,而gRPC並沒有提供分佈式系統相關的一些必要組件。而且,真正的線上服務還需要提供包括負載均衡,限流熔斷,監控報警,服務註冊和發現等等必要的組件。不過,這就不屬於本篇文章討論的主題了,我們還是先繼續看下如何使用gRPC。


gRPC DEMO實例詳解

  • 通過protobuf來定義接口和數據類型

  • 編寫gRPC server端代碼

  • 編寫gRPC client端代碼
    本文使用golang去實現demo,其中protobuf和grpc擴展的安裝就跳過了。

新建userrpc.proto

syntax = "proto3";
package user;
option go_package = "./grpc/user";

// The User service definition.
service User {   
  // Get all Users with id - A server-to-client streaming RPC.
  rpc GetUsers(UserFilter) returns (stream UserRequest) {}
  // Create a new User - A simple RPC 
  rpc CreateUser (UserRequest) returns (UserResponse) {}
}

// Request message for creating a new user
message UserRequest {
  int32 id = 1;  // Unique ID number for a User.
  string name = 2;
  string email = 3;
  string phone= 4;

  message Address {
    string province = 1;
    string city = 2;  
  }
  repeated Address addresses = 5;
}

message UserResponse {
  int32 id = 1;
  bool success = 2;
}
message UserFilter {
  int32 id = 1;
}

編譯 .proto 文件

protoc  --go_out=plugins=grpc:. userrpc.proto


新建服務端server.go

package main

import (
    "log"
    "net"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "userrpc/grpc/user"
)

const (
    port = ":50051"
)

// server is used to implement user.UserServer.
type server struct {
    savedUsers []*pb.UserRequest
}

// CreateUser creates a new User
func (s *server) CreateUser(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {

    s.savedUsers = append(s.savedUsers, in)
    return &pb.UserResponse{Id: in.Id, Success: true}, nil
}

// GetUsers returns all users by given id
func (s *server) GetUsers(filter *pb.UserFilter, stream pb.User_GetUsersServer) error {
    for _, user := range s.savedUsers {
        if filter.Id == 0 {
            continue
        }
        if err := stream.Send(user); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    // Creates a new gRPC server
    s := grpc.NewServer()
    pb.RegisterUserServer(s, &server{})
    s.Serve(lis)
}

客戶端client.go

package main

import (
    "io"
    "log"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "userrpc/grpc/user"
)

const (
    address = "localhost:50051"
)

// createUser calls the RPC method CreateUser of UserServer
func createUser(client pb.UserClient, user *pb.UserRequest) {
    resp, err := client.CreateUser(context.Background(), user)
    if err != nil {
        log.Fatalf("Could not create User: %v", err)
    }
    if resp.Success {
        log.Printf("A new User has been added with id: %d", resp.Id)
    }
}

// getUsers calls the RPC method GetUsers of UserServer
func getUsers(client pb.UserClient, id *pb.UserFilter) {
    // calling the streaming API
    stream, err := client.GetUsers(context.Background(), id)
    if err != nil {
        log.Fatalf("Error on get users: %v", err)
    }
    for {
        user, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("%v.GetUsers(_) = _, %v", client, err)
        }
        log.Printf("User: %v", user)
    }
}
func main() {
    // Set up a connection to the gRPC server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    // Creates a new UserClient
    client := pb.NewUserClient(conn)

    user := &pb.UserRequest{
        Id:    1,
        Name:  "test",
        Email: "[email protected]",
        Phone: "132222222",
        Addresses: []*pb.UserRequest_Address{
            &pb.UserRequest_Address{
                Province: "hebei",
                City:     "shijiazhuang",
            },
        },
    }

    // Create a new user
    createUser(client, user)
    // Filter with an  id
    filter := &pb.UserFilter{Id: 1}
    getUsers(client, filter)
}

啓動server.go

go run server.go

新打開一個窗口,啓動client.go

go run client.go

結果爲

2019/07/04 17:01:16 A new User has been added with id: 1
2019/07/04 17:01:16 User: id:1 name:"test" email:"[email protected]" phone:"132222222" addresses:<province:"hebei" city:"shijiazhuang" >

小結

Api實現起來比較繁瑣,給開發帶來難度。總的來說gRPC是一個不錯的跨語言rpc解決方案,當然每個人都自己的看法或見解。針對不同的業務場景採用不同的解決方案,最終都是運行效率和開發效率的相互妥協的結果。


睿江雲官網鏈接:https://www.eflycloud.com/home?from=RJ0035


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