1. 程式人生 > 實用技巧 >gRPC —— 通訊協議

gRPC —— 通訊協議

http://doc.oschina.net/grpc?t=58011

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幀序列例子

請求


HEADERS (flags = END_HEADERS)

:method = POST

:scheme = http

:path = /google.pubsub.v2.PublisherService/CreateTopic

:authority = pubsub.googleapis.com

grpc-timeout = 1S

content-type = application/grpc+proto

grpc-encoding = gzip

authorization = Bearer y235.wef315yfh138vh31hv93hv8h3v




DATA (flags = END_STREAM)

<Delimited Message>

應答


HEADERS (flags = END_HEADERS)

:status = 200

grpc-encoding = gzip




DATA

<Delimited Message>




HEADERS (flags = END_STREAM, END_HEADERS)

grpc-status = 0 # OK

trace-proto-bin = jher831yy13JHy3hc

使用者代理

當協議不需要一個使用者代理時,建議客戶端提供一個結構化的使用者代理字串來對要呼叫的庫、版本和平臺提供一個基本的描述來幫助在異質的環境裡進行問題診斷。庫開發者建議使用以下結構:


User-Agent → “grpc-” Language ?(“-” Variant) “/” Version ?( “ (“  *(AdditionalProperty “;”) “)” )

例如


grpc-java/1.2.3

grpc-ruby/1.2.3

grpc-ruby-jruby/1.3.4

grpc-java-android/0.9.1 (gingerbread/1.2.4; nexus5; tmobile)

HTTP2 傳輸對映

流識別

所有的 GRPC 呼叫需要定義指定一個內部 ID。我們將在這個模式裡使用 HTTP2 流 ID 來作為呼叫標識。注意:這些 ID 在一個開啟的 HTTP2 會話裡是前後關聯的,在一個處理多個 HTTP2 會話的程序裡不是唯一的,也不能被用作 GUID。

資料幀

資料幀邊界與界定訊息的邊界無關,實現時不應假定它們有一致性。

錯誤

當應用錯誤或執行時錯誤在 PRC 呼叫過程中出現時,狀態狀態訊息應當通過跟蹤訊息傳送。

在有些情況下可能訊息流的幀已經中斷,RPC 執行時會選擇使用 RST_STREAM 幀來給對方表示這種狀態。RPC 執行時宣告應當將 RST_STREAM 解釋為流的完全關閉,並且將錯誤傳播到應用層。 以下為從 RST_STREAM 錯誤碼到 GRPC 的錯誤碼的對映:

HTTP2 編碼GRPC 編碼
NO_ERROR(0) INTERNAL -一個顯式的GRPC OK狀態應當被髮出,但是這個也許在某些場景裡會被侵略性地使用
PROTOCOL_ERROR(1) INTERNAL
INTERNAL_ERROR(2) INTERNAL
FLOW_CONTROL_ERROR(3) INTERNAL
SETTINGS_TIMEOUT(4) INTERNAL
STREAM_CLOSED 無對映,因為沒有開啟的流來傳播。實現應記錄。
FRAME_SIZE_ERROR INTERNAL
REFUSED_STREAM UNAVAILABLE-表示請求未作處理且可以重試,可能在他處重試。
CANCEL(8) 當是由客戶端發出時對映為呼叫取消,當是由服務端發出時對映為 CANCELLED。注意服務端在需要取消呼叫時應僅僅使用這個機制,但是有效荷載位元組順序是不完整的
COMPRESSION_ERROR INTERNAL
CONNECT_ERROR INTERNAL
ENHANCE_YOUR_CALM RESOURCE_EXHAUSTED...並且執行時提供有額外的錯誤詳情,表示耗盡資源是頻寬
INADEQUATE_SECURITY PERMISSION_DENIED... 並且有額外的資訊表明許可被拒絕,因為對呼叫來說協議不夠安全

安全

HTTP2 規範當使用 TLS 時強制使用 TLS 1.2 及以上的版本,並且在部署上對允許的密碼施加一些額外的限制以避免已知的比如需要 SNI 支援的問題。並且期待 HTTP2 與專有的傳輸安全機制相結合,這些傳輸機制的規格說明不能提供有意義的建議。

連線管理

GOAWAY 幀

服務端發出這種幀給客戶端表示服務端在相關的連線上不再接受任何新流。這種幀包含服務端最後成功接受的流的ID。客戶端應該認為任何在最後成功的流後面初始化的任意流為 UNAVAILABLE,並且在別處重試這些呼叫。客戶端可以自由地在已經接受的流上繼續工作直到它們完成或者連線中斷。 服務端應該在終止連線前傳送 GOAWAY 幀,以可靠地通知客戶端哪些工作已經被服務端接受並執行。

PING 幀

客戶端和服務端均可以傳送一個 PING 幀,對方必須精確回顯它們所接收到的資訊。這可以被用來確認連線仍然是活動的,並且能夠提供估計端對端延遲估計的方法。假如服務端初始的 PING 在最後期限仍然沒有收到執行時所期待的應答的話,所有未完成的呼叫將會被以取消狀態關閉。一個客戶端期滿的初始的PING則會導致所有的呼叫被以用不可用狀態關閉。注意PING的頻率高度依賴於網路環境,實現可以根據網路和應用需要,自由地調整PING頻率。

連線失敗

假如客戶端檢測到連線失敗,所有的呼叫都會被以不可用狀態關閉。而服務端側則所有已經開啟的呼叫都會被以取消狀態關閉。

附錄 A - Protobuf 上的 GRPC

用 protobuf 定義的服務介面可以通過 protoc 的程式碼生成擴充套件簡單地對映成 GRPC ,以下定義了所用的對映:

  • 路徑 → / 服務名 / {方法名}

  • 服務名 → ?( {proto 包名} "." ) {服務名}

  • 訊息型別 → {全路徑 proto 訊息名}

  • 內容型別 → "application/grpc+proto"