透過例項demo帶你認識gRPC

語言: CN / TW / HK
摘要:gRPC是基於定義一個服務,指定一個可以遠端呼叫的帶有引數和返回型別的的方法。在服務端,服務實現這個介面並且執行gRPC服務處理客戶端呼叫。

本文分享自華為雲社群《gRPC介紹以及spring demo構架展示》,作者:gentle_zhou。

gRPC,即google Remote Procedure Call Protocol;在gRPC裡,客戶端可以直接呼叫不同機器上的服務應用的方法,就像本地物件一樣,所以建立分散式應用和服務就變簡單了。

gRPC是基於定義一個服務,指定一個可以遠端呼叫的帶有引數和返回型別的的方法。在服務端,服務實現這個介面並且執行gRPC服務處理客戶端呼叫。在客戶端,有一個stub提供和服務端相同的方法。

資料編碼

資料編碼即將請求的記憶體物件轉化成可以傳輸的位元組流傳送給服務端,並將收到的位元組流在轉化成記憶體物件。常見的資料編碼方法有JSON,而gRPC則預設選用protobuf。

為什麼選用protobuf呢?一個是因為它是谷歌自己的產品,二是它作為一種序列化資料結構的協定,在某些場景下傳輸的效率比JSON高。

一個.proto檔案裡的訊息格式如下:

而一個典型的JSON格式如下所示:

我們可以看到在JSON裡,記憶體方面,int欄位的12345會佔據5個位元組,bool欄位的true會佔據4個位元組,佔據記憶體就會比較大,編碼低效;還有一個缺點就是在JSON裡,同一個介面同一個物件,只是int欄位的值不同,每次卻都還要傳輸int這個欄位名。這樣做的好處就是JSON的可讀性很高,但同樣在編碼效率方面就會有所犧牲。

而Protobuf則是選用了VarInts對數字進行編碼(VarInts則是動態的,徵用了每個位元組的最高位MSB,如果是1表示還有後序位元組,如果是0表示後面就沒位元組了,以此來確定表示長度所需要的位元組數量,解決了效率問題),同時給每個欄位指定一個整數編號,傳輸的時候只傳欄位編號(解決了效率和冗餘問題)。

但是隻傳欄位編號的話,接收方如何知道各個編號對應哪個欄位呢?那就需要靠提前約定了。Protobuf使用.proto檔案當做密碼本,記錄欄位和編號的對應關係。

Protobuf 提供了一系列工具,為 proto 描述的 message 生成各種語言的程式碼。傳輸效率上去了,工具鏈也更加複雜了。

請求對映

IDL,Interactive Data Language的縮寫,互動式資料語言。

因為我們有.proto檔案作為IDL,Protobuf就可以做到RPC描述。比如在.proto檔案裡定義一個Greeter服務,其中有一個 SayHello 的方法,接受 HelloRequest 訊息並返回 HelloReply 訊息。如何實現這個 Greeter 則是語言無關的,所以叫 IDL。gRPC 就是用了 Protobuf 的 service 來描述 RPC 介面的。

gRPC 在底層使用的是 HTTP/2 協議。這個 HTTP 請求用的是 POST 方法,對應的資源路徑則是根據 .proto 定義確定的。我們前面提到的 Greeter 服務對應的路徑是/demo.hello.Greeter/SayHello 。

一個 gRPC 定義包含三個部分,包名、服務名和介面名,連線規則如下

/${包名}. ${服務名}/ ${介面名}

SayHello的包名是demo.hello,服務名是Greeter,介面名是SayHello,所以對應的路徑就是 /demo.hello.Greeter/SayHello。

gRPC 支援三種流式介面,定義的辦法就是在引數前加上 stream 關鍵字,分別是:請求流、響應流和雙向流。

  • 第一種叫請求流,可以在 RPC 發起之後不斷髮送新的請求訊息。此類介面最典型的使用場景是發推送或者簡訊。
  • 第二種叫響應流,可以在 RPC 發起之後不斷接收新的響應訊息。此類介面最典型的使用場景是訂閱訊息通知。
  • 最後一種是雙向流。可以在 RPC 發起之後同時收發訊息。此類介面最典型的使用場景是實時語音轉字幕。
    如下就是普通介面和三種流式介面的結構樣式:

最簡單的gRPC(非流式呼叫,unary)請求內容和相應內容如下所示:

如果單看非流式呼叫,也就是 unary call,gRPC 並不複雜,跟普通的 HTTP 請求也沒有太大區別。我們甚至可以使用 HTTP/1.1 來承載 gRPC 流量。但是gRPC 支援流式介面,這就有點難辦了。

我們知道,HTTP/1.1 也是支援複用 TCP 連線的。但這種複用有一個明顯的缺陷,所有請求必須排隊。也就是說一定要按照請求、等待、響應、請求、等待、響應這樣的順序進行。先到先服務。而在實際的業務場景中肯定會有一些請求響應時間很長,客戶端在收到響應之前會一直霸佔著TCP連線。在這段時間裡別的請求要麼等待,要麼發起新的 TCP 連線。在效率上確實有優化的餘地。一言以蔽之,HTTP/1.1 不能充分地複用 TCP 連線。

後來,HTTP/2 橫空出世!通過引入 stream 的概念,解決了 TCP 連線複用的問題。你可以把 HTTP/2 的 stream 簡單理解為邏輯上的 TCP 連線,可以在一條 TCP 連線上並行收發 HTTP 訊息,而無需像 HTTP/1.1 那樣等待。所以 gRPC 為了實現流式接品,選擇使用 HTTP/2 進行通訊。所以,前文的 Greeter 呼叫的實際通訊內容長這個樣子。

HTTP/2 的 header 和 data 使用獨立的 frame(幀,簡單來說也是一種 Length-Prefixed 訊息,是 HTTP/2 通訊的基本單位) 傳送,可以多次傳送。

springboot裡的grpc demo

整個專案可以分成三個project:

  1. grpc-springboot-demo-api:proto檔案(syntax=“proto3”; 定義服務,定義請求體,定義迴應內容)寫好之後用maven-install來編譯生成所需的類;
  2. grpc-springboot-demo-server:pom檔案(springboot的啟動依賴,grpc的依賴, api專案的依賴),springboot的啟動類,GRPC伺服器的啟動類,提供服務的業務邏輯實現類
  3. grpc-springboot-demo-consumer:pom檔案(springboot的啟動依賴,grpc的依賴, api專案的依賴),springboot啟動類(與服務端啟動類無差異),gRPC 客戶端(主要作用是監聽 gRPC 服務端,開啟通道)。

對應MVC關係就是:

grpc-springboot-demo-api就是service(介面,為提供實現);
grpc-springboot-demo-server就相當於serviceImpl(service的實現類);
grpc-springboot-demo-consumer就是controller的角色。

具體程式碼可以看:https://blog.csdn.net/Applying/article/details/115024675

拓展

repeated限定修飾符

repeated代表可重複,我們可以理解為陣列。比如下面的程式碼:

syntax = "proto3";//指定版本資訊,不指定會報錯

message Person //message為關鍵字,作用為定義一種訊息型別
{
    string name = 1;    //姓名
    int32 id = 2;       //id
    string email = 3;   //郵件
}

message AddressBook
{
    repeated Person people = 1;
}

編譯器就會把Person認定為陣列,而我們在使用Person,用add往裡面新增資訊,程式碼如下:

AddressBook addBopookReq = AddressBook.newBuilder().addName("Lily").build();

就不需要指定index了,直接往數組裡添加了一個新的addressbook,它的名字屬性則是Lily。

參考資料

  1. https://developers.google.com/protocol-buffers/docs/overview
  2. https://taoshu.in/grpc.html
  3. https://grpc.io/
  4. https://grpc.io/docs/languages/java/quickstart/
  5. https://blog.csdn.net/tennysonsky/article/details/73921025

 

點選關注,第一時間瞭解華為雲新鮮技術~