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

【剖析 | SOFARPC 框架】系列之SOFARPC跨語言支持剖析

SOFA

Scalable Open Financial Architecture

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


本文為《剖析 | SOFARPC 框架》第十二篇,作者鷗波

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

專案代號:,官方目錄目前已經全部認領完畢,文末提供了已完成的文章目錄。


  前言

隨著 TIOBE 10月份的編程語言排行的發佈,C++ 重回第三的位置,新興的 Swift 和 Go 表現出強勁的上升趨勢。雖然目前 Java 的領頭位置尚未出現有力挑戰,我們希望能夠在基礎設施的建設上預留跨語言的可擴展設計。同時,跨語言的挑戰也是工程實際面臨的現狀,螞蟻內部如 AI、IoT,演算法等缺少 JVM 原生支持的領域,往往不可避免地需要涉及到跨語言呼叫的問題。


本文將為大家介紹 基於 SOFARPC 的微服務應用在面臨跨語言呼叫時的方案和實現。

  總體設計

經過前面幾篇對 SOFARPC 的 BOLT 協議和序列化這些的介紹,相信大家已經對 RPC 有了一些理解,提到跨語言,我們會首先想到其他語言呼叫 Java,Java 呼叫其他語言,那麼這裡的跨,體現在代碼上,到底跨在哪裡?

從跨語言的實現上來說,主要解決兩個方面的問題:

  • 跨語言的通訊協議和序列化協議

  • 跨語言服務發現


另外從跨語言的落地來說,還得解決一個平滑兼容的問題。

業界常見的做法是一般是通過 DNS 和 HTTP 來解決跨語言的問題,但是在內部已經有完善技術棧體系的情況下,直接切換一個新的方案顯然是不合適的,所以螞蟻內部是在已有的技術體系基礎上進行改進。


螞蟻內部使用的通訊協議是 Bolt,序列化協議是 Hessian。我們知道,服務端和客戶端在請求和傳回之間攜帶的結構化的業務資料,需要在傳輸到達對端後,被本地的語言能夠易於解析消費。由於語言本身特性的差異,同一物件的在序列化和反序列化的轉換後,結構可能有差異,但是需要保證其轉換操作是可逆的。以上這點Hessian做的不是很好,其跨語言的兼容性不能滿足跨語言的需求,所以另外一個可行的方案就是就是選擇其它基於 IDL 的序列化協議,例如 Protobuf。


現成的服務註冊中心一般都有一些多語言解決方案,像 Zookeeper、SOFARegistry、Consul、etcd 等都有多語言客戶端,所以服務發現這塊問題不算太大。


例如下麵就是一個基於註冊中心 + Bolt協議 + Protobuf 序列化的設計圖


  通訊協議和序列化協議

通訊協議只要跨語言各方約定清楚,大家安裝約定實現即可,而序列化協議則需要較多的考量。

序列化的協議選擇列出一些考慮要點:

  • 是否採用具備自我描述能力的序列化方案,如不需要借助一些 schema 或者接口描述檔案。

  • 是否為語言無關的,包括腳本語言在內。

  • 是否壓縮比例足夠小,滿足網絡傳輸場景的要求。

  • 是否序列化和反序列化的性能均足夠優秀。

  • 是否向前/向後兼容,能夠處理傳輸物件的新增屬性在服務端和客戶端版本不一致的情況。

  • 是否支持加密、簽名、壓縮以及擴展的背景關係。

1、JSON Over HTTP

首先,說到跨語言,序列化支持,肯定有同學會問,為什麼不直接通過 Http的Json來搞定呢?

雖然得益於JSON和HTTP在各個語言的廣泛支持,在多語言場景下改造支持非常便捷,能夠低成本的解決網絡通訊和序列化的問題。服務發現的過程則可以使用最簡單的固定URL(協議+域名+端口+路徑)的形式,負載均衡依賴於F5或者LVS等實現。


但是這個方案的有明顯的局限性:

  • HTTP 作為無狀態的應用層協議,在性能上相比基於傳輸層協議(TCP)的方案處於劣勢。HTTP/1.1後可以通過設置keep-alive使用長連接,可以一定程度上規避建立連接的時間損耗;然而最大的問題是,客戶端執行緒採用了 request-response 的樣式,在發送了 request 之後被阻塞,直到拿到 response 之後才能繼續發送。這一問題直到 HTTP/2.0 才被解決。

  • JSON 是基於明文的序列化,較二進制的序列化方案,其序列化的結果可讀性強,但是壓縮率和性能仍有差距,這種對於互聯網高併發業務場景下,意味著硬體成本的提升。

  • 對於網絡變化的響應。訂閱端處理不夠強大。

2、Hessian Over BOLT

在否決了上一個方案後,我們繼續看,螞蟻內部,最開始的時候,SOFARPC 還沒有支持 Protobuf 作為序列化方式,當時為了跨語言,NodeJs的同學已經在此基礎上,用 js 重寫了一個 hessian 的版本,完成了序列化。也已經在線上平穩運行。但是當我們要擴展給其他語言的時候,重寫 hessian 的成本太高。而且 Java語言提供的接口和引數信息,其他語言也需要自己理解一遍,對應地轉換成自己的語言物件。因此該方案在特定場景下是可行的。但不具備推廣至其他語言的優勢。


Node的實現版本可以參考:https://github.com/alipay/sofa-rpc-node

3、Protobuf Over BOLT

Protobuf 基於 IDL,本身具備平臺無關、跨語言的特性,是一個理想的序列化方案。但是需要先編寫proto檔案,結構化地描述傳輸的業務物件,並生成中間代碼。

由於要重點介紹一下這種方案,因此再次回顧一下 SOFABolt 的協議規範部分,便於後面的解釋。



對於現有的通信協議,我們改進時,將 content 部分儲存為入參物件和傳回值,他們都是 pb 序列化之後的值。這樣將直接對接到現在的協議上。又利用了 BOLT 的通信協議。


以下描述了跨語言中對 Protobuf協議的使用:



首先我們看 essay-header 部分,是簡單的扁平化的 KV。預設會增加以下三個 Entry:


Key

Value

備註

sofa_head_method_name

對方方法名

對應 SofaRequest#methodName

sofa_head_target_app

對方的應用名

對應 SofaRequest#targetAppName

sofa_head_target_service

對方的服務唯一命名

對應 SofaRequest#targetServiceUniqueName

sofa_head_response_error

true/false

僅在響應中出現

我們再看 body 部分,根據 Protobuf 的實現,所有被序列化的物件均實現了 MessageLite 接口,然而由於多個 Classloader 存在的可能,代碼上為了避免強轉 MessageList 接口的失敗,並未直接呼叫 toByteArray 方法,而是通過反射機制呼叫 toByteArray 獲得 byte 陣列。


針對 SofaRequest 這個 RPC 中的傳輸物件,由於 Protobuf 僅支持對於單個物件的序列化,因此 SofaRequest 型別的物件進行序列化,實際支持的是 SofaRequest#methodArgs 陣列中的首個元素物件進行的序列化,也就是說目前我們僅支持一個入參物件。


針對 SofaResponse 這個響應物件,當出現框架異常或者傳回物件是一個 Throwable 代表的業務異常時,直接將錯誤訊息字串序列化;併在響應頭中設置 sofa_head_response_error=true,其他情況才序列化業務傳回物件。這樣可以避免比如 Java 語言的錯誤棧,由於含有 一些執行緒類和異常類,其他語言是無法解析的。


反序列化的過程稍複雜一些,上游呼叫傳入 SofaRequest/SofaResponse 的實體,先要在空白的 SofaRequest 物件中填入前文中在 essay-header 反序列化中的解析的頭部信息,接著根據 Header 中接口+方法名找到等待反序列化物件的 class,並借助反射呼叫 parseFrom 接口生成物件,成為 SofaRequest#MethodArgs 的首個元素物件。

4、Others Over BOLT

在上一個方案的基礎上,我們也可以支持更多的語言,對 JSON、Kyro 的支持也分別處於開發和規劃中。 JSON 的支持已經開發完成待合併。這裡不再做過多說明。

  服務發現

跨語言各方約定了通訊協議和序列化協議後,就可以完成各自的服務端和客戶端實現,跨語言已經能完成點對點的呼叫了。但在實際的線上場景下,我們還是需要通過註冊中心等服務發現的形式,來保證跨語言呼叫的可用性。目前,有兩種可選的方案。

1、各語言對接註冊中心

對於服務發現,前面說到的最早進行跨語言的 NodeJs 實現了對接 SOFARegistry 的能力。直接通過對 Java 原生序列化和一些 hessian 的重寫,來操作完成了。在螞蟻內部,這種方案在只有 Node 的情況下是可以的,但是更通用的場景下。如果我們有了新的註冊中心,要對接更多的註冊中心,其他語言在語言表達上的差異性,使得這種方案很難推廣到其他專案。NodeJs 版本的 hessian:https://github.com/alipay/sofa-hessian-node

2、各語言對接SOFAMosn

由於每個語言都去對接對接中心存在一定的難度,也不具備可推廣性,而在螞蟻內部,我們已經在一些跨語言的場景下,運行 SOFAMosn,通過 SOFAMosn,我們對接了站內的註冊中心,其他的語言,僅需要將自己需要訂閱和發佈的信息,通過 Http 的接口形式,通知 SOFAMosn,SOFAMosn 將會將這些信息和註冊中心進行註冊和訂閱,並維持地址信息。


這樣對於其他語言來說,僅需要非常簡單的 json請求,就完成了跨語言的服務註冊和訂閱。後續新註冊中心的對接等等。其他語言都不再需要理解。相關的 SDK 我們已經開發並實現完成。對於 SOFAMosn 的更多介紹,可以參看 SOFAMosn 官網:

http://www.sofastack.tech/sofa-mosn/docs/README


語言

實現

python

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

node

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

c++

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

當然如果你並不需要進行服務尋址,或者能夠接受硬負載或者固定 IP的呼叫方式。也可以直接使用。

  參考資料

  結語

至此,我們介紹了 SOFARPC 中對於 Protobuf 的跨語言實現,並介紹了一些 NodeJs 對跨語言的支持,最後介紹了我們用 SOFAMosn 實現通用的服務發現。


在大多數場景下,我們更推薦是使用 SOFAMosn 來做服務尋址,這樣之後 Mosn 層面的一些限流熔斷,也可以在多語言上進行使用。

而對一些場景比較簡單,能夠容忍固定 IP 呼叫,或者使用硬體負載均衡設備的。也可以直接使用各個跨語言客戶端,進行直接開發呼叫。


相關鏈接

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

SOFA: https://github.com/alipay

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

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

SOFAMosn: https://github.com/alipay/sofa-mosn

  《剖析 | SOFARPC 框架》系列歷史文章



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

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


點擊閱讀原文,加入我們

赞(0)

分享創造快樂