分散式服務框架 gRPC

KevinYan發表於2019-11-17

什麼是gRPC

gRPC是Google開發的高效能、通用的開源RPC框架,其由Google主要面向移動應用開發並基於HTTP/2協議標準而設計,基於Protobuf(Protocol Buffers)序列化協議開發,且支援眾多開發語言。在gRPC中一個客戶端可以像使用本地物件那樣直接呼叫位於不同機器上的服務端應用的方法(methods)。這讓你能夠更容易的構建分散式的應用和服務。和其他RPC系統類似,gRPC也是基於定義一個服務,指定服務可以被遠端呼叫的方法以及他們的引數和返回型別。在服務端,實現服務的介面然後執行一個gRPC服務來處理可出端的請求。在客戶端,客戶端擁有一個存根(stub在某些語言中僅稱為客戶端),提供與伺服器相同的方法。

圖片

·gRPC客戶端和伺服器可以在各種環境中執行並相互通訊,並且可以使用gRPC支援的任何語言編寫。因此,例如,您可以使用Go,Python或Ruby的客戶端輕鬆地用Java建立gRPC伺服器。此外,最新的Google API的介面將擁有gRPC版本,可讓您輕鬆地在應用程式中內建Google功能。

使用protocol buffer

預設情況下,gRPC使用protocol buffer,用於序列化結構化資料(儘管它可以與其他資料格式(例如JSON)一起使用)。使用協議緩衝區的第一步是在proto檔案中為要序列化的資料定義結構:proto副檔名為.proto的普通文字檔案。protocol buffer資料被構造為訊息,其中每個訊息都是資訊的邏輯記錄,其中包含一系列稱為欄位的名稱/值對。這是一個簡單的示例:

message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}

定義了資料結構後,就可以使用protocol buffer編譯器protoc生成你所選語言的資料訪問類。訪問類為每個欄位提供了簡單的訪問器(例如name())和set_name()),以及將整個結構序列化為原始位元組或從原始位元組中解析出整個結構的方法-例如,如果您選擇的語言是C ++,則在上面的示例將生成一個名為Person的類。然後,您可以在應用程式中使用此類來填充,序列化和檢索Person的protocol buffer訊息。

除此之外你還要在.proto件中定義gRPC服務,並將RPC方法引數和返回型別指定為protocol buffer訊息:

// The greeter 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 {
  string message = 1;
}

gRPC使用也是使用編譯器protoc從proto檔案生成程式碼,不過編譯器要首先安裝一個gRPC外掛。使用gRPC外掛,你可以獲得生成的gRPC客戶端和伺服器程式碼,以及用於填充,序列化和檢索訊息型別的常規protocol buffer訪問類程式碼。

下面會更詳細地介紹gRPC裡的一些關鍵的概念。

服務定義

與許多RPC系統一樣,gRPC圍繞定義服務的思想,指定可通過其引數和返回型別遠端呼叫的方法。預設情況下,gRPC使用protocol buffer作為介面定義語言(IDL)來描述服務介面和有效負載訊息的結構。如果需要,可以使用其他替代方法。

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,客戶端向伺服器傳送請求,並獲取流以讀取回一系列訊息。客戶端從返回的流中讀取,直到沒有更多訊息為止。 gRPC保證單個RPC呼叫中的訊息順序。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
  • 客戶端流式RPC,客戶端使用提供的流寫入訊息序列然後將它們傳送到伺服器。客戶端寫完訊息後,它將等待伺服器讀取訊息並返回響應。 gRPC保證了在單個RPC呼叫中的訊息順序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
  • 雙向流式RPC,雙方都使用讀寫流傳送一系列訊息。這兩個流是獨立執行的,因此客戶端和伺服器可以按照自己喜歡的順序進行讀寫:例如,伺服器可以在寫響應之前等待接收完所有客戶端訊息,或者可以先讀取一條訊息再寫入一條訊息,或其他一些讀寫組合。每個流中的訊息順序都會保留。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

在下面的RPC生命週期章節我們會更詳細的比較這幾種不同的RPC。

使用API介面

.proto檔案中的服務定義開始,gRPC提供了protocol buffer編譯器外掛,外掛可生成客戶端和伺服器端程式碼。 gRPC使用者通常在客戶端呼叫這些API,並在伺服器端實現相應的API。

  • 在服務側,伺服器實現服務中宣告的方法並執行一個gRPC伺服器來處理客戶端的呼叫。gRPC的基礎設施解碼傳入的請求,執行服務的方法,編碼服務的響應。
  • 在客戶端,客戶端擁有一個名為stub(存根)的本地物件(在有些語言中更傾向於把stub叫做客戶端)該物件同樣實現了服務中方法。客戶端可以只在本地物件上呼叫這些方法,將呼叫引數包裝在適當的protocol buffer訊息型別中,gRPC會負責將請求傳送給伺服器並且返回服務端的protocol buffer響應。

同步vs非同步

同步RPC呼叫會阻塞當前執行緒直到伺服器收到響應為止,這是最接近RPC所追求的過程呼叫抽象的近似方法。另一方面,網路本質上是非同步的,並且在許多情況下能夠啟動RPC而不阻塞當前執行緒很有用。

大多數語言中的gRPC程式設計介面都有同步和非同步兩種形式。可以在每種語言的教程和參考文件中找到更多資訊。

RPC生命週期

現在讓我們具體看一下當一個gRPC客戶端呼叫了一個gRPC伺服器的方法後都發生了什麼。我們不會檢視具體實現細節,留到後面的程式語言教程中再看實現細節。

一元RPC

首先來看一個最簡單的RPC型別,客戶端傳送一個請求然後接受一個響應。

  • 一旦客戶端呼叫了存根/客戶端物件上的方法,伺服器會被通知RPC已經被呼叫了,同樣會接收到呼叫時客戶端的後設資料、呼叫的方法名稱以及制定的截止時間(如果適用的話)。
  • 然後,伺服器可以立即傳送自己的初始後設資料(必須在傳送任何響應之前傳送),也可以等待客戶端的請求訊息-哪個先發生應用程式指定的。
  • 伺服器收到客戶的請求訊息後,它將完成建立和填充其響應所需的必要工作。然後將響應(如果成功)連同狀態詳細資訊(狀態程式碼和可選狀態訊息)以及可選尾隨後設資料一起返回。
  • 如果狀態是OK,客戶端將獲得響應,從而在客戶端完成並終結整個呼叫過程。

伺服器流式RPC

一個伺服器流式RPC與簡單的一元RPC類似,不同的是伺服器在接收到客戶端的請求訊息後會發回一個響應流。在傳送回所有的響應後,伺服器的狀態詳情(狀態碼和可選的狀態資訊)和可選的尾隨後設資料會被髮回以完成服務端的工作。客戶端在接收到所有的伺服器響應後即完成操作。

客戶端流式RPC

客戶端流式RPC也類似於一元PRC,不同之處在於客戶端向伺服器傳送請求流而不是單個請求。伺服器通常在收到客戶端的所有請求後(但不一定)傳送單個響應,以及其狀態詳細資訊和可選的尾隨後設資料。

雙向流式RPC

在雙向流式RPC中,呼叫再次由客戶端呼叫方法發起,伺服器接收客戶端後設資料,方法名稱和期限。同樣,伺服器可以選擇發回其初始後設資料,或等待客戶端開始傳送請求。

接下來發生的情況取決於應用程式,因為客戶端和伺服器可以按任何順序進行讀取和寫入-流操作完全是獨立地執行。因此,例如,伺服器可以等到收到所有客戶端的訊息後再寫響應,或者伺服器和客戶端可以玩“乒乓”:伺服器收到請求,然後發回響應,然後客戶端傳送基於響應的另一個請求,依此類推。

截止時間/超時時間

gRPC允許客戶端指定在RPC被DEADLINE_EXCEEDED錯誤終結前願意等待多長時間來讓RPC完成工作。在伺服器端,伺服器可以檢視一個特定的RPC是否超時或者還有多長時間剩餘來完成RPC。

如何指定期限或超時的方式因語言而異-例如,並非所有語言都有預設期限,某些語言API按照期限(固定的時間點)工作,而某些語言API根據超時來工作(持續時間)。

RPC終止

在gRPC中,客戶端和服務端對呼叫是否成功做出獨立的基於本地的決定,而且兩端的結論有可能不匹配。這意味著,比如說,你可能會有一個在服務端成功完成(“我已經傳送完所有響應了”)但是在客戶端失敗(“響應是在我指定的deadline之後到達的”)的RPC。伺服器也有可能在客戶端傳送所有請求之前決定RPC完成了。

取消RPC

客戶端或伺服器都可以隨時取消RPC。取消操作將立即終止RPC,因此不再進行任何工作。這不是“撤消”:取消之前所做的更改不會回滾。

後設資料

後設資料是以鍵值對列表形式提供的關於特定RPC呼叫的資訊(比如說身份驗證詳情),其中鍵是字串,值通常來說是字串(但是也可以是二進位制資料)。後設資料對gRPC本身是不透明的-它允許客戶端向伺服器提供與呼叫相關的資訊,反之亦然。

對後設資料的訪問取決於語言。

通道

一個gRPC通道提供了一個到指定主機和埠號的gRPC伺服器的連線,它在建立客戶端存根(或者對某些語言來說就是“客戶端”)時被使用。客戶端可以指定通道引數來更改gRPC的預設行為,比如說開啟/關閉訊息壓縮。每個通道都有狀態,狀態包括connectedidle(閒置)

gRPC怎麼處理關掉的通道是語言相關的,有些語言還允許查詢通道的狀態。


WX20191117-152623@2x.png

課程推薦:簡明高效的Go語言入門和實戰指南

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章