官术网_书友最值得收藏!

2.8 遠程方法調(diào)用(RMI)

基于對象的技術已經(jīng)展示了它在開發(fā)非分布式應用程序方面的價值。對象最重要的特征之一是,通過定義良好的接口向外界隱藏其內(nèi)部結(jié)構。這種機制保證了只要保持對象接口不變,就可以方便地替換或者修改對象。

當RPC機制逐漸成為分布式系統(tǒng)通信處理的事實標準時,人們開始認識到RPC的原理同樣可以應用于對象。在本節(jié)中,把RPC的思想拓展到對遠程對象的調(diào)用上,并且說明與RPC相比,這種方法是如何增強分布透明性的。本節(jié)集中討論遠程對象調(diào)用。在第4章中,將詳細討論包括CORBA、DCOM和J2EE在內(nèi)的多種基于對象的分布式系統(tǒng)。

2.8.1 分布式對象

對象的關鍵特性是對數(shù)據(jù)的封裝,這些被封裝的數(shù)據(jù)稱為狀態(tài)(State),對這些數(shù)據(jù)的操作稱為方法(Method)。只有通過接口(Interface)才能使用方法。理解以下內(nèi)容很重要:一個進程除了可以通過對象的接口來引用它可用的方法外,不存在其他合法途徑來訪問或者操縱對象的狀態(tài)。一個對象可以實現(xiàn)多個接口。同樣,在給定一個接口定義的情況下,也可以由若干個對象提供該接口的實現(xiàn)。

將接口與實現(xiàn)這些接口的對象分離開來,對于分布式系統(tǒng)是至關緊要的。由于進行了嚴格的分離,因此可以將接口放在一臺機器上,而讓對象本身駐留在另一臺機器上。這種組織結(jié)構如圖2-46所示,通常稱為分布式對象(Distributed Object)。

圖2-46 遠程對象調(diào)用的一般組織結(jié)構

當一個客戶綁定(Bind)到一個分布式對象的時候,該對象接口的一種實現(xiàn)——稱為代理(Proxy)被加載到客戶機的地址空間中。代理與RPC系統(tǒng)中的客戶機存根相類似。代理惟一的工作是將對方法的調(diào)用編組成消息,并且對應答消息進行解編,將調(diào)用的方法得到的結(jié)果返回給客戶機。實際對象駐留在服務器所在機器上,它向服務器提供的接口與向客戶機提供的接口相同。輸入的調(diào)用請求首先傳遞給服務器存根—這種存根也稱為骨架(skeleton),它將調(diào)用請求解編成對服務器上對象接口的恰當?shù)姆椒ㄕ{(diào)用。服務器存根同時還負責對應答消息進行編組,并且將應答消息發(fā)送回客戶端代理。

多數(shù)分布式對象所特有的但同時又是直觀難以想象的特性是:它們的狀態(tài)并不是分布的;它駐留在單個機器上,只有由該對象實現(xiàn)的接口可以在其他機器上使用。這種對象也稱為遠程對象(Remote Objects)。對此將會在后續(xù)章節(jié)中介紹。在普通的分布式對象中,狀態(tài)本身可能是物理上分布在多臺機器上的,但是這種分布對于客戶端來說是隱藏在對象接口后面的。

1.編譯時對象與運行時對象

分布式系統(tǒng)中的對象可能以多種形式出現(xiàn)。最常見的一種對象形式直接與語言級對象(如Java、C++以及其他面向?qū)ο笳Z言直接支持的對象)相關聯(lián),這種對象稱為編譯時對象。在這種情況下,對象定義為類的實例。類(Class)是對一種抽象類型的描述,是含有數(shù)據(jù)元素以及對這些數(shù)據(jù)元素的操作的模塊。

在分布式系統(tǒng)中,使用編譯時對象常常使得構建分布式應用程序更加方便。比如,在Java中,可以通過定義類及該類實現(xiàn)的接口來完整地定義一個對象。對該類定義進行編譯,得到可以實例化Java對象的代碼。對接口進行編譯可以得到客戶機存根以及服務器存根,用來從遠程機器上引用Java對象。Java開發(fā)人員在大多數(shù)情況下都看不到對象的分布,所看到的只有Java程序的代碼。

編譯時對象的一個顯而易見的缺陷是對特定編程語言的依賴性。因此,另一種構建分布式對象的方法是,在運行時顯式生成對象。由于這種方案獨立于編寫分布式應用程序時所使用的編程語言,許多基于對象的分布式系統(tǒng)都使用它。應特別指出的是,可以使用由多種不同語言編寫的對象來構建應用程序。

在使用運行對象的時候,實現(xiàn)實際對象的方法是多種多樣的。比如,開發(fā)人員可以選擇用C語言編寫一個庫,庫中包含許多函數(shù),這些函數(shù)可以處理常規(guī)的數(shù)據(jù)文件。根本問題是,如何讓這樣的實現(xiàn)表現(xiàn)為一個對象,以便從遠程機器調(diào)用其方法。一種常用的方案是使用一種對象適配器(Object Adapter),它可以作為實現(xiàn)的一種包裝,惟一的作用就是讓該實現(xiàn)呈現(xiàn)出對象的外部特征。適配器(Adapter)這個術語來自于一種設計模式,這種設計模式用來將接口轉(zhuǎn)換成客戶機期望的某種東西。例如,動態(tài)綁定到上面所說的那個C庫,隨后打開代表對象當前狀態(tài)的對應數(shù)據(jù)文件的適配器就是對象適配器。

對象適配器在基于對象的分布式系統(tǒng)中扮演了一個重要角色。為了使包裝盡可能簡單,對象根據(jù)它們實現(xiàn)的接口來惟一定義。接口的實現(xiàn)可以在適配器中注冊,隨后適配器可以使該接口可供(遠程)調(diào)用。適配器會注意到有調(diào)用請求發(fā)出,隨后向客戶機提供遠程對象的映像。

2.持久對象和暫時對象

除了可以將對象分為語言級對象和運行時對象外,還可以將對象分為持久對象(Persistent Object)和暫時對象(Transient Object)。持久對象無論當前是否位于服務器進程的地址空間內(nèi),都始終保持存在。換句話說,持久對象并不依賴于當前的服務器。這意味著當前管理持久對象的服務器在退出運行之前,可以先把持久對象的狀態(tài)存儲到輔助存儲器上;之后,新啟動的服務器可以由存儲器將對象的狀態(tài)讀到自己的地址空間中,對調(diào)用請求進行處理。與持久對象相反,暫時對象只在管理該對象的服務器存在的期間存在,服務器退出運行后,該對象也就不再存在。在是否應該使用持久對象的問題上存在爭論:有些人認為使用暫時對象就足夠了。

2.8.2 將客戶機綁定到對象

傳統(tǒng)RPC系統(tǒng)與支持分布式對象的系統(tǒng)有一個很有趣的不同點,后者一般都提供系統(tǒng)范圍內(nèi)的對象引用。這種對象引用(比如作為方法調(diào)用的參數(shù))可以在位于不同機器上的進程之間自由傳遞。通過將對象引用的實際實現(xiàn)隱藏起來,使其變得不透明,甚至變成引用對象的惟一途徑,與傳統(tǒng)RPC系統(tǒng)相比,分布透明性增強了。

如果進程獲得了某個對象的引用,那么在調(diào)用該對象的任何方法之前必須首先綁定到該對象。綁定將產(chǎn)生一個位于該進程地址空間內(nèi)的代理,它實現(xiàn)了包含有此進程所能調(diào)用的方法的接口。在多數(shù)情況下,綁定是自動進行的。如果給底層系統(tǒng)一個對象引用,它就需要通過某種途徑來定位管理該實際對象的服務器,隨后將代理放置在客戶機地址空間中。

通過隱式綁定(Implicit Binding),可向客戶機提供一種簡單機制,該機制允許客戶機在只使用對象引用的情況下直接進行方法調(diào)用。比如,C++允許重載一元成員選擇操作符(—>),這樣就可以引入對象的引用,就好像它們是通常的指針一樣,如圖2-47(a)所示。通過隱式綁定,可以在引用被連接到實際對象上時將客戶機透明地綁定到對象上。與之相反,如果使用顯式綁定(Explicit Binding),客戶機必須首先調(diào)用某個特殊函數(shù)來綁定到對象上,隨后才能調(diào)用該對象的方法。顯式綁定一般返回指向代理的指針,該代理可以在本地使用,如圖2-47(b)所示。

圖2-47 隱式綁定和顯式綁定的例子

很明顯,對象引用必須包含足夠的信息,以便使客戶機可以綁定到服務器上。一個簡單的對象引用包含實際對象駐留的機器的網(wǎng)絡地址,以及端點(用來標志管理該對象的服務器),還有對象的標號。對象的標號一般由服務器提供,比如可以用16 位數(shù)來表示。這里的端點對應于由服務器的本地操作系統(tǒng)動態(tài)分配的某個本地端口。然而,這種對象引用存在諸多缺陷。

首先,如果服務器所在機器崩潰,而在機器恢復后又給服務器分配一個與原先不同的端點,所有原來的對象引用就都變得無效了。這個問題可以通過如下辦法解決:在每臺機器上駐留一個本地守護程序,監(jiān)聽某個公開的端點,并且通過端點表來追蹤服務器與端點的對應關系。在將客戶機綁定到對象上之前,首先要請求守護程序給出服務器當前所用的端點。如果使用這種方案,就需要將服務器ID編碼到對象引用中,該對象引用可作為端點表中的索引,還要求服務器通過本地守護程序進行注冊。

然而,將服務器所在機器的網(wǎng)絡地址編碼到對象引用中并不是一個好主意。這種方法存在的問題是,在不破壞服務器所管理的所有對象引用的前提下,無法將服務器移到另一臺機器上。一個容易想到的解決方法是,對維護端點表的守護程序的思路進行拓展,使用定位服務器(Location Server)來追蹤管理對象的服務器當前在哪一臺機器上運行。在對象引用中不僅包含定位服務器的網(wǎng)絡地址,而且還含有服務器的系統(tǒng)級標志符。這種解決方法同樣也存在很多嚴重缺陷,特別是在側(cè)重考慮可擴展性的情況下更是如此。

前面已做了這樣一個默認的假定:客戶機和服務器都已經(jīng)配置為使用相同的協(xié)議棧。這不僅意味著它們使用相同的傳輸協(xié)議(如TCP),而且意味著它們必須使用相同的協(xié)議進行參數(shù)編組和解除編組,而且還必須使用相同的協(xié)議來建立初始連接,以相同方式進行錯誤處理和流控制,等等。

如果能夠從對象引用中得到更多的信息,就能夠放心地丟掉這個假定。所需要的信息包括用來綁定到一個對象的協(xié)議標志,以及那些由負責對象管理的服務器支持標志。比如,某個服務器可能支持通過TCP連接和UDP數(shù)據(jù)報同時傳入數(shù)據(jù),此時客戶機就必須負責為對象引用中標出的至少一種協(xié)議提供代理實現(xiàn)。

對以上方案做進一步的改進,在對象引用中加入實現(xiàn)句柄(Implementation Handle),它指向代理的完整實現(xiàn),客戶機可以在綁定到對象時動態(tài)加載這些代理。比如,實現(xiàn)句柄可以采用URL的形式,指向檔案文件,例如ftp://ftp.clientware.org/proxies/java/proxy-vl.la.zip。綁定協(xié)議隨后,只需要規(guī)定這種文件必須動態(tài)下載、解包、安裝且實例化。這種方案的優(yōu)點是,客戶端不需要關心它是否擁有針對于某種特定協(xié)議的實現(xiàn)。另外,它還賦予對象開發(fā)者自主設計針對特定對象的代理的自由。然而,必須采取專門的安全措施來確保客戶機能夠信任下載的代碼。

2.8.3 靜態(tài)遠程方法調(diào)用與動態(tài)遠程方法調(diào)用

在將客戶機綁定到對象之后,就可以通過代理來調(diào)用對象的方法。這種遠程方法調(diào)用(Remote Method Invocation)簡稱為RMI,它在參數(shù)編組及傳遞方面十分接近于RPC。RMI與RPC的本質(zhì)上不同是,RMI一般支持系統(tǒng)級對象引用,就像前面所介紹的那樣。另外,RMI不需要使用通用的客戶機和服務器存根,而可以更方便地使用針對特定對象的存根。

提供RMl支持的常規(guī)方法是,利用接口定義語言來指定對象的接口,與RPC中采用的方法相似。另外,也可以使用諸如Java之類的基于對象的語言來自動生成存根。使用預先確定的接口定義的方法一般稱為靜態(tài)調(diào)用(Static Invocation)。靜態(tài)調(diào)用要求在開發(fā)客戶應用程序時就已知對象接口,這也意味著如果接口發(fā)生變化,就必須重新編譯客戶應用程序,然后才能使用新的接口。

另一種方法是,采用更為動態(tài)的方式進行方法調(diào)用。在實踐中,有時在運行時建立方法調(diào)用會更為方便,這種做法稱為動態(tài)調(diào)用(Dynamic Invocation),它與靜態(tài)調(diào)用的本質(zhì)區(qū)別是,使用動態(tài)調(diào)用的應用程序直到運行時才對需要在遠程對象上調(diào)用的方法做出選擇。動態(tài)調(diào)用的常用形式如下所示:

invoke(object,method,input_parameters,output_parameters);

其中,參數(shù)object代表分布式對象,參數(shù)method指定要調(diào)用的方法,input_parameters是存儲方法需要的輸入?yún)?shù)值的數(shù)據(jù)結(jié)構,而output_parameters是存儲輸出值的數(shù)據(jù)結(jié)構。

舉個例子,考慮在文件對象fobject的末尾添加整型數(shù)int,該對象實現(xiàn)了方法append。在這種情況下,靜態(tài)調(diào)用的形式是:

fobject.append(int)

相應的動態(tài)調(diào)用的形式可能會是:

invoke(fobject,id(append),int)

其中,操作id(append)返回方法append的標志符。

為了進一步說明動態(tài)調(diào)用的用處,下面研究對象瀏覽器,它可以用來查看一組對象。假定該瀏覽器支持遠程對象調(diào)用。這種瀏覽器能夠綁定到分布式對象上,而且可將分布式對象的接口提供給用戶,隨后要求用戶選擇一個方法,并且提供該方法的輸入?yún)?shù)值,然后瀏覽器就可以執(zhí)行實際調(diào)用了。典型地,開發(fā)這種對象瀏覽器時應該考慮到為任何可能的接口提供支持,這就要求在運行時檢查接口并動態(tài)建立方法調(diào)用。

另一個應用動態(tài)調(diào)用的例子是批處理服務。這種服務可以處理指定了調(diào)用執(zhí)行時間的調(diào)用請求。該服務可以通過調(diào)用請求隊列的形式實現(xiàn),在隊列中根據(jù)請求中指定的執(zhí)行調(diào)用時間的先后來對調(diào)用請求進行排序。該服務中的主循環(huán)的結(jié)構是:簡單地等待,直到下一次調(diào)用的時刻來臨,在調(diào)用前將該調(diào)用請求從隊列中移出,然后調(diào)用上面代碼中的invoke。

2.8.4 參數(shù)傳遞

由于多數(shù)RMI系統(tǒng)都支持系統(tǒng)級對象引用,因此對方法調(diào)用中的參數(shù)傳遞的要求不像RPC中那么嚴格。然而,也有一些細微的東西會使得RMI變得比預想的復雜,將在下面對這些問題進行簡要的討論。

首先考慮以下這種狀況:只有分布式對象存在,換句話說也就是系統(tǒng)中的所有對象都可以通過遠程機器訪問。在這種情況下,可以一成不變地使用對象引用作為方法調(diào)用的參數(shù)。引用通過值來進行傳遞,從一臺機器復制到另一臺機器上。當把作為方法調(diào)用的結(jié)果的對象引用賦予進程時,該進程就可以在必要的時候簡單地綁定到引用的對象上。

不幸的是,只使用分布式對象可能造成效率降低,特別是在對象規(guī)模比較小的情況下,比如當對象是整型數(shù)或者布爾值的時候。如果客戶機引用的是位于另一服務器上的對象,那么它執(zhí)行的每一次調(diào)用所生成的請求都要跨越不同地址空間,甚至要跨越不同的機器。因此,指向遠程對象的引用和指向本地對象的引用要區(qū)別對待。

在使用對象引用作為參數(shù)來進行方法調(diào)用時,只有在該引用指向遠程對象的情況下,才將對象引用作為值參數(shù)來復制和傳遞。在這種情況下,對象是完全通過引用來傳遞的。然而,如果引用指向的是本地對象,也就是位于與客戶機相同的地址空間中的對象,那么被引用對象將完整地進行復制,并且與調(diào)用一起傳遞。也就是說,此時對象是通過值傳遞的。

注意:正在處理的引用指向的是本地對象還是遠程對象,可以是高度透明的,比如在Java中就是這樣。在Java中,二者間可見的惟一不同點只是:本地對象的數(shù)據(jù)類型與遠程對象在本質(zhì)上是不同的,而在其他方面,這兩種引用幾乎一樣。但是,如果使用的是C語言這樣的常規(guī)編程語言,指向本地對象的引用可以是簡單的指針,而這種簡單指針是不可能用來指向遠程對象的。

將對象引用作為參數(shù)來進行方法調(diào)用的做法也會帶來副作用,即可能要復制整個對象。因為無法對這種情況進行隱藏,所以被迫顯式地區(qū)分本地對象和分布式對象。很明顯,這種區(qū)分不但損害了分布透明性,而且導致編寫分布式應用程序變得更加困難。

主站蜘蛛池模板: 襄城县| 布尔津县| 施秉县| 尉犁县| 福海县| 马关县| 九江县| 宁安市| 德化县| 玉田县| 上林县| 吴堡县| 临邑县| 屏东县| 孝感市| 开鲁县| 耒阳市| 民县| 象州县| 许昌县| 天水市| 建宁县| 开平市| 南召县| 竹山县| 株洲市| 上高县| 长汀县| 蓝田县| 台东市| 宾川县| 安吉县| 湖州市| 都兰县| 囊谦县| 南安市| 山东| 河曲县| 济阳县| 天等县| 鹤峰县|