了解gRPC概念、使用案例與最佳實踐

隨著應用開發的推進,在眾多事項中,有一點我們較少擔憂:計算能力。由於雲服務供應商的出現,管理數據中心的憂慮減少了。所有資源都能在幾秒內按需獲取,這也導致了數據量的增加。大量數據在單一請求中通過多種媒介生成和傳輸。

數據量的增加帶來了序列化、反序列化及傳輸成本等額外活動。雖然我們不擔心計算資源,但延遲成為了負擔。我們需要減少傳輸。過去為此開發了許多消息協議。SOAP過於臃腫,而REST是簡化版,但我們需要更高效的框架。這正是遠程過程調用(RPC)的用武之地。

本文將探討RPC的定義及其多種實現,重點關注Google的RPC實現——gRPC。我們還將比較REST與RPC,並深入了解gRPC的各個方面,包括安全性、工具支持等。

什麼是RPC?

RPC代表遠程過程調用。其定義已體現在名稱中。過程調用即函數/方法調用,關鍵在於“遠程”二字。如果我們能夠遠程調用函數,那將如何?

簡而言之,若一個函數存於伺服器上,且需從客戶端觸發,是否能簡化至如同本地方法/函數調用?RPC(遠程過程調用)的本質即為客戶端營造一種錯覺,彷彿調用的是本地方法,實則是遠端機器上的方法,此過程巧妙地抽象了網絡層的任務。其美妙之處在於合約保持極度嚴謹與透明(後文將詳述)。

RPC調用涉及的步驟:

RPC序列流程

典型的REST處理流程如下:

而RPC則將此流程簡化至:

這是因為與請求相關的所有複雜性已被抽象化,我們無需操心(後續將於代碼生成部分討論),只需專注於數據與邏輯。

gRPC:其意義、原因及運作方式

至此,我們已探討了RPC,即遠程方法/函數調用,帶來諸如“嚴格的合約定義”、“抽象數據傳輸與轉換”、“降低延遲”等益處,後續將深入討論。接下來,我們將深入探究RPC的一種實現——gRPC,它基於RPC概念構建的框架。

RPC的多種實現包括:

  • gRPC(由Google開發)

  • Thrift(Facebook)

  • Finalge(Twitter)

Google的RPC版本稱為gRPC,於2015年推出,自此逐漸受到重視,成為微服務架構中最受青睞的通訊機制之一。

gRPC採用protocol buffers(一種開源訊息格式)作為客戶端與伺服器間的預設通訊方式,同時也以HTTP/2為預設協議。gRPC支援四種通訊類型:

接著來看gRPC中廣泛使用的訊息格式——協定緩衝區,又稱為protobuf。一個protobuf訊息類似以下所示:

message Person {
string name = 1;
string id = 2;
string email = 3;
}

在此,‘Person’是我們希望傳輸的訊息(作為請求/回應的一部分),它包含字段‘name’(字串類型)、‘id’(字串類型)和‘email’(字串類型)。數字1、2、3代表資料在序列化為二進位格式時的位置(例如‘name’、‘id’和‘has_ponycopter’)。

開發者一旦創建了包含所有訊息的Protocol Buffer文件,便可使用協定緩衝區編譯器(一個二進制文件)來編譯該協定緩衝區文件,從而生成所有與訊息互動所需的實用類別和方法。例如,如所示,生成的程式碼(取決於所選語言)將呈現此種樣貌。

如何定義服務?

我們需定義利用上述訊息進行傳送/接收的服務。

在撰寫完必要的請求與回應訊息類型後,下一步是編寫服務本身。

gRPC服務同樣在Protocol Buffers中定義,並使用”service”和”RPC”關鍵字來定義服務。

請查看以下proto文件的內容:

message HelloRequest {
string name = 1;
string description = 2;
int32 id = 3;
}

message HelloResponse {
string processedMessage = 1;
}

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

在此,HelloRequest與HelloResponse是訊息,而HelloService則提供了一個名為SayHello的單向RPC,該RPC以HelloRequest作為輸入,並以HelloResponse作為輸出。

如前所述,HelloService 目前僅包含一個單一的 RPC。但它可以包含多個 RPC,並且這些 RPC 可以是多種類型(單一/客戶端串流/伺服器串流/雙向)。

要定義一個串流 RPC,只需在請求/回應參數前加上 ‘stream ’ 前綴,串流 RPC 的 proto 定義及生成代碼.

在上面的代碼庫連結中:

gRPC與REST的對比

我們確實談了不少關於gRPC的內容,也提到了REST。然而,我們未曾深入探討兩者之間的差異。既然已有REST這樣成熟且輕量的通信框架,為何還需尋求另一種通信框架呢?讓我們更深入地了解gRPC相對於REST的各方面,並探討各自的優缺點。

為了進行比較,我們需要一些評估標準。因此,讓我們將比較分解為以下幾個參數:

  • 訊息格式:Protocol Buffers 與 JSON 的比較

    • 在所有數據大小(小/中/大)的情況下,Protocol Buffers 的序列化與反序列化速度遠優於 JSON。基準測試結果

    • 序列化後的 JSON 是可讀的,而 Protocol Buffers(以二進制格式)則否。不確定這是否是一個缺點,因為有時您可能希望在 Google 開發者工具或 Kafka 主題中查看請求詳情,而在使用 Protocol Buffers 的情況下,您無法從中獲取任何信息。

  • 通訊協議:HTTP 1.1 vs. HTTP/2T

    • REST基於HTTP 1.1;REST客戶端與服務器之間的通訊需要建立TCP連接,而此過程涉及三次握手。當客戶端發送請求並從服務器接收回應後,該TCP連接即告終止。處理下一個請求時,必須重新建立TCP連接。每次請求都需建立TCP連接,這增加了延遲。

    • 因此,基於HTTP 2的gRPC通過使用持續連接來應對這一挑戰。我們應記住,HTTP 2中的持續連接與WebSockets中的不同,後者是劫持TCP連接且數據傳輸未經監控。在gRPC連接中,一旦TCP連接建立,它將被重用於多個請求。來自同一客戶端與服務器對的所有請求都會多路復用到同一TCP連接上。

  • 僅需關注數據與邏輯:代碼生成作為首要考量

    • gRPC通過其內置的protoc編譯器原生支持代碼生成功能。對於REST API,則需要使用如Swagger這樣的第三方工具來自動生成各種語言的API調用代碼。

    • 在gRPC的情況下,它抽象了序列化/反序列化、建立連接和發送/接收消息的過程;我們真正需要關心的是我們想要發送或接收的數據以及邏輯。

  • 傳輸速度

    • 由於二進制格式比JSON格式輕得多,因此gRPC情況下的傳輸速度比REST快7到10倍。

特點

REST

gRPC

通信協議

遵循請求-響應模型。它可以與任一HTTP版本工作,但通常與HTTP 1.1一起使用

遵循客戶端-響應模型並基於HTTP 2。某些服務器有解決方案使其能與HTTP 1.1(通過REST網關)工作

瀏覽器支持

在各處均可使用

有限支援。需使用gRPC-Web,此為網頁的擴展,基於HTTP 1.1

載荷數據結構

主要使用JSON及XML基礎的載荷來傳輸數據

默認使用協議緩衝區傳輸載荷

代碼生成

需使用第三方工具如Swagger來生成客戶端代碼

gRPC對多種語言的代碼生成有原生支持

請求快取

易於在客戶端和服務器端快取請求。大多數客戶端/服務器原生支持此功能(例如透過cookies)

默認不支持請求/回應快取

再次,目前gRPC在瀏覽器支持方面尚無,因為大多數UI框架對gRPC的支持仍有限或無。雖然在內部微服務通信時,gRPC是大多數情況下的自動選擇,但在需要UI整合的外部通信中則不然。

經過對gRPC和REST兩種框架的比較後,何時使用哪一個?

  • 在具有多個輕量級微服務的微服務架構中,數據傳輸效率至關重要時,gRPC將是理想的選擇。

  • 若需支援多種語言的代碼生成,gRPC應成為首選框架。

  • 憑藉gRPC的串流功能,像交易或OTT這樣的即時應用將從中受益,而非使用REST進行輪詢。

  • 若頻寬受限,gRPC能提供更低的延遲和更高的吞吐量。

  • 若需求是快速開發及高速迭代,REST應為首選方案。

gRPC概念

負載均衡

儘管持續連接解決了延遲問題,卻引入了另一挑戰,即負載均衡。由於gRPC(或HTTP2)建立持續連接,即便存在負載平衡器,客戶端仍與負載平衡器後的伺服器建立持續連接,類似於黏性會話。

透過示範,我們可以理解這個問題或挑戰。而程式碼及部署檔案位於:https://github.com/infracloudio/grpc-blog/tree/master/grpc-loadbalancing.

從上述示範的程式碼基礎中,我們可以發現,負載均衡的重擔落在客戶端上。這導致了gRPC的一個優勢,即持久連接,在這種變化下不復存在。但gRPC仍可因其其他優點而被使用。

深入了解負載均衡在gRPC中的應用。

在上述示範的程式碼基礎中,僅展示了輪詢負載均衡策略。但gRPC確實支援另一種名為”pick-first”的OOB客戶端負載均衡策略。

此外,自訂客戶端側負載均衡亦受到支援。

清晰的合約

在REST中,客戶端與伺服器之間的合約雖有文檔記錄,但並非嚴格。若回顧SOAP,合約是透過wsdl文件公開的。在REST中,我們透過Swagger及其他方式公開合約。但嚴謹性不足,我們無法確定在客戶端程式開發期間,伺服器端是否已更改合約。

透過gRPC,合約無論是透過proto檔案或由proto檔案產生的生成存根,都與客戶端和服務端共享。這就像是進行遠端函數呼叫。由於我們正在進行函數呼叫,因此我們清楚知道需要傳送什麼以及預期會得到什麼回應。建立與客戶端的連接、處理安全性、序列化-反序列化等的複雜性被抽象化。我們所關心的只是數據。

考慮以下程式碼基礎:

https://github.com/infracloudio/grpc-blog/tree/master/greet_app     

客戶端使用存根(由proto檔案產生的程式碼)來建立客戶端物件並呼叫遠端函數:

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



cc, err := grpc.Dial(“<server-address>”, opts)

if err != nil {

    log.Fatalf("could not connect: %v", err)

}



c := greetpb.NewGreetServiceClient(cc)



res, err := c.Greet(context.Background(), req)

if err != nil {

    log.Fatalf("error while calling greet rpc : %v", err)

}

```

同樣地,服務端也使用相同的存根(由proto檔案產生的程式碼)來接收請求物件並建立回應物件:

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



func (*server) Greet(_ context.Context, req *greetpb.GreetingRequest) (*greetpb.GreetingResponse, error) {

 

  // 對'req'進行某些操作

 

   return &greetpb.GreetingResponse{

    Result: result,

      }, nil

}

```

兩者都使用位於此處的相同存根,該存根由proto檔案生成。

而該存根是使用以下proto編譯器命令生成的。

 

```sh

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/pkg/proto/*.proto

```

安全性

gRPC的認證與授權機制運作於兩個層面:

  • 呼叫級別的認證/授權通常通過在呼叫時應用於元數據中的令牌來處理。基於令牌的認證示例

  • 通道級別的認證使用連接級別應用的客戶端證書。它還可以包括應用於通道上每個呼叫的自動呼叫級別認證/授權憑證。基於證書的認證示例

這兩種機制可以單獨或同時使用來加強服務的安全性。

中介軟體

在REST中,我們使用中介軟體來達成多種目的,例如:

  • 速率限制

  • 請求/回應前後驗證

  • 解決安全威脅

在gRPC中我們也能達成相同效果。在gRPC中,這些功能被稱為攔截器,雖然術語不同,但它們執行類似的任務。

在’greet_app’代碼庫的中介軟體分支中,我們已整合了日誌記錄器和Prometheus攔截器。

查看如何配置攔截器以使用Prometheus和日誌記錄包這裡

但我們還可以整合其他包到攔截器中,用於目的如防止恐慌和恢復(處理異常)、追蹤,甚至是認證等等。

gRPC框架支持的中介軟體.

Proto文件的打包、版本控制及編碼實踐

打包

讓我們跟隨包裝分支的步驟。

首先從‘Taskfile.yaml’開始,任務‘gen-pkg’指示‘protoc –proto_path=packaging packaging/*.proto –go_out=packaging’。這意味著‘protoc’(編譯器)將把‘packaging/*.proto’中的所有文件轉換成相應的‘go’文件,這些文件將存放在‘packaging’目錄下,如標誌‘–go_out=packaging’所示。

其次,在‘processor.proto’文件中,定義了兩個消息類型:‘CPU’和‘GPU’。CPU是一個包含三個內建數據類型字段的簡單消息,而GPU則有一個名為‘Memory’的自定義數據類型。‘Memory’是一個獨立的消息,定義在另一個文件中。

那麼如何在‘processor.proto’文件中使用‘Memory’消息呢?通過使用引入

即使你在提到引入後嘗試通過運行任務‘gen-pkg’生成proto文件,它也會拋出錯誤。因為默認情況下,‘protoc’假設‘memory.proto’和‘processor.proto’在不同的包中。因此,你需要在兩個文件中指定相同的包名。

選項‘go_package’指示編譯器為go文件創建一個名為‘pb’的包名。如果需要創建其他語言的proto文件,包名將是‘laptop_pkg’。

版本管理

  • gRPC中的變更可分為兩類:重大變更和非重大變更。

  • 非重大變更包括新增服務、向服務添加新方法、向請求或回應的proto添加字段,以及向枚舉添加值。

  • 重大變更如重命名字段、更改字段數據類型、字段編號、重命名或移除包、服務或方法,這些都要求對服務進行版本管理。

  • 可選包裝。

代碼實踐

  • 請求消息必須以request結尾,例如`CreateUserRequest`。

  • 回應消息必須以response結尾,例如`CreateUserResponse`。

  • 若回應訊息為空,可選擇使用空物件 `CreateUserResponse` 或使用 `google.protobuf.Empty`

  • 套件名稱必須有意義且需版本化,例如:套件 `com.ic.internal_api.service1.v1`

工具

gRPC 生態系統支援多種工具,以簡化非開發任務,如文件製作、gRPC 伺服器的 REST 閘道、整合自訂驗證器、程式碼檢查等。以下是一些能協助我們達成目標的工具:

  • protoc-gen-grpc-gateway — 用於建立 gRPC REST API 閘道的插件。它允許 gRPC 端點作為 REST API 端點,並執行從 JSON 到 proto 的轉換。基本上,您定義一個帶有自訂註解的 gRPC 服務,它使這些 gRPC 方法能透過 REST 使用 JSON 請求存取。

  • protoc-gen-swagger — 此為grpc-gateway的輔助插件,能根據gRPC gateway所需的特定註解生成swagger.json檔案。您可將此檔案匯入您選擇的REST客戶端(例如 Postman),並對您所公開的方法進行REST API呼叫。

  • protoc-gen-grpc-web — 此插件允許前端透過gRPC呼叫與後端通訊。關於此主題的獨立部落格文章將於未來發布。

  • protoc-gen-go-validators — 此插件允許為proto消息字段定義驗證規則。它為proto消息生成一個Validate() error方法,您可以在GoLang中調用該方法來驗證消息是否符合您預先定義的期望。

  • https://github.com/yoheimuta/protolint — 此插件用於向proto文件添加lint規則

使用POSTMAN進行測試

與使用Postman或類似工具如Insomnia測試REST API不同,測試gRPC服務並不十分舒適。

注意:gRPC服務亦可透過CLI使用evans-cli等工具進行測試。但前提是gRPC伺服器需啟用反射功能(若未啟用,則需提供proto檔案路徑)。變更設定以啟用反射及如何進入evans-cli的REPL模式。進入evans-cli的REPL模式後,即可直接從CLI測試gRPC服務,詳細流程可參閱evans-cli的GitHub頁面。

Postman目前提供測試gRPC服務的測試版

以下是操作步驟:

  1. 開啟Postman

  2. 前往左側邊欄的‘APIs’

    

  1. 點擊‘+’號建立新API:

    

  1. 在彈出視窗中,輸入‘名稱’、‘版本’及‘架構詳情’,然後點擊建立[除非您需要從GitHub/Bitbucket等來源匯入]。此步驟適用於您希望複製貼上proto合約的情況。

5. 你的 API 將如以下所示建立。點擊版本 ‘1.0.0’,前往定義並輸入你的 proto 合約。

  1. 請記住,此處不支援導入功能,因此最好將所有相依的 protos 存放在同一位置。

  2. 上述步驟將有助於保留合約以供未來使用。

  3. 接著點擊 ‘新建’ 並選擇 ‘gRPC 請求’:

  1. 輸入 URI 並從已保存的 API 列表中選擇 proto:

    

  1. 輸入你的請求訊息並點擊 ‘呼叫’:

在上述步驟中,我們已經了解了如何透過POSTMAN測試gRPC API的過程。使用POSTMAN測試gRPC端點的流程與測試REST端點有所不同。值得注意的是,在建立並保存proto合約時(如#5所示),所有proto消息和服務定義必須位於同一位置。因為POSTMAN不支持跨版本訪問proto消息。

總結

本文中,我們對RPC有了初步了解,並將其與REST進行了比較及討論兩者間的差異,接著深入探討了由Google開發的RPC實現——gRPC。

gRPC作為一個框架,在微服務架構的內部通信中尤為關鍵。它亦可用於外部通信,但需配合REST網關。對於流式傳輸和即時應用而言,gRPC是不可或缺的。

隨著Golang在服務器端腳本語言領域的崛起,gRPC也正成為通信框架的實際標準。

Source:
https://dzone.com/articles/understanding-grpc-concepts-use-cases-amp-best-pra