官方文檔
概念文檔 https://grpc.io/docs/guides/
java helloword demo https://grpc.io/docs/quickstart/
引導文檔 https://grpc.io/docs/guides/
以上是建議學習順序
以下是概念文檔的翻譯,以後會出具體實現代碼
簡介
gRPC 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多複用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間佔用。
gRPC 概念
本文檔通過對於 gRPC 的架構和 RPC 生命週期的概覽來介紹 gRPC 的主要概念。本文是在假設你已經讀過文檔部分的前提下展開的。針對具體語言細節請查看對應語言的快速開始、教程和參考文檔(很快就會有完整的文檔)。
概覽
服務定義
正如其他 RPC 系統,gRPC 基於如下思想:定義一個服務, 指定其可以被遠程調用的方法及其參數和返回類型。gRPC 默認使用 protocol buffers 作爲接口定義語言,來描述服務接口和有效載荷消息結構。如果有需要的話,可以使用其他替代方案。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
required string greeting = 1;
}
message HelloResponse {
required string reply = 1;
}
gRPC 允許你定義四類服務方法:
- 單項 RPC,即客戶端發送一個請求給服務端,從服務端獲取一個應答,就像一次普通的函數調用。
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服務端流式 RPC,即客戶端發送一個請求給服務端,可獲取一個數據流用來讀取一系列消息。客戶端從返回的數據流裏一直讀取直到沒有更多消息爲止。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客戶端流式 RPC,即客戶端用提供的一個數據流寫入併發送一系列消息給服務端。一旦客戶端完成消息寫入,就等待服務端讀取這些消息並返回應答。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 雙向流式 RPC,即兩邊都可以分別通過一個讀寫數據流來發送一系列消息。這兩個數據流操作是相互獨立的,所以客戶端和服務端能按其希望的任意順序讀寫,例如:服務端可以在寫應答前等待所有的客戶端消息,或者它可以先讀一個消息再寫一個消息,或者是讀寫相結合的其他方式。每個數據流裏消息的順序會被保持。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
我們將在下面 RPC 生命週期章節裏看到各類 RPC 的技術細節。
使用 API 接口
gRPC 提供 protocol buffer 編譯插件,能夠從一個服務定義的 .proto 文件生成客戶端和服務端代碼。通常 gRPC 用戶可以在服務端實現這些API,並從客戶端調用它們。
- 在服務側,服務端實現服務接口,運行一個 gRPC 服務器來處理客戶端調用。gRPC 底層架構會解碼傳入的請求,執行服務方法,編碼服務應答。
- 在客戶側,客戶端有一個存根實現了服務端同樣的方法。客戶端可以在本地存根調用這些方法,用合適的 protocol buffer 消息類型封裝這些參數— gRPC 來負責發送請求給服務端並返回服務端 protocol buffer 響應。
同步 vs 異步
同步 RPC 調用一直會阻塞直到從服務端獲得一個應答,這與 RPC 希望的抽象最爲接近。另一方面網絡內部是異步的,並且在許多場景下能夠在不阻塞當前線程的情況下啓動 RPC 是非常有用的。
在多數語言裏,gRPC 編程接口同時支持同步和異步的特點。你可以從每個語言教程和參考文檔裏找到更多內容(很快就會有完整文檔)。
RPC 生命週期
現在讓我們來仔細瞭解一下當 gRPC 客戶端調用 gRPC 服務端的方法時到底發生了什麼。我們不究其實現細節,關於實現細節的部分,你可以在我們的特定語言頁面裏找到更爲詳盡的內容。
單項 RPC
首先我們來了解一下最簡單的 RPC 形式:客戶端發出單個請求,獲得單個響應。
- 一旦客戶端通過樁調用一個方法,服務端會得到相關通知 ,通知包括客戶端的元數據,方法名,允許的響應期限(如果可以的話)
- 服務端既可以在任何響應之前直接發送回初始的元數據,也可以等待客戶端的請求信息,到底哪個先發生,取決於具體的應用。
- 一旦服務端獲得客戶端的請求信息,就會做所需的任何工作來創建或組裝對應的響應。如果成功的話,這個響應會和包含狀態碼以及可選的狀態信息等狀態明細及可選的追蹤信息返回給客戶端 。
- 假如狀態是 OK 的話,客戶端會得到應答,這將結束客戶端的調用。
服務端流式 RPC
服務端流式 RPC 除了在得到客戶端請求信息後發送回一個應答流之外,與我們的簡單例子一樣。在發送完所有應答後,服務端的狀態詳情(狀態碼和可選的狀態信息)和可選的跟蹤元數據被髮送回客戶端,以此來完成服務端的工作。客戶端在接收到所有服務端的應答後也完成了工作。
客戶端流式 RPC
客戶端流式 RPC 也基本與我們的簡單例子一樣,區別在於客戶端通過發送一個請求流給服務端,取代了原先發送的單個請求。服務端通常(但並不必須)會在接收到客戶端所有的請求後發送回一個應答,其中附帶有它的狀態詳情和可選的跟蹤數據。
雙向流式 RPC
雙向流式 RPC ,調用由客戶端調用方法來初始化,而服務端則接收到客戶端的元數據,方法名和截止時間。服務端可以選擇發送回它的初始元數據或等待客戶端發送請求。 下一步怎樣發展取決於應用,因爲客戶端和服務端能在任意順序上讀寫 - 這些流的操作是完全獨立的。例如服務端可以一直等直到它接收到所有客戶端的消息才寫應答,或者服務端和客戶端可以像"乒乓球"一樣:服務端後得到一個請求就回送一個應答,接着客戶端根據應答來發送另一個請求,以此類推。
截止時間
gRPC 允許客戶端在調用一個遠程方法前指定一個最後期限值。這個值指定了在客戶端可以等待服務端多長時間來應答,超過這個時間值 RPC 將結束並返回DEADLINE_EXCEEDED
錯誤。在服務端可以查詢這個期限值來看是否一個特定的方法已經過期,或者還剩多長時間來完成這個方法。 各語言來指定一個截止時間的方式是不同的 - 比如在 Python 裏一個截止時間值總是必須的,但並不是所有語言都有一個默認的截止時間。
RPC 終止
在 gRPC 裏,客戶端和服務端對調用成功的判斷是獨立的、本地的,他們的結論可能不一致。這意味着,比如你有一個 RPC 在服務端成功結束("我已經返回了所有應答!"),到那時在客戶端可能是失敗的("應答在最後期限後纔來到!")。也可能在客戶端把所有請求發送完前,服務端卻判斷調用已經完成了。
取消 RPC
無論客戶端還是服務端均可以再任何時間取消一個 RPC 。一個取消會立即終止 RPC 這樣可以避免更多操作被執行。它不是一個"撤銷", 在取消前已經完成的不會被回滾。當然,通過同步調用的 RPC 不能被取消,因爲直到 RPC 結束前,程序控制權還沒有交還給應用。
元數據集
元數據是一個特殊 RPC 調用對應的信息(授權詳情]) ,這些信息以鍵值對的形式存在,一般鍵的類型是字符串,值的類型一般也是字符串(當然也可以是二進制數據)。元數據對 gRPC 本事來說是不透明的 - 它讓客戶端提供調用相關的信息給服務端,反之亦然。 對於元數據的訪問是語言相關的。
通訊協議
HTTP2 協議上的 gRPC
本文檔作爲 gRPC 在 HTTP2 草案17框架上的實現的詳細描述,假設你已經熟悉 HTTP2 的規範。產品規則採用的是ABNF 語法
大綱
以下是 gRPC 請求和應答消息流中一般的消息順序:
- 請求 → 請求報頭 *有定界符的消息 EOS
- 應答 → 應答報頭 *有定界符的消息 EOS
- 應答 → (應答報頭 *有定界符的消息 跟蹤信息) / 僅僅跟蹤時
請求
-
請求 → 請求報頭 *界定的消息 EOS 請求報頭是通過報頭+聯繫幀方式以 HTTP2 報頭來發送的。
-
請求報頭 → 調用定義 *自定義元數據
-
調用定義 → 方法模式路徑TE [授權] [超時] [內容類型] [消息類型] [消息編碼] [接受消息類型] [用戶代理]
-
方法 → “:method POST”
-
模式 → “:scheme ” (“http” / “https”)
-
路徑 → “:path” {開放的 API 對應的方法路徑}
-
Authority → “:authority” {授權的對應的虛擬主機域名}
-
TE → “te” “trailers” # 用來檢測不兼容的代理
-
超時 → “grpc-timeout” 超時時間值 超時時間單位
-
超時時間值 → {至少8位數字正整數的 ASCII 碼字符串}
-
超時時間單位 → 時 / 分 / 秒 / 毫秒 / 微秒 / 納秒
-
時 → “H”
-
分 → “M”
-
秒 → “S”
-
毫秒 → “m”
-
微秒 → “u”
-
納秒 → “n”
-
內容類型 → “content-type” “application/grpc” [(“+proto” / “+json” / {自定義})]
-
內容編碼 → “gzip” / “deflate” / “snappy” / {自定義}
-
消息編碼 → “grpc-encoding” Content-Coding
-
接受消息編碼 → “grpc-accept-encoding” Content-Coding *("," Content-Coding)
-
用戶代理 → “user-agent” {結構化的用戶代理字符串}
-
消息類型 → “grpc-message-type” {消息模式的類型名}
-
自定義數據 → 二進制報頭 / ASCII 碼報頭
-
二進制報頭 → {以“-bin”結尾小寫的報頭名稱的 ASCII 碼 } {以 base64 進行編碼的值}
-
ASCII 碼報頭 → {小寫報頭名稱的 ASCII 碼} {值}
HTTP2 需要一個在其他報頭之前以“:”開始的保留報頭。額外的實現應該在保留報頭後面馬上傳送超時信息,並且應該在發送自定義元數據前發送調用定義報頭。 如果超時信息被遺漏,服務端會認爲是無限時長的超時。客戶端實現可以根據發佈需要自由地發送一個默認最小超時時間。 自定義元數據是應用層定義的任意的鍵值對集合。除了 HTTP2 報頭部總長度的傳輸限制外,唯一的約束就是以“grpc-”開始的報頭名稱是爲將來使用保留的。
注意 HTTP2 並不允許隨意使用字節序列來作爲報頭值,所以二進制的報頭值必須使用 Base64 來編碼,參見https://tools.ietf.org/html/rfc4648#section-4。 實現必須接受填充的和非填充的值,並且發出非填充的值。應用以“-bin”結尾的名稱來定義二進制報頭。運行時庫在報頭被髮送和接收時,用這個後綴來檢測二進制報頭並且正確地在報頭被髮送和接收時進行 Base64 編碼和解碼。
界定的消息的重複序列通過數據幀來進行傳輸。
-
界定的消息 → 壓縮標誌 消息長度 消息
-
壓縮標誌 → 0 / 1 # 編碼爲 1 byte 的無符號整數
-
消息長度 → {消息長度} # 編碼爲 4 byte 的無符號整數
-
消息 → *{二進制字節}
壓縮標誌 值爲1 表示消息的二進制序列通過消息編碼報頭聲明的機制進行壓縮,爲0表示消息的字節碼沒有進行編碼。壓縮上下文不在消息編輯間維護,聲明必須爲流中的每個消息創建一個新的上下文。假如 壓縮標誌 被遺漏了,那麼壓縮標誌 必須爲0。
對請求來講,EOS (end-of-stream)以最後接收到的數據幀出現 END_STREAM 標誌爲準。 在請求流需要關閉但是沒有數據繼續發送的情況下,代碼必鬚髮送包含這個標誌的空數據幀。
應答
-
應答 → (應答報頭 界定的消息 跟蹤信息) / 僅僅跟蹤
-
應答報頭 → HTTP 狀態 [消息編碼] [消息接受編碼] 內容類型 *自定義元數據
-
僅僅跟蹤 → HTTP 狀態 內容類型 跟蹤消息
-
跟蹤消息 → 狀態 [狀態消息] *自定義元數據
-
HTTP狀態 → “:status 200”
-
狀態 → “grpc-status” <狀態碼的 ASCII 字符串>
-
狀態消息 → “grpc-message” <狀態描述文本對應的 ASCII 字符串>
應答報頭 和 僅僅跟蹤 分別在一個HTTP2報頭幀塊裏發送。大多數應答期望既有報頭又有跟蹤消息,但是調用允許僅僅跟蹤生成一個立即的錯誤。假如狀態碼是 OK 的話,則必須在跟蹤消息裏發送狀態。 對於應答來講,通過在最後一個接收的包含跟蹤信息的報頭幀裏提供一個 END_STREAM 標誌來表明流結束。
實現應當會讓中斷的部署在應答裏發送一個非200的HTTP狀態碼和一系列非GRPC內容類型並且省略狀態和狀態消息。 當發生這種情況時實現應當合成狀態和狀態消息來擴散到應用層。
安全
HTTP2 規範當使用 TLS 時強制使用 TLS 1.2 及以上的版本,並且在部署上對允許的密碼施加一些額外的限制以避免已知的比如需要 SNI 支持的問題。並且期待 HTTP2 與專有的傳輸安全機制相結合,這些傳輸機制的規格說明不能提供有意義的建議。
連接管理
GOAWAY 幀
服務端發出這種幀給客戶端表示服務端在相關的連接上不再接受任何新流。這種幀包含服務端最後成功接受的流的ID。客戶端應該認爲任何在最後成功的流後面初始化的任意流爲 UNAVAILABLE,並且在別處重試這些調用。客戶端可以自由地在已經接受的流上繼續工作直到它們完成或者連接中斷。 服務端應該在終止連接前發送 GOAWAY 幀,以可靠地通知客戶端哪些工作已經被服務端接受並執行。
PING 幀
客戶端和服務端均可以發送一個 PING 幀,對方必須精確回顯它們所接收到的信息。這可以被用來確認連接仍然是活動的,並且能夠提供估計端對端延遲估計的方法。假如服務端初始的 PING 在最後期限仍然沒有收到運行時所期待的應答的話,所有未完成的調用將會被以取消狀態關閉。一個客戶端期滿的初始的PING則會導致所有的調用被以用不可用狀態關閉。注意PING的頻率高度依賴於網絡環境,實現可以根據網絡和應用需要,自由地調整PING頻率。
連接失敗
假如客戶端檢測到連接失敗,所有的調用都會被以不可用狀態關閉。而服務端側則所有已經打開的調用都會被以取消狀態關閉。
Protobuf 上的 GRPC
用 protobuf 定義的服務接口可以通過 protoc 的代碼生成擴展簡單地映射成 GRPC ,以下定義了所用的映射:
-
路徑 → / 服務名 / {方法名}
-
服務名 → ?( {proto 包名} "." ) {服務名}
-
消息類型 → {全路徑 proto 消息名}
-
內容類型 → "application/grpc+proto"