gRPC & Protocol Buffer 構建高效能介面實踐

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

文章介紹了如何使用 gRPC 和 ProtoBuf,快速串連 gRPC 可以參考這篇文章第一段:gRPC quick Start。

介面開發是軟體開發佔據舉足輕重的地位,是現代軟體開發之基石。體現在無論是前後端分離的 Web 前端還是移動用戶端,乃至基於不同系統、程式設計語言構建的軟體系統之間,API 都是充當橋樑的作用把不同端的系統連結在一起從而形成了一套穩固的商用系統。

基於 Web 的介面通常都是 RESTful API 結合 JSON 在前後端之間傳遞資訊,這種模式比較適合於前後端分離及移動用戶端於後端通訊;但對於承載大規模並發、高效能要求的微服務架構,基於 JSON 傳輸的 RESTful 是否還適用於高並發、伸縮性強及商務邏輯複雜的軟體架構嗎?基於 RESTful 架構是否能夠簡單是想雙向流 (bidrectional stream) 的介面。gRPC 和 protocol buffer 就是解決上述問題。

關於 gRPC 和 Protobuf 的簡介可以看看這篇文章:Google Protocol Buffer 和 gRPC 簡介

gRPC & Protocol Buffer 實踐

我本地的 GOPATH 目錄為 /Users/hww/work/go ,給我們的 demo 項目建立一個目錄 cd $GOPATH/src && mkdir rpc-protobuf

定義 Protocol Buffer 的訊息類型和服務

在項目根目錄 rpc-protobuf 建立檔案目錄 customer。首先給 Protocol Bufffer 檔案定義服務介面和 paylaod 資訊的資料結構,$GOPATH/scr/rpc-protobuf/customer/customer.proto:

syntax = "proto3";package customer;// The Customer sercie definitionservice Customer {    // Get all Customers with filter - A server-to-client streaming RPC.    rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}    // Create a new Customer - A simple RPC    rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}}message CustomerRequest {    int32 id = 1;   // Unique ID number for a Customer.    string name = 2;    string email = 3;    string phone = 4;    message Address {        string street = 1;        string city = 2;        string state = 3;        string zip = 4;        bool isShippingAddress = 5;    }    repeated Address addresses = 5;}message CustomerResponse {    int32 id = 1;    bool success = 2;}message CustomerFilter {    string keyword = 1;}

.proto 檔案,第一行代碼為版本號碼,在這裡我們使用了 proto3 ;第二行代碼為包名,通過該檔案產生的 Go 源碼包名和這裡的一致為 customer

我們定義了訊息類型和服務介面。標準資料類型有 int32, float, double, 或 string 這些常見的類型。一種訊息類型就是多個欄位的集合,每個欄位都被一個在該訊息中唯一的整數標記;Customer 服務中有兩個 RPC 方法:

service Customer {    // Get all Customers with filter - A server-to-client streaming RPC.    rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}    // Create a new Customer - A simple RPC    rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}}

解釋 Customer 服務之前,我們首先來大概瞭解一下 gRPC 中的三種類型的 RPC 方法。

  • simple RPC
    應用於常見的典型的 Request/Response 模型。用戶端通過 stub 請求 RPC 的服務端並等待服務端的響應。
  • Server-side streaming RPC
    用戶端給服務端發送一個請求並擷取服務端返回的流,用以讀取一連串的服務端響應。stream 關鍵字在響應類型的前面。
    // 例子rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
  • Client-side streaming RPC
    用戶端發送的請求 payload 有一連串的的資訊,通過流給服務端發送請求。stream 關鍵字在請求類型前面。
    // 例子rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}
  • Bidirectional streaming RPC
    服務端和用戶端之間都使用 read-write stream 進行通訊。stream 關鍵字在請求類型和響應類型前面。
    // 例子rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}

理解 gRPC 提供的四種類型 RPC 方法之後,回到 Customer 的例子中。在 Customer 服務提供了兩種類型的 RPC 方法,分別是 simple RPC(CreateCustomer) 和 server-side streaming(GetCustomers) 。CreateCustomer 遵循標準 Request/Response 規範建立一個使用者;GetCustomers 方法中,服務端通過 stream 返回多個消費者資訊的列表。

基於 proto 檔案產生服務端和用戶端的 Go 代碼

定義好 proto 檔案之後,然後產生你需要的程式設計語言原始碼,這些原始碼是服務端和用戶端商務邏輯代碼的介面。用戶端代碼通過訊息類型和服務介面調用 RPC 方法。
protocol buffer 編譯器通過 gRPC 的 Go 外掛程式產生用戶端和服務端的代碼。在項目根目錄下運行命令:

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

在 customer 目錄下產生了 customer.pb.go 檔案。該源碼包含三大類功能:

  • 讀寫和序列化請求和響應訊息類型
  • 提供定義在 proto 檔案中定義的用戶端調用方法介面
  • 提供定義在 proto 檔案中定義的服務端實現方法介面

建立 gRPC 服務

以下程式碼片段建立依據 proto 檔案中定義的服務建立 gRPC 服務端。

// server/main.gopackage mainimport (    "log"    "net"    "strings"    "golang.org/x/net/context"    "google.golang.org/grpc"    pb "rpc-protobuf/customer")const (    port = ":50051")// server is used to implement customer.CustomerServer.type server struct {    savedCustomers []*pb.CustomerRequest}// CreateCustomer creates a new Customerfunc (s *server) CreateCustomer(ctx context.Context, in *pb.CustomerRequest) (*pb.CustomerResponse, error) {    s.savedCustomers = append(s.savedCustomers, in)    return &pb.CustomerResponse{Id: in.Id, Success: true}, nil}// GetCustomers returns all customers by given filterfunc (s *server) GetCustomers(filter *pb.CustomerFilter, stream pb.Customer_GetCustomersServer) error {    for _, customer := range s.savedCustomers {        if filter.Keyword != "" {            if !strings.Contains(customer.Name, filter.Keyword) {                continue            }        }        if err := stream.Send(customer); err != nil {            return err        }    }    return nil}func main() {    lis, err := net.Listen("tcp", port)    if err != nil {        log.Fatal("failed to listen: %v", err)    }    //Create a new grpc server    s := grpc.NewServer()    pb.RegisterCustomerServer(s, &server{})    s.Serve(lis)}

服務端源碼中,server 結構體定義在 customer.pb.go 中的 CustomerServer 介面;CreateCustomerGetCustomers 兩個方法定義在 customer.pb.go 檔案的 CustomerClient 介面中。

CreateCustomer 是一個 simple rpc 類型的 RPC 方法,在這裡它接受兩個參數,分別是 context objct 和用戶端的請求資訊,傳回值為 proto 檔案定義好的 CustomerResponse 對象;GetCustomers 是一個 server-side streaming 類型的 RPC 方法,接受兩個參數:CustomerRequest 對象、以及用來作為服務端對用戶端響應 stream 的 對象 Customer_GetCustomersServer 。
看看 customer.pb.go 中對 CustomerServer 介面的定義:

// Server API for Customer servicetype CustomerServer interface {    // Get all Customers with filter - A server-to-client streaming RPC.    GetCustomers(*CustomerFilter, Customer_GetCustomersServer) error    // Create a new Customer - A simple RPC    CreateCustomer(context.Context, *CustomerRequest) (*CustomerResponse, error)}

對比理解服務端代碼對兩個方法的實現,我們就可以理解參數的傳遞原理。

服務端代碼中 GetCustomers 方法內部有一行代碼 stream.Send(customer) 這個 Send 方法是 customer.pb.go 給 Customer_GetCustomersServer 介面定義並好的方法,表示給用戶端返回 stream

最後看看服務端代碼中的 main 方法。
首先 grpc.NewServer 函數建立一個 gRPC 服務端;
然後調用 customer.pb.go 中的 RegisterCustomerServer(s *grpc.Server, srv CustomerServer) 函數註冊該服務:pb.RegisterCustomerServer(s, &server{})
最後通過 gRPC 的 Golang API Server.Serve 監聽指定的連接埠號碼:s.Serve(lis),建立一個 ServerTransportservice goroutine處理監聽的連接埠收到的請求。

建立 gRPC 用戶端

首先看 customer.pb.go 產生的用戶端調用方法介面部分的代碼:

// Client API for Customer servicetype CustomerClient interface {    // Get all Customers with filter - A server-to-client streaming RPC.    GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error)    // Create a new Customer - A simple RPC    CreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error)}type customerClient struct {    cc *grpc.ClientConn}func NewCustomerClient(cc *grpc.ClientConn) CustomerClient {    return &customerClient{cc}}

*grpc.ClientConn 表示串連到 RPC 服務端的用戶端,NewCustomerClient 函數返回一個 customerClient 結構體對象。CustomerClient 介面定義了兩個能夠被用戶端服務調用的方法,另外我們可以在 customer.pb.go 看到給 customerClient 類型的結構體實現這兩個函數的方法,故用戶端對象能夠調用 GetCustomersCreateCustomer 方法:

func (c *customerClient) GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error) {  ...}...func (c *customerClient) CreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error) {  ...}

接著回到實現用戶端的源碼:

// client/main.gopackage mainimport (    "io"    "log"    "golang.org/x/net/context"    "google.golang.org/grpc"    pb "rpc-protobuf/customer")const (    address = "localhost:50051")// createCustomer calls the RPC method CreateCustomer of CustomerServerfunc createCustomer(client pb.CustomerClient, customer *pb.CustomerRequest) {    resp, err := client.CreateCustomer(context.Background(), customer)    if err != nil {        log.Fatalf("Could not create Customer: %v", err)    }    if resp.Success {        log.Printf("A new Customer has been added with id: %d", resp.Id)    }}// GetCustomers calls the RPC method GetCustomers of CustomerServerfunc getCustomers(client pb.CustomerClient, filter *pb.CustomerFilter) {    // calling the streaming API    stream, err := client.GetCustomers(context.Background(), filter)    if err != nil {        log.Fatal("Error on get customers: %v", err)    }    for {        customer, err := stream.Recv()        if err == io.EOF {            break        }        if err != nil {            log.Fatal("%v.GetCustomers(_) = _, %v", client, err)        }        log.Printf("Customer: %v", customer)    }}func main() {    // Set up a connection to the RPC server    conn, err := grpc.Dial(address, grpc.WithInsecure())    if err != nil {        log.Fatal("did not connect: %v", err)    }    defer conn.Close()    // creates a new CustomerClient    client := pb.NewCustomerClient(conn)    customer := &pb.CustomerRequest{        Id:    101,        Name:  "Shiju Varghese",        Email: "shiju@xyz.com",        Phone: "732-757-2923",        Addresses: []*pb.CustomerRequest_Address{            &pb.CustomerRequest_Address{                Street:            "1 Mission Street",                City:              "San Francisco",                State:             "CA",                Zip:               "94105",                IsShippingAddress: false,            },            &pb.CustomerRequest_Address{                Street:            "Greenfield",                City:              "Kochi",                State:             "KL",                Zip:               "68356",                IsShippingAddress: true,            },        },    }    // Create a new customer    createCustomer(client, customer)    customer = &pb.CustomerRequest{        Id:    102,        Name:  "Irene Rose",        Email: "irene@xyz.com",        Phone: "732-757-2924",        Addresses: []*pb.CustomerRequest_Address{            &pb.CustomerRequest_Address{                Street:            "1 Mission Street",                City:              "San Francisco",                State:             "CA",                Zip:               "94105",                IsShippingAddress: true,            },        },    }    // Create a new customer    createCustomer(client, customer)    //Filter with an empty Keyword    filter := &pb.CustomerFilter{Keyword: ""}    getCustomers(client, filter)}

用戶端需要建立 gRPC 通道(channel) 才可與服務端建立通訊,調用 RPC 方法。grpc.Dial 函數表示建立與 RPC 服務端的串連。Dial函數在 gRPC golang 實現的庫中聲明代碼如下:

func Dial(target string, opts ...DialOption) (*ClientConn, error)

除了串連地址作為第一個參數外,還可以傳多個選擇性參數。這些選擇性參數表示鑒權校正,例如 TLS 或者 JWT 。在這裡的 grpc.WithInsecure 表示用戶端串連的安全傳輸被禁用。

調用服務端的 RPC 方法前,首先需要建立用戶端 stub :

// creates a new CustomerClientclient := pb.NewCustomerClient(conn)

在例子中,通過調用 RPC CreateCustomer 方法新增了兩個 customer 資料 : createCustomer(client, customer) ;調用 RPC GetCustomers 方法擷取所有 customers 資料。

至此,我們已經簡單地實現了一套 gRPC 用戶端和服務端代碼。在項目根目錄下運行命令:

➜  rpc-protobuf (nohup go run server/main.go &) && go run client/main.goappending output to nohup.out2017/10/28 18:08:02 A new Customer has been added with id: 1012017/10/28 18:08:02 A new Customer has been added with id: 1022017/10/28 18:08:02 Customer: id:101 name:"Shiju Varghese" email:"shiju@xyz.com" phone:"732-757-2923" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" > addresses:<street:"Greenfield" city:"Kochi" state:"KL" zip:"68356" isShippingAddress:true >2017/10/28 18:08:02 Customer: id:102 name:"Irene Rose" email:"irene@xyz.com" phone:"732-757-2924" addresses:<street:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" isShippingAddress:true >

參考來源

Building High Performance APIs In Go Using gRPC And Protocol Buffers

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.