1. 程式人生 > >趣談網路協議---二進位制類RPC協議:還是叫NBA吧,總說全稱多費勁

趣談網路協議---二進位制類RPC協議:還是叫NBA吧,總說全稱多費勁

接入層,對於靜態資源或動態資源靜態化的部分可以做快取,但對於下單、支付等交易場景,還是需要呼叫 API。

對於微服務架構,API 需要一個 API 閘道器統一的處理。API 閘道器實現由多種方式,Nginx 或 OpenResty 結合 Lua 指令碼是常用的方式,也可以用 Spring Cloud 中的元件 Zuul。

資料中心內部是如何相互呼叫的?

API 閘道器用來管理 API,而 API 的實現一般在 Controller 層,基本上都是基於 RESTful 的,面向大規模的網際網路應用。 在這裡插入圖片描述 Controller 為網際網路應用的業務邏輯實現,業務邏輯的實現應該是無狀態的,從而可以橫向擴充套件。所以,資源狀態的維護在最底層的持久化層,一般會是分散式資料庫和 ElasticSearch。

因為硬碟讀寫效能差,持久化層往往吞吐量較小,因此前面需要由一層快取層,Redis 或 memcached。

快取和持久化層之上一般是基礎服務層,提供一些原子化的介面,如對使用者、商品、訂單、庫存的增刪改查。有了這層,上層業務邏輯看到的都是介面,而不會呼叫資料庫和快取,有利於對快取和資料庫的運維。

再往上是組合層,實現負責的業務邏輯,如下單、扣優惠券等。

Controller 層、組合服務層、基礎服務層會相互呼叫,這個呼叫在資料中心內部,使用 RPC 機制實現。

由於服務較多,需要一個單獨的註冊中心來做服務發現。服務提供方會自己提供服務註冊到註冊中心去,同時服務消費訂閱該服務,從而對該服務呼叫。

RPC 呼叫時,如果使用文字類,則相對於二進位制,同樣的資訊佔用更多空間,傳輸起來更加佔頻寬,時延也高。因而,多數公司還是更願意使用二進位制方案。

Dubbo 服務框架二進位制的 RPC 方式 在這裡插入圖片描述

  • Dubbo 在客戶端的本地啟動一個 Proxy,即客戶端 Stub。
  • Dubbo 從註冊中心獲取服務端的列表,根據路由規則和負責均衡規則,從多個服務端中選擇一個進行呼叫。
  • 呼叫服務端時,進行編碼和序列化,形成 Dubbo 頭和序列化的方法和引數,交給網路客戶端傳送。
  • 網路服務端收到訊息,解碼,將任務發給某個執行緒處理,執行緒呼叫服務端程式碼邏輯,然後返回結果。

如何解決協議約定問題?

Dubbo 中預設的 RPC 協議時 Hessian 2。Hessian2 將遠端呼叫序列化為二進位制進行傳輸,並且可以進行一定的壓縮。

Hessian2 與前面的二進位制的 RPC 的區別:

1、綜合了 XML 和二進位制共同的優勢。

原先要定義一個協議檔案,然後通過該檔案生成客戶端和服務端 Stub,才能相互呼叫。Hessian2 不需要定義協議檔案,而是自描述的。關於呼叫哪個函式,引數是什麼,另一方不需要拿到某個協議檔案,而是拿到二進位制後,靠 Hessian2 的規則解析。

Hessian2 的序列化額語法描述檔案: 在這裡插入圖片描述 從 Top 起,下一層是 value,直到形成一棵樹。為防止歧義,每個型別的起始數字都設定為獨一無二的。

“add(2, 3)” 被序列化後:

H x02 x00     # H 開頭,表示使用的協議是 Hessian
C          # C 開頭,表示是一個 RPC 呼叫
 x03 add     # 0x03,表示方法名是 3 個字元
 x92        # 0x92,表示有 2 個引數,加上 0x90 是為了防止歧義,表示這一定是個 int
 x92        # 第一個引數是 2
 x93        # 第二個引數是 3

2、Hessian2 是面向物件的,可傳輸一個物件。

class Car {
 String color;
 String model;
}
out.writeObject(new Car("red", "corvette"));
out.writeObject(new Car("green", "civic"));
---
C            # 定義類,定義在位置 0
 x0b example.Car    # 類名為 example.Car,11 個字元
 x92          # 2 個成員變數
 x05 color       # color 成員變數,5 個字元
 x05 model       # model 成員變數,5 個字元

O            # 傳輸的物件引用這個類
 x90          #  因為類定義在位置 0,所以物件會指向這個位置 0
 x03 red        # color 的值為 red,3 個字元
 x08 corvette      # model 的值為 corvette,8 個字元

x60           # 傳輸一個屬於相同類的物件,不儲存對類的引用,只儲存 0x60,表示同上
 x05 green       # color 的值為 green,5 個字元
 x05 civic       # model 的值為 civic,5 個字元

如何解決 RPC 傳輸問題?

Dubbo 中,使用了 Netty 的網路傳輸框架。

Netty 是一個非阻塞的基於事件的網路傳輸框架,在服務端啟動時,會監聽一個埠,並註冊以下事件。

  • 連線事件:收到客戶端的連線事件時,呼叫 void connected(Channel channel) 方法。
  • 可寫事件觸發時,呼叫 void sent(Channel channel, Object message),服務端向客戶端返回響應資料。
  • 可讀事件觸發時,呼叫 void received(Channel channel, Object message),服務端接收客戶端的請求資料。
  • 發生異常時,呼叫 void caught(Channel channel, Throwable exception)。

事件觸發後,服務端可直接在函式中操作,也可將請求分發到執行緒池處理。

上面的架構中,如果使用二進位制的方式進行序列化,雖然不用協議檔案生成 Stub,但對於介面的定義,及傳遞的物件 DTO,還是需要共享 JAR。客戶端 和服務端都有 JAR,才能成功地序列化和反序列化。

關係複雜時,JAR 的依賴也會複雜,在 DTO 中增加一個欄位,雙方 JAR 沒有匹配好,也會導致序列化失敗,還可能迴圈依賴,這時,一般有兩種選擇:

1、建立嚴格的專案管理流程

  • 不允許迴圈呼叫,不訊息跨層呼叫。
  • 介面保持相容性。
  • 升級時,先升級服務提供端,再升級服務消費端。

2、改用 RESTful 的方式。

  • 使用 Sprint Cloud,消費端和提供端不共享 JAR,各宣告各的,只要能變成 JSON 即可。
  • 使用 RESTful 方式,效能會降低,所以需要通過橫向擴充套件來地下單機的效能損耗。