SOFA
Scalable Open Financial Architecture
是螞蟻金服自主研發的金融級分散式中介軟體,包含了構建金融級雲原生架構所需的各個元件,是在金融場景裡錘煉出來的最佳實踐。
本文為《剖析 | SOFARPC 框架》第七篇,作者莫那·魯道 ,來自 E簽寶。
《剖析 | SOFARPC 框架》系列由 SOFA 團隊和原始碼愛好者們出品,
專案代號:
,官方目錄目前已經全部認領完畢。
目前 SOFABolt 原始碼解析正在進行,有興趣的朋友,可在文末申請領取
前言
我們知道,在 RPC 呼叫中,客戶端需要載入服務端提供的介面定義類,但是,這個並不總是可行的,於是,衍生了泛化呼叫的需求,一個成熟的、功能完善的 RPC 框架一般都會支援泛化呼叫,那麼什麼是泛化呼叫呢?SOFARPC 又是如何支援泛化呼叫的?同時又是如何實現的? 和其他的 RPC 泛化呼叫又有何不同?有何優勢?我們將在本文一一解答。
泛化呼叫介紹
當客戶端因為某種原因無法得到服務提供方的介面 jar 包時,或者是客戶端是一個比較通用的系統,並不想依賴每個服務提供方提供的 facade 介面,但是又需要進行呼叫,那麼此時就需要進行泛化呼叫。
例如:
-
當分散式系統由多個語言開發,假設是 NodeJs ,同時 NodeJs 需要呼叫 Java 語言的 RPC 服務,那麼,我們就需要在兩者之間架設適配層,讓適配層處理 NodeJs 的請求後再轉發給 Java 的 RPC 服務。
-
一些中間系統的功能,比如某些內部閘道器,需要以一個統一的方式實現對其他下游系統的呼叫(非 SPI 的情況),逐個依賴下游的包顯然是不可能的。
-
一些流量回放類的線上系統,可以將資料採集攔截,之後,透過泛化呼叫回放,而不需要依賴全站的應用。
這種情況下,肯定不能包含所有介面的 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);
-
$invoke 該方法使用場景:使用者知道引數型別和傳回值型別,那麼就可以使用該方法。
-
$genericInvoke 該方法是個多載方法,多載一的使用場景是:如果你的應用不知道介面的引數型別和傳回值型別,這個時候,你就需要使用 GenericObject 類,來包裝傳回值和引數。
-
$genericInvoke 多載二的使用場景是:如果應用不知道介面引數型別,但是知道介面傳回值的型別,那麼就不需要使用 GenericObject 作為傳回值了。
基本上,已經改寫了常用的集中場景,可以說功能相當全面。
泛化使用
由於篇幅有限,這裡就不貼使用 demo 了,感興趣的可以透過連結檢視官方的 demo 或者原始碼,包含 SOFARPC 的 API 使用方式和 SOFABoot 的使用方式:
-
demo wiki 地址:
http://www.sofastack.tech/sofa-rpc/docs/Generic-Invoke
-
原始碼地址:
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 泛化呼叫的流程圖,有助於後面理解泛化實現。
我們來說一下這個圖:
-
泛化 API 呼叫時,會載入泛化過濾器,作用是做一些引數轉換,同時設定序列化工廠型別。
-
SOFARPC 在使用 SOFABolt 進行網路呼叫前,會建立 context 背景關係並傳遞給 SOFABolt,背景關係中包含著序列化工廠型別資訊,這個資訊將決定使用何種序列化器,同時這個背景關係將流轉於整個呼叫期間。
-
在 SOFABolt 正式傳送資料之前,會將 GenericObject 物件序列化成普通物件的位元組流,這樣,服務提供方就不必關心是否為泛化呼叫,從圖中可見,提供方不用對泛化呼叫做任何改變 —— 這是 SOFARPC 泛化區別於其他 RPC 泛化的關鍵。
-
當提供方成功接收請求後,使用普通序列化器即可反序列化資料,只需要正常呼叫並傳回即可。
-
當消費者的 SOFABolt 接收到響應資料後,便根據 context 的序列化型別,對傳回值做反序列化,即將普通的位元組流反序列化成 GenericObject 物件 —— 因為客戶端有可能不知道傳回值的 Class 型別。
-
最終,泛化 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 的泛化設計與實現做個小結:
-
設計標的是:服務端無需感知是否泛化,一切都是由客戶端進行處理。
帶來的好處是:應用如果想要支援泛化,不需要改動服務端,只需要修改客戶端即可。這是和其他 RPC 框架泛化呼叫最大的區別。
-
實現方式:透過 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 協議框架解析
領取方式:
直接回覆本公眾號想認領的文章名稱,我們將會主動聯絡你,確認資質後,即可加入
相關連結
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