天天看點

【剖析 | SOFARPC 架構】系列之 SOFARPC 泛化調用實作剖析前言泛化調用介紹SOFA RPC 的泛化調用使用SOFARPC 泛化調用的設計與實作SOFARPC 與 Dubbo 的泛化調用比較總結參考

SOFA

Scalable Open Financial Architecture

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

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

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

項目代号:<SOFA:RPCLab/>,官方目錄目前已經全部認領完畢。

前言

我們知道,在 RPC 調用中,用戶端需要加載服務端提供的接口定義類,但是,很多情況下,這個并不總是可行的,于是,衍生了泛化調用的需求,一個成熟的,功能完善的 RPC 架構一般都會支援泛化調用,那麼什麼是泛化調用呢?SOFA RPC 又是如何支援泛化調用的?同時又是如何實作的? 和其他的 RPC 泛化調用又有何不同?有何優勢?我們将在本文一一解答這些問題。

泛化調用介紹

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

例如:

  1. 當分布式系統由多個語言開發,假設是 Node Js ,同時 Node Js 需要調用 Java 語言的 RPC 服務,那麼,我們就需要在兩者之間架設适配層,讓适配層處理 Node Js 的請求後再轉發給 Java 的 RPC 服務。
  2. 一些中間系統的功能,比如某些内部網關,需要以一個統一的方式實作對其他下遊系統的調用(非 SPI的情況),逐個依賴下遊的包顯然是不可能的。
  3. 一些流量回放類的線上系統,可以将資料采集攔截,之後,通過泛化調用回放,而不需要依賴全站的應用。

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

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

SOFA RPC 的泛化調用使用

SOFA RPC 的官方文檔十分詳細,在官方 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. &dollar;invoke 該方法使用場景:使用者知道參數類型和傳回值類型,那麼就可以使用該方法。
  2. &dollar;genericInvoke 該方法是個重載方法,重載一的使用場景是:如果你的應用不知道接口的參數類型和傳回值類型,這個時候,你就需要使用 GenericObject 類,來包裝傳回值和參數。
  3. &dollar;genericInvoke 重載二的使用場景是:如果應用不知道接口參數類型,但是知道接口傳回值的類型,那麼就不需要使用 GenericObject 作為傳回值了。

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

泛化使用

由于篇幅有限,這裡就不貼使用 demo 了,感興趣的可以通過連結檢視官方的 demo 或者源碼,包含 SOFARPC 的 API 使用方式和 SOFABoot 的使用方式:

  1. demo wiki 位址: 使用者手冊->基本特性->泛化調用
  2. 源碼位址: 示例源碼

SOFARPC 泛化調用的設計與實作

接下來我們重點來介紹 SOFARPC 是如何實作泛化調用的。

架構調用設計

簡單來說,泛化調用的關鍵就是對象表示和序列化,SOFARPC 提供了 GenericObject 等對象來表示參數對象或者傳回值對象,而将 GenericObject 對象序列化成目标對象,或者将傳回值反序列化成 GenericObject 對象,是 SOFARPC 實作泛化的關鍵。

這裡我們先來看一下 SOFARPC 泛化調用的流程圖,有助于後面了解泛化實作。

【剖析 | SOFARPC 架構】系列之 SOFARPC 泛化調用實作剖析前言泛化調用介紹SOFA RPC 的泛化調用使用SOFARPC 泛化調用的設計與實作SOFARPC 與 Dubbo 的泛化調用比較總結參考

我們來說一下這個圖:

  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 等接口。在這裡,設計了一下幾個類,來描述

中對應的類型資訊,同時實作這幾個類的序列化和反序列化。對應關系如下

【剖析 | SOFARPC 架構】系列之 SOFARPC 泛化調用實作剖析前言泛化調用介紹SOFA RPC 的泛化調用使用SOFARPC 泛化調用的設計與實作SOFARPC 與 Dubbo 的泛化調用比較總結參考

我們以 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 的泛化調用流程如下圖:

【剖析 | SOFARPC 架構】系列之 SOFARPC 泛化調用實作剖析前言泛化調用介紹SOFA RPC 的泛化調用使用SOFARPC 泛化調用的設計與實作SOFARPC 與 Dubbo 的泛化調用比較總結參考

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

總結

本文主要講解了 SOFARPC 泛化調用的設計與實作,介紹了泛化調用的場景,同時,提及了 SOFA RPC 泛化調用的 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
【剖析 | SOFARPC 架構】系列之 SOFARPC 泛化調用實作剖析前言泛化調用介紹SOFA RPC 的泛化調用使用SOFARPC 泛化調用的設計與實作SOFARPC 與 Dubbo 的泛化調用比較總結參考

長按關注,擷取分布式架構幹貨

歡迎大家共同打造 SOFAStack

https://github.com/alipay