【實戰分享】從選型到項目落地,漫談 gRPC


什么是 gRPC?

gRPC 的幾種常見模式

在學習 gRPC 的時候,相信大家對於它的四種模式都有了解,我們來簡單回顧一下:

  • 簡單模式(Simple RPC):這種模式最為傳統,即客戶端發起一次請求,服務端響應一個數據,這和大家平時熟悉的 RPC 沒有什么大的區別,所以不再詳細介紹。

  • 服務端數據流模式(Server-side streaming RPC):這種模式是客戶端發起一次請求,服務端返回一段連續的數據流。典型的例子是客戶端向服務端發送一個股票代碼,服務端就把該股票的實時數據源源不斷的返回給客戶端。如果是使用我們容器雲功能的同學應該會發現,我們的容器實時日志流就是使用了這個典型模式。

  • 客戶端數據流模式(Client-side streaming RPC):與服務端數據流模式相反,這次是客戶端源源不斷地向服務端發送數據流,而在發送結束后,由服務端返回一個響應。典型的例子是物聯網終端向服務器報送數據。

  • 雙向數據流模式(Bidirectional streaming RPC):顧名思義,這是客戶端和服務端都可以向對方發送數據流,這個時候雙方的數據可以同時互相發送,也就是可以實現實時交互。典型的例子是聊天機器人。

接下來我們通過一個小例子來看看 gRPC 具體的使用流程。

假設我們有一個聊天機器人,現需要增加一個對外提供服務的接口。具體需求為,接口傳入參數是一個人名,返回一段內容是“Hello 人名”的音頻。如果這個是讓你在不使用 gRPC 的情況下,你會怎么做?大家可能會選擇使用 restful api 來實現這個功能,傳入人名,返回音頻二進制數據。

那么如果使用 gRPC,我們需要怎么來設計呢?

第一步,需要定義一個接口文檔,也就是 proto 文件。在定義內會定義一個 Service,接下來再在 Service 里定義一個 SayHello 的方法。下面定義傳入參數,輸入 name 返回 message,需要注意 message 是 bytes 類型,即返回的格式是二進制數據。對於 Golang 底層對應的是一個 bytes 數據,對於其他語言可能是字節流或二進制。

syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
bytes message = 1;

定義完成后 ,下一步就是使用 protoc 命令行工具生成代碼。下圖左側是初始化的項目,你會發現,有一個單獨的目錄 protoc,存放了 hello.proto 這個文件,這個文件就是前面定義好的。

下圖右側是自動生成代碼后的項目結構,生成了一個 pkg/helloworld 的包,里面有一個 hello.pb.go,打開這個文件,你會發現剛才定義的 proto 已經被翻譯成了 Go 語言。具體 protoc 命令行工具如何使用,可以自行搜索下,這里不再過多展開。

定義好了 proto 文件,以及生成了相應的 package 代碼,下一步我們就可以編寫業務邏輯了。

Hello gRPC - 服務端業務代碼

import (
"google.golang.org/grpc"
pb "grpc-hw/pkg/helloworld"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
tempFile := "srv.wav"
err := exec.Command("flite", "-t", "Hello "+in.GetName(), "-o", tempFile).Run()
if err != nil {
return nil, fmt.Errorf("make audio failed: %v", err)
}
data, _ := ioutil.ReadFile(tempFile)
return &pb.HelloReply{Message: data}, nil
}
func main() {
lis, _ := net.Listen("tcp", port)
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}

在服務端側,需要實現 SayHello 的方法來滿足 GreeterServer 接口的要求。SayHello 方法的傳入參數,是在 proto 文件中定義的 HelloRequest,傳出參數,是在 proto 文件中定義的 HelloReply,以及一個 error。

業務邏輯也比較簡單,獲取 HelloRequest 中 Name 字段,然后通過命令行行工具轉換成對應的音頻,將 bytes 數組存在在 HelloReply 中返回。

Hello gRPC - 客戶端業務代碼

func main() {
flag.StringVar(&address, "addr", address, "server address")
flag.StringVar(&name, "name", "world", "name")
flag.Parse()
// Set up a connection to the server.
conn, _ := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
tempFile := "cli.wav"
ioutil.WriteFile(tempFile, r.Message, 0666)
exec.Command("afplay", tempFile).Run()
}

我們再來看一下如何實現 Client。首先,建立一個 gRPC 的連接,並初始化 GreeterClient,然后直接調用下 GreeterClient 的 SayHello 的方法,將把返回結果另存為一個文件,通過播放器播放就可以了。

總體而言,整個使用過程很簡單,並且非常容易上手,讓開發可以更加關注在客戶端、服務端業務的實現,不用操心傳輸層的事情。

gRPC 的使用總結

通過剛剛的小例子,我們來總結一下 gRPC 的使用:

  • 定義好接口文檔

  • 工具生成服務端/客戶端代碼

  • 服務端補充業務代碼

  • 客戶端建立 gRPC 連接后,使用自動生成的代碼調用函數

  • 編譯、運行

以上 5 步就是 gRPC 的簡單使用方法了。

gRPC 與 Protobuf

接下來我們來聊聊 gRPC 跟 Protobuf 之間的聯系,當然在這之前我們需要先知道 Protobuf 是什么。

Protobuf

Protobuf 是一個語言無關、平台無關的可擴展的結構化數據序列化方案。大家可能會覺得它跟 JSON 好像沒什么區別,功能上看起來是一樣的,但像上文那樣去定義 SayHello 的操作,JSON 是做不到的,JSON 只能定義一個請求體或者一個返回體,沒法定義一個方法,但是 Protobuf 是可以的。

Protobuf 多用於協議通訊、數據存儲和其他更多用途。它是一個比較靈活的、高效的、自動化的結構化數據序列機制,但是更小,更快並且更簡單。一旦定義好數據如何構造, 就可以使用特殊生成的代碼來輕易地讀寫結構化數據,無需關心用什么語言來實現。你甚至可以更新數據結構而不打破已部署的使用"舊有"格式編譯的程序。

上圖是 Protobuf 與 JSON 以及 JSON stream 三者之間的性能比較,可以明顯的看到在解碼的時候 Protobuf 比其他兩項快了不只一星半點。

Protobuf  與 XML、JSON 的吞吐量比較

上圖中,我們可以看到 Protobuf 還是有一些缺點的,比如瀏覽器的支持沒有其他幾個支持的好。但是,在數據安全方面,由於傳輸過程中采用的是加密壓縮后的字節流,一般無法直接查看,安全性非常好。以及在處理速度方面,因為編解碼效率很高使得整體吞吐量有了顯著提升。還有一點,定義方法,這個是其他兩種序列化協議所做不到的。

gRPC 跟 Protobuf 的聯系

雖然每次 gRPC 與 Protobuf 都是同時出現的,但是其實兩者之間並沒有很深的聯系。只是因為兩者都是由 Google 開發的,和 gRPC 本身負載無關,在使用時也可以選擇 JSON 等等,但是考慮到 Protobuf 有定義方法的優勢,在微服務里還是很推薦使用的。

gRPC vs Restful API

上圖是 gRPC 與 Restful API 的對比,平常我們可能更多使用 Restful API。但從圖上可以看到,因為 gRPC 用的是 Protobuf,本身就比較小所以很快,不像 JSON 體積比較大、比較慢。另外,gRPC 是加載 HTTP/2 上面的,延遲比較低,也因為 HTTP/2 支持鏈接復用,這就可以讓多個 stream 共用一個連接,從而進一步提升速度。對比 Restful 則使用的是 HTTP 1.1,延遲比較高,而且在一般情況下,每次請求都需要建一下新的連接。

gRPC 是雙向的。什么是雙向呢?比如我們平常做 Restful,都是從客戶端到服務端,但是服務端沒辦法直接主動向客戶端發送信息,gRPC 則可以。gRPC 也支持流,Restful只支持Request/Response 這樣的機制。gRPC是面向 API 的,沒有限制,也面向增刪改查。gRPC 可以通過 Protobuf 直接生成代碼,而 Restful 需要依賴第三方。

另外 gRPC 支持 RPC 可以調用服務器上的一些方法,而 Restful 是基於HTTP的語法,很多東西需要自己去定義。這方面相信大家都有感觸,比如 REST 定義post/put/delete/get時,因為每個人都有自己的習慣,所以協作時需要溝通討論進行指定。但是 gRPC 就不需要了,它支持定義函數式辦法,不需要大家去考慮如何設計語法。

以上就是 gRPC 跟 Restful API 的對比。

引入 gRPC 需要考慮哪些問題?

那么當我們引入 gRPC 的時候需要考慮什么呢?以下幾點肯定是不可避免的考慮項:

  • 是否可以滿足當前需求

  • 性能如何

  • 連接異常斷開后,是否需要客戶端重試

  • TCP 連接是否可以復用

  • 業務層改動是否足夠便利

  • 業務后期迭代是否會出現問題,如何避免

這個也是我們引入一項新的東西時,往往需要考慮到的問題。

回顧與總結

從選擇 gRPC 到整個項目落地,以及現在上線后正常使用。整個過程中,我對於項目的思考可以包括了過去、現在和未來三個階段。

對我而言,過去就是要去看我選擇的這個東西,用的人多不多,完善程度怎么樣了?而現在則是要結合項目,看看合不合適,能不能使用。當然不能思考到能使用就結束,我們還需要考慮這個項目在未來的 3-5 年的發展,你引入它后在這個時間內需不需要大的變動。這個是非常重要的,雖然我們現在常說敏捷開發,也經常會進行很多的調整,但是在類似 gRPC 這種底層基礎來說,是固定的。

以上就是我今天的全部分享內容,講的比較簡單,希望能帶給大家一些收獲。

推薦閱讀

秋天的第一份“干貨” I Referer 防盜鏈,為什么少了個字母 R?

“網頁內容無法訪問”可能是跨域錯誤!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM