java sofa rpc_【剖析 | SOFARPC 框架】
Scalable?Open?Financial?Architecture
是螞蟻金服自主研發(fā)的金融級(jí)分布式中間件,包含了構(gòu)建金融級(jí)云原生架構(gòu)所需的各個(gè)組件,是在金融場(chǎng)景里錘煉出來的最佳實(shí)踐。
本文為《剖析 | SOFARPC 框架》第十二篇,作者鷗波。
《剖析 | SOFARPC 框架》系列由 SOFA 團(tuán)隊(duì)和源碼愛好者們出品,
項(xiàng)目代號(hào):,官方目錄目前已經(jīng)全部認(rèn)領(lǐng)完畢,文末提供了已完成的文章目錄。
前言
隨著 TIOBE 10月份的編程語言排行的發(fā)布,C++ 重回第三的位置,新興的 Swift 和 Go 表現(xiàn)出強(qiáng)勁的上升趨勢(shì)。雖然目前 Java 的領(lǐng)頭位置尚未出現(xiàn)有力挑戰(zhàn),我們希望能夠在基礎(chǔ)設(shè)施的建設(shè)上預(yù)留跨語言的可擴(kuò)展設(shè)計(jì)。同時(shí),跨語言的挑戰(zhàn)也是工程實(shí)際面臨的現(xiàn)狀,螞蟻內(nèi)部如 AI、IoT,算法等缺少 JVM 原生支持的領(lǐng)域,往往不可避免地需要涉及到跨語言調(diào)用的問題。
本文將為大家介紹 基于 SOFARPC 的微服務(wù)應(yīng)用在面臨跨語言調(diào)用時(shí)的方案和實(shí)現(xiàn)。
總體設(shè)計(jì)
經(jīng)過前面幾篇對(duì) SOFARPC 的 BOLT 協(xié)議和序列化這些的介紹,相信大家已經(jīng)對(duì) RPC 有了一些理解,提到跨語言,我們會(huì)首先想到其他語言調(diào)用 Java,Java 調(diào)用其他語言,那么這里的跨,體現(xiàn)在代碼上,到底跨在哪里?
從跨語言的實(shí)現(xiàn)上來說,主要解決兩個(gè)方面的問題:跨語言的通訊協(xié)議和序列化協(xié)議
跨語言服務(wù)發(fā)現(xiàn)
另外從跨語言的落地來說,還得解決一個(gè)平滑兼容的問題。
業(yè)界常見的做法是一般是通過 DNS 和 HTTP 來解決跨語言的問題,但是在內(nèi)部已經(jīng)有完善技術(shù)棧體系的情況下,直接切換一個(gè)新的方案顯然是不合適的,所以螞蟻內(nèi)部是在已有的技術(shù)體系基礎(chǔ)上進(jìn)行改進(jìn)。
螞蟻內(nèi)部使用的通訊協(xié)議是 Bolt,序列化協(xié)議是 Hessian。我們知道,服務(wù)端和客戶端在請(qǐng)求和返回之間攜帶的結(jié)構(gòu)化的業(yè)務(wù)數(shù)據(jù),需要在傳輸?shù)竭_(dá)對(duì)端后,被本地的語言能夠易于解析消費(fèi)。由于語言本身特性的差異,同一對(duì)象的在序列化和反序列化的轉(zhuǎn)換后,結(jié)構(gòu)可能有差異,但是需要保證其轉(zhuǎn)換操作是可逆的。以上這點(diǎn)Hessian做的不是很好,其跨語言的兼容性不能滿足跨語言的需求,所以另外一個(gè)可行的方案就是就是選擇其它基于 IDL 的序列化協(xié)議,例如 Protobuf。
現(xiàn)成的服務(wù)注冊(cè)中心一般都有一些多語言解決方案,像 Zookeeper、SOFARegistry、Consul、etcd 等都有多語言客戶端,所以服務(wù)發(fā)現(xiàn)這塊問題不算太大。
例如下面就是一個(gè)基于注冊(cè)中心 + Bolt協(xié)議 + Protobuf 序列化的設(shè)計(jì)圖
通訊協(xié)議和序列化協(xié)議
通訊協(xié)議只要跨語言各方約定清楚,大家安裝約定實(shí)現(xiàn)即可,而序列化協(xié)議則需要較多的考量。
序列化的協(xié)議選擇列出一些考慮要點(diǎn):是否采用具備自我描述能力的序列化方案,如不需要借助一些 schema 或者接口描述文件。
是否為語言無關(guān)的,包括腳本語言在內(nèi)。
是否壓縮比例足夠小,滿足網(wǎng)絡(luò)傳輸場(chǎng)景的要求。
是否序列化和反序列化的性能均足夠優(yōu)秀。
是否向前/向后兼容,能夠處理傳輸對(duì)象的新增屬性在服務(wù)端和客戶端版本不一致的情況。
是否支持加密、簽名、壓縮以及擴(kuò)展的上下文。
1、JSON Over HTTP
首先,說到跨語言,序列化支持,肯定有同學(xué)會(huì)問,為什么不直接通過 Http的Json來搞定呢?
雖然得益于JSON和HTTP在各個(gè)語言的廣泛支持,在多語言場(chǎng)景下改造支持非常便捷,能夠低成本的解決網(wǎng)絡(luò)通訊和序列化的問題。服務(wù)發(fā)現(xiàn)的過程則可以使用最簡(jiǎn)單的固定URL(協(xié)議+域名+端口+路徑)的形式,負(fù)載均衡依賴于F5或者LVS等實(shí)現(xiàn)。
但是這個(gè)方案的有明顯的局限性:HTTP 作為無狀態(tài)的應(yīng)用層協(xié)議,在性能上相比基于傳輸層協(xié)議(TCP)的方案處于劣勢(shì)。HTTP/1.1后可以通過設(shè)置keep-alive使用長(zhǎng)連接,可以一定程度上規(guī)避建立連接的時(shí)間損耗;然而最大的問題是,客戶端線程采用了 request-response 的模式,在發(fā)送了 request 之后被阻塞,直到拿到 response 之后才能繼續(xù)發(fā)送。這一問題直到 HTTP/2.0 才被解決。
JSON 是基于明文的序列化,較二進(jìn)制的序列化方案,其序列化的結(jié)果可讀性強(qiáng),但是壓縮率和性能仍有差距,這種對(duì)于互聯(lián)網(wǎng)高并發(fā)業(yè)務(wù)場(chǎng)景下,意味著硬件成本的提升。
對(duì)于網(wǎng)絡(luò)變化的響應(yīng)。訂閱端處理不夠強(qiáng)大。
2、Hessian Over BOLT
在否決了上一個(gè)方案后,我們繼續(xù)看,螞蟻內(nèi)部,最開始的時(shí)候,SOFARPC 還沒有支持 Protobuf 作為序列化方式,當(dāng)時(shí)為了跨語言,NodeJs的同學(xué)已經(jīng)在此基礎(chǔ)上,用 js 重寫了一個(gè) hessian 的版本,完成了序列化。也已經(jīng)在線上平穩(wěn)運(yùn)行。但是當(dāng)我們要擴(kuò)展給其他語言的時(shí)候,重寫 hessian 的成本太高。而且 Java語言提供的接口和參數(shù)信息,其他語言也需要自己理解一遍,對(duì)應(yīng)地轉(zhuǎn)換成自己的語言對(duì)象。因此該方案在特定場(chǎng)景下是可行的。但不具備推廣至其他語言的優(yōu)勢(shì)。
Node的實(shí)現(xiàn)版本可以參考:https://github.com/alipay/sofa-rpc-node
3、Protobuf Over BOLT
Protobuf 基于 IDL,本身具備平臺(tái)無關(guān)、跨語言的特性,是一個(gè)理想的序列化方案。但是需要先編寫proto文件,結(jié)構(gòu)化地描述傳輸?shù)臉I(yè)務(wù)對(duì)象,并生成中間代碼。
由于要重點(diǎn)介紹一下這種方案,因此再次回顧一下 SOFABolt 的協(xié)議規(guī)范部分,便于后面的解釋。
對(duì)于現(xiàn)有的通信協(xié)議,我們改進(jìn)時(shí),將 content 部分存儲(chǔ)為入?yún)?duì)象和返回值,他們都是 pb 序列化之后的值。這樣將直接對(duì)接到現(xiàn)在的協(xié)議上。又利用了 BOLT 的通信協(xié)議。
以下描述了跨語言中對(duì) Protobuf協(xié)議的使用:
首先我們看 header 部分,是簡(jiǎn)單的扁平化的 KV。默認(rèn)會(huì)增加以下三個(gè) Entry:
KeyValue備注
sofa_head_method_name對(duì)方方法名對(duì)應(yīng) SofaRequest#methodName
sofa_head_target_app對(duì)方的應(yīng)用名對(duì)應(yīng) SofaRequest#targetAppName
sofa_head_target_service對(duì)方的服務(wù)唯一命名對(duì)應(yīng) SofaRequest#targetServiceUniqueName
sofa_head_response_errortrue/false僅在響應(yīng)中出現(xiàn)
我們?cè)倏?body 部分,根據(jù) Protobuf 的實(shí)現(xiàn),所有被序列化的對(duì)象均實(shí)現(xiàn)了 MessageLite 接口,然而由于多個(gè) Classloader 存在的可能,代碼上為了避免強(qiáng)轉(zhuǎn) MessageList 接口的失敗,并未直接調(diào)用 toByteArray 方法,而是通過反射機(jī)制調(diào)用 toByteArray 獲得 byte 數(shù)組。
針對(duì) SofaRequest 這個(gè) RPC 中的傳輸對(duì)象,由于 Protobuf 僅支持對(duì)于單個(gè)對(duì)象的序列化,因此 SofaRequest 類型的對(duì)象進(jìn)行序列化,實(shí)際支持的是 SofaRequest#methodArgs 數(shù)組中的首個(gè)元素對(duì)象進(jìn)行的序列化,也就是說目前我們僅支持一個(gè)入?yún)?duì)象。
針對(duì) SofaResponse 這個(gè)響應(yīng)對(duì)象,當(dāng)出現(xiàn)框架異常或者返回對(duì)象是一個(gè) Throwable 代表的業(yè)務(wù)異常時(shí),直接將錯(cuò)誤消息字符串序列化;并在響應(yīng)頭中設(shè)置 sofa_head_response_error=true,其他情況才序列化業(yè)務(wù)返回對(duì)象。這樣可以避免比如 Java 語言的錯(cuò)誤棧,由于含有 一些線程類和異常類,其他語言是無法解析的。
反序列化的過程稍復(fù)雜一些,上游調(diào)用傳入 SofaRequest/SofaResponse 的實(shí)例,先要在空白的 SofaRequest 對(duì)象中填入前文中在 header 反序列化中的解析的頭部信息,接著根據(jù) Header 中接口+方法名找到等待反序列化對(duì)象的 class,并借助反射調(diào)用 parseFrom?接口生成對(duì)象,成為 SofaRequest#MethodArgs 的首個(gè)元素對(duì)象。
4、Others Over BOLT
在上一個(gè)方案的基礎(chǔ)上,我們也可以支持更多的語言,對(duì) JSON、Kyro 的支持也分別處于開發(fā)和規(guī)劃中。 JSON 的支持已經(jīng)開發(fā)完成待合并。這里不再做過多說明。
服務(wù)發(fā)現(xiàn)
跨語言各方約定了通訊協(xié)議和序列化協(xié)議后,就可以完成各自的服務(wù)端和客戶端實(shí)現(xiàn),跨語言已經(jīng)能完成點(diǎn)對(duì)點(diǎn)的調(diào)用了。但在實(shí)際的線上場(chǎng)景下,我們還是需要通過注冊(cè)中心等服務(wù)發(fā)現(xiàn)的形式,來保證跨語言調(diào)用的可用性。目前,有兩種可選的方案。
1、各語言對(duì)接注冊(cè)中心
對(duì)于服務(wù)發(fā)現(xiàn),前面說到的最早進(jìn)行跨語言的 NodeJs 實(shí)現(xiàn)了對(duì)接 SOFARegistry 的能力。直接通過對(duì) Java 原生序列化和一些 hessian 的重寫,來操作完成了。在螞蟻內(nèi)部,這種方案在只有 Node 的情況下是可以的,但是更通用的場(chǎng)景下。如果我們有了新的注冊(cè)中心,要對(duì)接更多的注冊(cè)中心,其他語言在語言表達(dá)上的差異性,使得這種方案很難推廣到其他項(xiàng)目。NodeJs 版本的 hessian:https://github.com/alipay/sofa-hessian-node
2、各語言對(duì)接SOFAMosn
由于每個(gè)語言都去對(duì)接對(duì)接中心存在一定的難度,也不具備可推廣性,而在螞蟻內(nèi)部,我們已經(jīng)在一些跨語言的場(chǎng)景下,運(yùn)行 SOFAMosn,通過 SOFAMosn,我們對(duì)接了站內(nèi)的注冊(cè)中心,其他的語言,僅需要將自己需要訂閱和發(fā)布的信息,通過 Http 的接口形式,通知 SOFAMosn,SOFAMosn 將會(huì)將這些信息和注冊(cè)中心進(jìn)行注冊(cè)和訂閱,并維持地址信息。
這樣對(duì)于其他語言來說,僅需要非常簡(jiǎn)單的 json請(qǐng)求,就完成了跨語言的服務(wù)注冊(cè)和訂閱。后續(xù)新注冊(cè)中心的對(duì)接等等。其他語言都不再需要理解。相關(guān)的 SDK 我們已經(jīng)開發(fā)并實(shí)現(xiàn)完成。對(duì)于 SOFAMosn 的更多介紹,可以參看 SOFAMosn 官網(wǎng):
http://www.sofastack.tech/sofa-mosn/docs/README
語言實(shí)現(xiàn)
pythonhttps://github.com/alipay/sofa-bolt-python
nodehttps://github.com/alipay/sofa-rpc-node
c++https://github.com/alipay/sofa-bolt-cpp
當(dāng)然如果你并不需要進(jìn)行服務(wù)尋址,或者能夠接受硬負(fù)載或者固定 IP的調(diào)用方式。也可以直接使用。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的java sofa rpc_【剖析 | SOFARPC 框架】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 16年尼桑蓝鸟启动以后一直有电机声音
- 下一篇: 丰田雷凌尾门铰链怎么拆?