歡迎光臨
每天分享高質量文章

【剖析 | SOFARPC 框架】系列之 SOFARPC 泛化呼叫實現剖析

SOFA

Scalable Open Financial Architecture

是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景里錘煉出來的最佳實踐。


本文為《剖析 | SOFARPC 框架》第七篇,作者莫那·魯道 ,來自 E簽寶

《剖析 | SOFARPC 框架》系列由 SOFA 團隊和原始碼愛好者們出品,

專案代號:,官方目錄目前已經全部認領完畢。


目前 SOFABolt 原始碼解析正在進行,有興趣的朋友,可在文末申請領取

  前言

我們知道,在 RPC 呼叫中,客戶端需要加載服務端提供的接口定義類,但是,這個並不總是可行的,於是,衍生了泛化呼叫的需求,一個成熟的、功能完善的 RPC 框架一般都會支持泛化呼叫,那麼什麼是泛化呼叫呢?SOFARPC 又是如何支持泛化呼叫的?同時又是如何實現的? 和其他的 RPC 泛化呼叫又有何不同?有何優勢?我們將在本文一一解答。

  泛化呼叫介紹

當客戶端因為某種原因無法得到服務提供方的接口 jar 包時,或者是客戶端是一個比較通用的系統,並不想依賴每個服務提供方提供的 facade 接口,但是又需要進行呼叫,那麼此時就需要進行泛化呼叫。


例如:

  1. 當分佈式系統由多個語言開發,假設是 NodeJs ,同時 NodeJs 需要呼叫 Java 語言的 RPC 服務,那麼,我們就需要在兩者之間架設適配層,讓適配層處理 NodeJs 的請求後再轉發給 Java 的 RPC 服務。

  2. 一些中間系統的功能,比如某些內部網關,需要以一個統一的方式實現對其他下游系統的呼叫(非 SPI 的情況),逐個依賴下游的包顯然是不可能的。

  3. 一些流量回放類的線上系統,可以將資料採集攔截,之後,通過泛化呼叫回放,而不需要依賴全站的應用。


這種情況下,肯定不能包含所有接口的 jar 檔案,否則就太臃腫了。實際上也是不現實的,總不能每增加一個服務端,就增加一個 jar 包依賴,然後應用進行發佈重啟。

這個時候就可以使用泛化呼叫,將相應的請求包裝成泛化呼叫,就能夠實現不依賴接口 jar 包,多語言呼叫 RPC 服務,避免重覆開發。

  SOFARPC 的泛化呼叫使用

SOFARPC 的官方文件十分詳細,在官方 wiki 泛化呼叫 中,已有詳細介紹。同時,在原始碼中的 example 模塊中,也有現成的 demo 可以跑起來,讀者可以自己 clone 原始碼閱讀,這裡我們簡要說明一下使用方式,以便大家有一個直觀的瞭解。

接口定義

總的來說,泛化呼叫有 2 個 API,包含 5 個方法,其中, 2 個方法已經廢棄,也就是說,有 3 個主要方法。分別是:

/** * 泛化呼叫 * @return 正常型別(不能是GenericObject型別) */

Object $invoke(String methodName, String[] argTypes, Object[] args);/** * 支持引數型別無法在類加載器加載情況的泛化呼叫 * @return 除了JDK等內置型別,其它物件是GenericObject型別 */

Object $genericInvoke(String methodName, String[] argTypes, Object[] args);/** * 支持引數型別無法在類加載器加載情況的泛化呼叫 * @return 傳回指定的T型別傳回物件 */

<T> T $genericInvoke(String methodName, String[] argTypes, Object[] args, Class<T> clazz);

  1. $invoke 該方法使用場景:用戶知道引數型別和傳回值型別,那麼就可以使用該方法。

  2. $genericInvoke 該方法是個多載方法,多載一的使用場景是:如果你的應用不知道接口的引數型別和傳回值型別,這個時候,你就需要使用 GenericObject 類,來包裝傳回值和引數。

  3. $genericInvoke 多載二的使用場景是:如果應用不知道接口引數型別,但是知道接口傳回值的型別,那麼就不需要使用 GenericObject 作為傳回值了。

基本上,已經改寫了常用的集中場景,可以說功能相當全面。

泛化使用

由於篇幅有限,這裡就不貼使用 demo 了,感興趣的可以通過鏈接查看官方的 demo 或者原始碼,包含 SOFARPC 的 API 使用方式和 SOFABoot 的使用方式:

  1. demo wiki 地址:

    http://www.sofastack.tech/sofa-rpc/docs/Generic-Invoke

  2. 原始碼地址:

    https://github.com/alipay/sofa-rpc/tree/master/example/src/test/java/com/alipay/sofa/rpc/invoke/generic

  SOFARPC 泛化呼叫的設計與實現

接下來我們重點來介紹 SOFARPC 是如何實現泛化呼叫的。

框架呼叫設計

簡單來說,泛化呼叫的關鍵就是物件表示和序列化,SOFARPC 提供了 GenericObject 等物件來表示引數物件或者傳回值物件,而將 GenericObject 物件序列化成標的物件,或者將傳回值反序列化成 GenericObject 物件,是 SOFARPC 實現泛化的關鍵。


這裡我們先來看一下 SOFARPC 泛化呼叫的流程圖,有助於後面理解泛化實現。

我們來說一下這個圖:

  1. 泛化 API 呼叫時,會加載泛化過濾器,作用是做一些引數轉換,同時設置序列化工廠型別。

  2. SOFARPC 在使用 SOFABolt 進行網絡呼叫前,會創建 context 背景關係並傳遞給  SOFABolt,背景關係中包含著序列化工廠型別信息,這個信息將決定使用何種序列化器,同時這個背景關係將流轉於整個呼叫期間。

  3. 在 SOFABolt 正式發送資料之前,會將 GenericObject 物件序列化成普通物件的位元組流,這樣,服務提供方就不必關心是否為泛化呼叫,從圖中可見,提供方不用對泛化呼叫做任何改變 —— 這是 SOFARPC 泛化區別於其他 RPC 泛化的關鍵

  4. 當提供方成功接收請求後,使用普通序列化器即可反序列化資料,只需要正常呼叫並傳回即可。

  5. 當消費者的 SOFABolt 接收到響應資料後,便根據 context 的序列化型別,對傳回值做反序列化,即將普通的位元組流反序列化成 GenericObject 物件 —— 因為客戶端有可能不知道傳回值的  Class 型別。

  6. 最終,泛化 API 即可得到 GenericObject 型別的傳回值。


從上面的流程可以看出,序列化器在泛化呼叫中,占了極大的篇幅和作用。而 SOFARPC 針對泛化呼叫,對 hessian3 進行了改造,使其支持泛化呼叫所需要的序列化功能。SOFA-Hessian 的改動可以參考這裡。

Hessian 泛化實現

SOFA-Hessian 在 hessian 的包中加入了 com.alipay.hessian.generic 包,此包的作用就是處理泛化呼叫,重寫的關鍵是實現或繼承 SerializerFactory 類和 Serializer、Deserializer 等接口。在這裡,設計了一下幾個類,來描述
中對應的型別信息,同時實現這幾個類的序列化和反序列化。對應關係如下



我們以 GenericObjectSerializer 為例,該序列化器重寫了 writeObject 方法,該方法的作用就是將 GenericObject 物件序列化成標的物件位元組流。即,拿出 GenericObject 的 type 欄位和 fields 欄位,組裝成標的物件的位元組流。


例如:

有一個型別是的 RPC 物件

public class TestObj {    private String str;    private int    num; }

在泛化呼叫客戶端,可以直接構造一個 GenericObject物件

 GenericObject genericObject = new GenericObject(                    "com.alipay.sofa.rpc.invoke.generic.TestObj");                genericObject.putField("str", "xxxx");                genericObject.putField("num", 222);


此時,GenericObjectSerializer 就可以通過這些信息,將 GenericObject 物件轉成 TestObj 物件的位元組流。服務提供方就可以通過普通的 hessian2 反序列化得到物件。

相比較其他 RPC 框架兩端都需要對泛化進行支持,SOFARPC 顯得要友好的多。也就是說,如果應用想要支持泛化,只需要升級客戶端(消費者)即可,服務端(提供者)是無感知的。因為在服務端看來,收到的物件是完全一致的。你可能覺得對於複雜型別,寫出這樣一個構造是很困難的。SOFA-Hessian中已經提供了一個工具類

com.alipay.hessian.generic.util.GenericUtils

來輔助使用者來生成,可以直接使用。

  SOFARPC 與 Dubbo 的泛化呼叫比較

下麵我們來介紹下泛化呼叫和業界一些其他產品的比較,首先介紹一下序列化本身的一些性能和優勢比較。

序列化本身的比較

在 Github 上,有一個專門針對 Java  序列化進行的 benchmark,可以稍微做一下參考。雖然在實際的場景中, 每個序列化的場景不同,帶來的結果可能和這裡的 benchmark  結果不同,但還是有參考意義,從該專案的基準測試可以看出:Json 無論是壓縮比還是序列化時間,相比 hessian 等都有相當大的劣勢

同時,雖然 hessian 相對於 protostuff、kryo 等在性能上有一點差距,但是 hessian 反序列化無需指定型別,這個優勢是非常有價值的。

Dubbo的泛化呼叫

在眾多的 RPC 框架中,Dubbo 也提供了泛化呼叫的功能,接下來我們再來說說 Dubbo 的泛化。Dubbo 泛化和 SOFA RPC 泛化最大的不同就是:Dubbo 需要服務端也支持泛化,因此,如果想提供泛化功能,服務端也必須進行升級,這看起來可能沒有 SOFA RPC 友好。


Dubbo 的泛化呼叫流程如下圖:

可以看到,Dubbo 的服務端也需要泛化過濾器將 Map 解析成 POJO 來解析資料。

  總結

本文主要講解了 SOFARPC 泛化呼叫的設計與實現,介紹了泛化呼叫的場景,同時,提及了 SOFARPC 泛化呼叫的 API 使用,也詳細講解了 SOFARPC 的泛化設計和實現。最後,對社區中的一些 RPC 框架的泛化呼叫做了簡單的比較。


這裡對 SOFARPC 的泛化設計與實現做個小結:

  1. 設計標的是:服務端無需感知是否泛化,一切都是由客戶端進行處理。

    帶來的好處是:應用如果想要支持泛化,不需要改動服務端,只需要修改客戶端即可。這是和其他 RPC 框架泛化呼叫最大的區別。

  2. 實現方式:通過 SOFA-Hessian 序列化支持泛化序列化,在進行泛化呼叫時,bolt 會根據背景關係的序列化標記來使用對應的序列化器,SOFA-Hessian 特有的泛化序列化器可將 GenericObject 物件序列化成標的物件的位元組流,服務端按正常反序列化即可。SOFA-Hessian 特有的泛化反序列化器也可將標的傳回值反序列化成 GenericObject 等物件。

  參考

https://github.com/eishay/jvm-serializers

https://github.com/alipay/sofa-hessian

http://www.sofastack.tech/sofa-rpc/docs/Generic-Invoke

  One more thing

歡迎加入 ,參與 SOFABolt 原始碼解析


SOFABolt 原始碼解析目錄:

我們會逐步詳細介紹每部分的代碼設計和實現,預計會按照如下的目錄進行,以下也包含目前的原始碼分析文章的認領情況:

  • 【已完成】SOFABolt 的設計與私有協議解析

  • 【已認領】SOFABolt 的鏈接管理功能分析

  • 【已認領】SOFABolt 的超時控制機制及心跳機制

  • 【已認領】SOFABolt 編解碼機制(Codec)

  • 【待認領】SOFABolt 序列化機制(Serializer)

  • 【待認領】SOFABolt 協議框架解析


領取方式:

直接回覆本公眾號想認領的文章名稱,我們將會主動聯繫你,確認資質後,即可加入,It’s your show time!


相關鏈接

SOFA 文件: http://www.sofastack.tech/

SOFA: https://github.com/alipay

SOFARPC: https://github.com/alipay/sofa-rpc

SOFABolt: https://github.com/alipay/sofa-bolt

  小彩蛋

來這裡看看有沒有你的 ID 吧:

http://www.sofastack.tech/awesome


長按關註,獲取分佈式架構乾貨

歡迎大家共同打造 SOFAStack https://github.com/alipay


赞(0)

分享創造快樂