淺析“遠程對象調用”

EarlHoliday 8年前發布 | 10K 次閱讀 EJB

遠程對象調用的概念

要說“遠程對象”,必先說“遠程調用”,也就是 RPC 。比較著名的 RPC 框架有,最近很火的 gRPC ,也就是 Google 開源的 RPC 。另外還有 非死book 開源的 Thrift 等等……我廠內部也有很多 RPC 框架,琳瑯滿目不暇接。 Java 在 JDK 里面也支持 RMI ( Remote Method Invoke: 遠程方法請求)功能,也可以視為一種 RPC ,但實際上這個更像我們現在要討論的“遠程對象調用”。

在諸多的 RPC 中,我們都基本認為是通過網絡,對運行在另外一個進程(或者電腦)里的某個函數,發起一次調用請求。既然是一次函數調用,那么我們自然要傳入參數,然后期望獲得返回值。在這個過程中,我們往往只需要輸入:函數名 + 參數, RPC 就能找到一個遠程的進程,去執行對應的函數,然后傳入目標參數。在這個過程里,執行這個函數的進程,會被認為是無狀態的,所有的輸出,都僅與輸入的參數有關,除非有一部分狀態是記錄在數據庫(持久化設備)上的。因此,計算的過程(算法),和計算的數據,實際上分離的,這些計算所需的數據,要么來源于參數,要么是數據庫設備。而被請求的函數,以及裝載這個函數的容器——進程,是不保證任何的狀態維護能力的。

而“遠程對象調用”,正是在“狀態”這個環節上,和 RPC 不同——它是由框架去保證某種狀態的。當我們發起一個遠程對象調用的時候,是需要首先“找到”一個遠程對象,然后再發起“方法”(成員函數)調用。這和 RPC 就產生了兩個明顯的區別:

一、 我們需要用某種手段定位到對象,而不是僅僅用一個函數名。對象是一個更復雜的遠程概念,因為有可能同屬于一個類( class ),而存在多個狀態一致或不一致的對象,在遠程的機器上存在。我們就不能僅僅通過一個固定的路由標志(比如類名)去找一個這樣的對象。遠程對象的路由方式成為不同“遠程對象調用”框架之間的一個顯著區別。

二、 我們并不需要把所有的數據,在每次請求時都通過參數發給遠程對象,因為對于同一個遠程對象來說,它是可以包含大量過程狀態的。我們只要找到正確的遠程對象,就能獲得之前操作所造成的結果狀態。有遠程對象往往是生存在進程的內存中,所以對于訪問自己的狀態數據,會非常快速,這對于有延遲壓力的程序來說,是非常有用的。

 

所以,遠程對象調用,最大的特點,就是數據和計算是合并在一起的——這很好的提高了使用面向對象編程的便利性,也大大降低了遠程調用中因為數據拉取產生的延遲。

遠程對象的優點: DB 壓力、易用性

在傳統的“請求 - 響應”為基礎的分布式服務器中,最常見的數據系統是:接入 - 邏輯 - 緩存 - 數據庫 這樣一個四層結構。為了讓承擔計算壓力的“邏輯”模塊能分布到不同的進程上,我們往往會把“邏輯”模塊做成“無狀態”的,這樣我們就可以隨意的啟動、停止任何一個邏輯模塊的進程,而不需要擔心因此丟失用戶數據。但是這樣做,邏輯模塊是輕松了,承擔狀態存儲的“緩存 - 數據庫”哥倆壓力就大了。因為每一個數據操作,都需要去從他們這里讀取數據,然后再回寫結果(如果有數據修改操作的話)。

由于“緩存 - 數據庫”模塊是有狀態的,一般來說還很難簡單的做分布式部署,因為如果隨機分布數據的話,邏輯模塊可能就會找不到狀態所在的緩存進程。從 CAP 理論可以知道,我們要讓狀態能分布,就一定要犧牲一些一致性或可用性。因此我們更傾向以 NoSQL 的存儲系統去充當“緩存 - 數據庫”模塊。但是,即便是 NoSQL ,還是會有兩個缺點:一個是跨進程訪問的延遲;一個是編程上的復雜性。

跨進程訪問的延遲來源于兩方面,一方面是本身跨進程通過 socket 之類的手段通訊,就會有比進程內存訪問高的多的延遲,而且我們常常會把一個業務流程按數據的類型劃分到不同的“邏輯模塊”里,這樣一個業務請求可能會需要多次的跨進程訪問才能訪問完所需的數據,這就大大加重了因為網絡帶來的延遲;另外一方面來源于路由查找,雖然我們可以用一致性哈希這類算法取代路由查找,但是基于數據的業務特性,我們卻不太喜歡把所有數據都拆的七零八落,所以常常還是有一個查詢、或探索數據所在地的過程。

編程的復雜性也是很嚴重的問題。不管是 SQL 還是 NoSQL ,這些數據都是以序列化的方式描述的,并且也按照數據的組織(存放)形式,要求使用者去準備好輸入或者解析讀出這些數據。這些數據和我們在編程中常用的結構體、對象往往完全是不一樣的形式。這就造成了我們很多額外的編碼和調試的工作。這些數據往往還是“結構敏感”的:如果我們修改了數據結構,往往需要重新配置數據表結構,修改訪問代碼等等。這讓我們在快速開發業務邏輯的時候,背上沉重的開發效率包袱。——因此業界才有很多所謂 ORM (對象關系映射)的框架出現。

但是如果我們使用“遠程對象調用”,就可以有效的緩解以上兩個問題:

一、 緩解跨進程延遲。由于遠程對象本身已經包含了數據,所以對于所需的數據,都是從內存中直接讀寫,這方面的延遲是絕對最快的。另外,由于遠程對象調用發起之前,已經需要先查找到目地對象,這樣就把查找方法和查找數據的兩個過程合二為一了,在路由層面也能有效降低延遲。

二、 極好的易用性。由于面向對象編程的概念已經深入人心,所以對于“先找到一個對象”,然后“調用其方法”的過程,是非常自然的。復雜的負載均衡、容災、擴容等問題,實際上都隱藏在“查找對象”這個環節底下,開發者幾乎無需關心,所以用起來會非常方便。而編寫一個遠程對象,也非常簡單,就是寫一個類,實例化一個對象,然后登記到服務器里而已。這都是面向對象編程的傳統做法。由于對象本身都是帶數據的,所以編寫這些遠程方法也會比較簡單,大部分的數據都直接在本地內存讀寫,比如從對象成員屬性里。節省了大量編寫 SQL 或者定義和使用特別的存儲設備協議的時間。

業界遠程對象方案: EJB/MS-WCF/IBM-ORB

遠程對象調用的框架,在業界也是常見的東西,這里大概說一下三家的 : EJB, MS WCF, IBM ORB 。這三家的框架大概的說明現在遠程對象調用的主流用法。

一, EJB

EJB 全稱 Enterprise Java Bean ,是 Java 的企業分布式集群方案的核心( J2EE 規范)。能部署在多個服務器上提供遠程對象調用服務的 JAVA 對象,就稱為 EJB 對象。底層的網絡是通過 JDK 自帶的 RMI 功能實現。 EJB 本身只是 J2EE 規范中的一部分,僅僅是一套接口。具體的實現由類似 Weblogic 這樣的“ EJB 容器”軟件提供。 EJB 之所以不及 SSH ( Spring Structs Hibernate )流行,很大原因就是因為這些容器軟件都是商業軟件,需要花很貴的價格購買。但這并不影響 EJB 作為一個優秀的遠程對象方案的技術地位。 EJB 現在已經升級到 3.0 版本以上了,摒棄了以前配置復雜,功能晦澀的特點,大膽的使用更簡單的生命周期管理、簡單的注解式配置、好用的 ORM 能力,讓 EJB 3.0 重新成為一流的技術。

 

一個客戶端程序,想要訪問一個 EJB 對象,一般需要使用一個叫做 JNDI 的 API ,來具體連接到 EJB 對象上。 JNDI 的全稱是 Java Naming and Directory Inerface ,基本等于我們常說的名字、目錄服務接口。 Java 通過一套 API 規范,來統一各種目錄服務器的使用方法。所有的 J2EE 容器,都必須提供一個 JNDI 服務,而客戶端程序則通過使用 J2EE 容器提供的 JNDI 來訪問容器內的 EJB 對象。 JNDI 的使用方法,基本上就是輸入一個字符串,然后 API 會返回給你一個對象。在 J2EE 的環境里,這個對象就是 EJB 對象的 Home 接口對象(對應遠程 EJB 對象的一個映像,也叫樁對象)。代碼類似:

Context ctx = new InitialContext(env);

Object ejbHome = ctx.lookup( java:comp/env/ejb/HelloBean );

HelloHome empHome = (HelloHome) PortableRemoteObject.narrow(ejbHome, HelloHome.class );

輸入 lookup() 函數的字符串,是用戶可以自己定義的任何內容,只要在對應的 EJB 容器里面登記了這個對應關系即可。從這個代碼我們可以看到,如果 EJB 想要做容災、負載均衡等功能,是完全可以通過 ctx.lookup() 這個接口來實現的。另外,遠程對象的 Home 接口(樁代碼)是需要預先部署在客戶端測,在上面的例子里是 HelloHome.class 這個類。而 EJB 對象的這個 Home 接口類,是由 EJB 工具,自動通過來源的 EJB 對象類定義生成的。對比 CORBA , Thrift 等技術, EJB 可以直接用 .java 源代碼代替 IDL 定義,然后自動生成樁代碼,這確實是簡便很多。

EJB 規范把遠程對象定義為三種:無狀態會話 Bean ,有狀態會話 Bean ,消息驅動 Bean 。這意味著 EJB 容器對于 EJB 對象的生命周期是有管理的。其中無狀態會話 Bean 和消息驅動 Bean 的聲明周期是類似的,都是來一個請求(消息驅動的意思是每來一個 JMS 消息),就可能 new 一個 Bean 對象。當然也可能不是每次請求都新建對象,總之容器不保證會保持 Bean 對象的生存周期,這樣容器可以根據負載壓力,靈活的管理眾多的 Bean 對象。而最特別的是“有狀態會話 Bean ”,容器會根據客戶端的會話狀態(和客戶端的 context 對象對應),來保持 Bean 對象,也就是說,每個客戶端 context 對應一個有狀態 Bean 。如果你用這個客戶端 context ,發起多次 lookup() 查找,訪問的那個 EJB 對象都將會是同一個。這對于需要保持登錄狀態的服務,就非常方便了。客戶無需自己去維持一個遠程對象的生命周期,而能得到狀態保存的功能。

最后說說 EJB 的部署配置,以前的 EJB 容器部署異常復雜。除了需要寫一個繼承于特定基類的業務 JAVA 類外,還要配置很多細節。而 EJB3.0 之后,通過 JAVA 注解功能( Annotation ),這些配置都可以和源代碼寫到一起,而業務 JAVA 類也無需集成特定的接口和類型,可以是任何一個普通的類( POJO ),只是需要加上一些特定的注釋即可。 EJB 容器提供工具對這些加了 EJB 注釋的 JAVA 類進行處理,一方面把這個 JAVA 類自動部署到容器中,另一方面生成客戶端的 Home 接口類文件,供用戶發布(拷貝)到需要使用的客戶方服務器上去。而一些 EJB 容器(如 Weblogic )還提供了 Eclipse ( IDE )的圖形界面工具,讓整個過程幾乎都不在需要編寫額外的配置和命令行操作。

二, MS WCF

WCF 全稱 Windows Communication Foundation ,是微軟發布的用于構建面向服務的應用程序框架。這套框架的底層是 Windows 的 COM+ 技術,而編程接口則更多的使用 C# 語言 /VB 語言和 .Net 平臺。這和 EJB 有一定的類似,差別就是 WCF 中的遠程對象,不需要一個像 JVM 那樣的虛擬機,而是結合在 WINDOWS 操作系統里。

淺析“遠程對象調用” 

無獨有偶, WCF 的遠程接口定義,也是直接使用 C#/VB 代碼,加上類似注解的“特性”( Attribute )功能注釋,標注在一個定義好的接口( Interface )上來組成的。具體的業務實現類,只要“實現”定義的這個接口就可以了,和一個普通的類沒有任何差別。和 EJB 的差別是,我們還是需要寫一段 XML 配置,把這個遠程對象的接口和查找字符串,注冊到萬能的 IIS 服務器里面。一旦注冊完成,就可以通過 URL : http://xx.xx.xx.xx/servicesname/service.svc 這樣的字符串去訪問了。同時,如果客戶端想要訪問這個遠程對象,則需要使用 svcuitl.exe 這個工具,輸入剛剛注冊的那個 URL ,就可以生成對應的客戶端樁代碼庫。客戶端可以直接 new 這個新建立的樁類型對象,然后直接調用其方法,就和調用本地對象的方法一樣。

// Create a client.

CalculatorClient client = new  CalculatorClient();

// Call the Add service operation.

double  value1 = 100.00D;

double  value2 = 15.99D;

double  result = client.Add(value1, value2);

Console.WriteLine( "Add({0},{1}) = {2}" , value1, value2, result);

當然,如果你想連接不同的服務器,還是有機會的,一位內生成的客戶端代碼,會使用一個配置文件。在里面可以修改遠程服務器的地址(還是那個注冊的 URL )。

< client >

< endpoint

address = " http://localhost/servicemodelsamples/service.svc "

binding = " wsHttpBinding "

contract = "  Microsoft.ServiceModel.Samples.ICalculator " />

</ client >

你除了可以通過 IIS 來提供 WCF 的遠程對象服務外,還可以自己寫一個單獨的程序,通過定義 main() 來完全的控制這些遠程對象,從而提供服務。另外, WCF 除了通過 URL 直接對應一個遠程對象外,還可以通過編寫“路由服務”,來對同一個 URL 的遠程對象調用進行靈活的路由。雖然 WCF 沒有提供類似 EJB 的遠程對象生命周期管理功能,但是你完全可以通過 WCF 的服務 API 和路由服務,來自己編碼實現任何形式的遠程對象生命周期管理。

三, IBM RMI-IIOP

IBM 公司的 RMI-IIOP 服務,是以 JAVA 技術為基礎的,但是又不同于 EJB 的另外一套遠程對象技術。這套技術更接近于以 JAVA 為基礎實現的 CORBA 體系。這個技術的使用標準的 JAVA RMI 接口( RMIInterface )作為遠程對象的接口,使用 JAVA 的序列化、反序列化能力作為編碼能力。然后自己寫一個 main() 函數,建立一個 org.omg.CORBA.ORB 對象來構造一個遠程服務器。而客戶端則是通過一個字符串來定位想要訪問的遠程對象。這個字符串類似: corbaloc:iiop:1.2@localhost:8080/OurLittleClient   。我們可以看到這里面有 IP 和端口,還有一個編寫服務器遠程對象時注冊的字符串 OurlLittleClient 。我們通過 rmic – iiop Server 這樣的命令行部署遠程對象,然后用 start java Server 啟動服務器,用 start java Client 啟動客戶機。這些命名,都是包含在 IBM Developer Kit for Java technology v1.3.1 里面的。我們可以發現, RMI-IIOP 是一個更加原始的遠程對象方案,基本上就是一個 CORBA 的 API 實現的組合。使用起來有點繁瑣,但是好處是不需要學習和部署復雜的容器服務,可以完全自己編碼去實現一套遠程對象服務。這里沒有限定你使用什么方法去定位查找遠程對象,也沒有限定你怎么管理遠程對象的生命周期,一切都由開發者自己去編寫實現。

總結

規范

遠程對象定位

遠程對象生命周期管理

服務器部署

EJB

JNDI 路徑字符串查找

自動管理,帶會話狀態對象

使用容器服務

WCF

URL 、路由服務

部署到 IIS 或自寫 main()

RMI-IIOP

COBRA URL 定位

自寫 main()

 

在對象定位的選擇上,通過字符串查找已經是標準,而復雜的自定義路由也可以隱藏在這個查找操作下面。遠程對象的生命周期管理,實際上是對服務器資源的管理,除了 EJB 有容器支持以外,其他的方案都比較少提供這樣的能力,說明這一塊是比較困難的。服務器部署方面,可以讓用戶以 API 自己寫 main() 去構建服務器,提供了極大的靈活性。

遠程對象的挑戰:生命周期管理、數據一致性

通過上面的分析,我們可以發現,遠程對象的生命周期管理,是一個比較重大且復雜的課題。我們要保證這樣的生命周期管理程序,能有一個通用的策略,來保持各種業務情況下的服務器資源穩定,是比較困難的。而且在分布式系統的情況下,為了負載均衡,還要把同樣類型的遠程對象,部署到不同的進程上,這就引入了一個新的問題:數據一致性。

遠程對象的生命周期,除了占用服務器的內存資源外,還會占用記錄其地址的路由空間,檢查維護生命周期的 CPU 運算時間。如果我們提供自動化的對象生命周期管理,勢必就需要在客戶使用的時候,提供這方面的教育,以及防止客戶使用錯誤、過載等情況下對象管理失效的防御性策略。所以即便是 EJB 容器,也僅僅提供了非常簡單的生命周期管理策略:會話狀態、無狀態這兩種。

對于一般的互聯網應用,只有 EJB 這兩種生命周期管理的遠程對象,基本上是夠用的。因為一般的互聯網應用,大部分數據都是持久化數據,需要讀寫數據庫。臨時狀態數據一般來說不多,主要是用戶登錄后的產生的一些過程數據,有一個“會話( Session )”類型的生命周期就足夠了。但是,如果我們的業務是網絡游戲,那么這么簡單的生命周期就是完全不夠的,因為游戲中有大量的臨時狀態,比如組隊的狀態,玩家所在房間的狀態,關卡副本的狀態等等。這些臨時狀態,都是需要我們通過業務邏輯代碼,來控制和管理所對應的對象生命周期的。所以一個適合游戲的遠程對象系統,需要提供讓客戶端程序來選擇,“新建 / 初始化”和“銷毀”遠程對象的能力。

在對遠程對象進行管理的時候,我們常常會用到一種叫“對象池”的技術,使用這種技術避免頻繁的新建和銷毀對象。但是如果這些對象的是帶狀態的,那么我們的“池”就必須帶索引,并且對象也必須有一個 key 。同時我們的對象還需要有一個“ reset ”的重置方法,用來讓對象回歸到初始化狀態。

在分布式的系統下,我們的對象池因為是分別存放在不同的機器上,所以其一致性的維護往往是比較困難的。但是,我們可以把這個問題,轉換成構建一個“分布式對象池”的問題。假如每個對象池,都按 KEY 的某個規律,如一致性哈希,存放不同的對象。那么只要在遠程調用發起的時候,也就是通過 lookup() 查找遠程對象的時候,把請求導向到對象所在進程,那么就能很方便的從本地進程對象池中獲得對象。遠程對象的“定位”和“一致性”在查找對象這個環節結合起來,是一個非常好的想法。這樣能讓遠程狀態對象的使用進一步簡化,用戶完全無需關心遠程對象在什么地方,又能快速的訪問到正確的對象。

[ 擴容下的遠程對象遷移 ]

當分布式的對象容器出現部分進程故障,或者需要動態擴容的時候,只要我們針對對象查找的數據做某種程度的數據搬遷,或者緩存清理,就能很容易的實現對象的重新分布。如果對象同時能夠支持持久化,那么這種數據搬遷,只需要簡單的讓對象寫入持久化。然后在新的機器上,通過緩存建立的策略,從持久化設備讀取出對象即可。

總結

遠程對象調用,是一種業界成熟的分布式服務器系統模型。這套模型提供了強大的分布式程序架構能力,并且能方便的置入統一的運維特性能力:容災、擴容、負載均衡。

它比遠程方法調用,增加了對數據位置的指向,能有效的提高系統的響應速度。同時面向對象的形態,也能顯著降低復雜邏輯的開發成本。

遠程對象的生命周期管理,實際上一種分布式緩存系統的管理。良好的遠程對象系統,能提高豐富的生命周期管理功能,以適合網絡游戲,這種需要處理豐富臨時狀態的行業需求。

如果我們把遠程對象的尋址和數據一致性維護結合起來,并且提供對象的持久化支持,那么遠程對象調用將是一個高度自動化,且具有自我維護能力的強大分布式計算系統。

 

 

來自:http://mp.weixin.qq.com/s?__biz=MzA5ODExMTkwMA==&mid=502771982&idx=1&sn=06ad9449df7c2a9b82e5d95e7979eb44&chksm=089589fd3fe200eb5c19d7d93141d742b211a03c1499c1776a00c17851d18ff333abeedd7923&mpshare=1&scene=2&srcid=10100dlIfYKNX41SjCc34eLh&from=timeline&isappinstalled=0#wechat_redirect

 

 本文由用戶 EarlHoliday 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!