為JAVA性能而設計(三)

  第三部分: 遠程接口

創(chuàng)新互聯(lián)是一家專業(yè)提供圖木舒克企業(yè)網(wǎng)站建設,專注與成都網(wǎng)站設計、成都網(wǎng)站制作、H5頁面制作、小程序制作等業(yè)務。10年已為圖木舒克眾多企業(yè)、政府機構(gòu)等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設公司優(yōu)惠進行中。

  概述
  許多 Java 的通常性能問題來源于設計過程早期的類設計想法中, 早在開發(fā)者開始考慮性能問題之前. 在這個系列中, Brian Goetz 討論了一些通常的 Java 性能的冒險, 解釋了怎樣在設計時間避免它們. 在這篇文章中, 它檢驗了遠程應用程序中的特定的性能問題.

  遠程調(diào)用的概念

  在分布式的應用程序中, 一個運行在一個系統(tǒng)中的對象可以調(diào)用另一個系統(tǒng)中的一個對象的方法. 這個通過很多使遠程對象表現(xiàn)為本地的結(jié)構(gòu)的幫助而實現(xiàn). 要訪問一個遠程對象,你首先要找到它, 可以通過使用目錄或者命名服務來實現(xiàn), 象 RMI 注冊, JNDI, 或者 CORBA命名服務.

  當你通過目錄服務得到一個遠程對象的引用時, 你并沒有得到那個對象的實際的引用, 而是一個實現(xiàn)了和遠程對象同樣接口的stub對象的引用. 當你調(diào)用一個stub對象的方法時, 對象把方法的所有參數(shù)匯集起來 -- 把它們轉(zhuǎn)化成一個字節(jié)流的表現(xiàn)形式, 類似于序列化過程. 這個stub對象把匯集的參數(shù)通過網(wǎng)絡傳遞給一個skeleton對象, 把參數(shù)分解出來, 你想調(diào)用的實際的對象的方法. 然后這個方法向skeleton對象返回一個值, skeleton對象把它傳送給stub對象, stub對象把它分解出來, 傳遞給調(diào)用者. Phew! 一個單獨的調(diào)用要做這么多的工作. 很明顯, 除去表面的相似性, 一個遠程方法調(diào)用比本地方法調(diào)用更大.

  以上描述瀏覽了一些對于程序性能非常重要的細節(jié). 當一個遠程方法返回的不是一個原類? 而是一個對象時, 會發(fā)生什么? 不一定. 如果返回的對象是一種支持遠程方法調(diào)用的類型, 它就創(chuàng)建一個中stub對象和一個skeleton對象, 在這種情況下需要在注冊表中查找一個遠潭韻,這顯然是一個高代價的操作. (遠程對象支持一種分布式的垃圾回收的形式, 包括了每一個參與的 JVM 維護一個線程來和其他 JVM 的維護線程進行通訊, 來回傳遞引用信息). 如果返回的對象不支持遠程調(diào)用, 這個對象所有的域和引用的對象都要匯集起來, 這也是一個代價的操作.

  遠程和本地方法調(diào)用的性能比較

  遠程對象訪問的性能特征和本地的不一樣:遠程對象的創(chuàng)建比本地對象創(chuàng)建代價要高. 不僅僅是當它不存在時要創(chuàng)建它, 而且stub對和skeleton對象也要創(chuàng)建, 還要互相感知.

  遠程方法調(diào)用還包括網(wǎng)絡的傳遞 -- 匯集起來的參數(shù)必須發(fā)送到遠程系統(tǒng), 而且響應也需匯集起來, 在調(diào)用程序重新得到控制權(quán)之前發(fā)送回來. 匯集, 分解, 網(wǎng)絡延時, 實際的遠調(diào)用所導致的延遲都加在一起; 客戶端通常是等待所有這些而步驟完成. 一個遠程調(diào)用也大地依賴于底層網(wǎng)絡的延時.

  不同的數(shù)據(jù)類型有不同的匯集開支. 匯集原類型相對來說花費少一些; 匯集簡單的對象, Point 或者 String 要多一些; 匯集遠程對象要多得多, 而匯集那些引用非常多的對象的對象(象 collection 等)要更多. 這和本地調(diào)用完全矛盾, 因為傳遞一個簡單對象的引用比一個復雜對象的引用花費多.

  接口設計是關(guān)鍵

  設計不好的遠程接口可能完全消除一個程序的性能. 不幸的是, 對本地對象來說好的接口的特性對遠程對象可能不適合. 大量的臨時對象創(chuàng)建, 就象在本系列的第一, 二部分討論,也能阻礙分布式的應用程序, 但是大量的傳遞更是一個性能問題. 所以, 調(diào)用一個在一個時對象(比如一個 Point)中返回多個值的方法比多次調(diào)用來分別得到它們可能更有效.

  實際遠程應用程序的一些重要的性能指導:

  提防不必要的數(shù)據(jù)傳遞. 如果一個對象要同時得到幾個相關(guān)的項, 如果可能的話, 在一個遠程調(diào)用中實現(xiàn)可能容易一些.
  當調(diào)用者可能不必要保持一個遠程對象的引用時, 提防返回遠程的對象.當遠程對象不需要一個對象的拷貝時, 提防傳遞復雜對象.
  幸運的是, 你可以通過簡單查看遠程對象的接口來找出所有的問題. 要求做任何高層動作的方法調(diào)用序列可以從類接口中明顯看到. 如果你看到一個通常的高層操作需要許多連續(xù)的遠程方法調(diào)用, 這就是一個警告信號, 可能你需要重新查看一下類接口.

  減少遠程調(diào)用代價的技巧

  一個例子, 考慮下面假定的管理一個組織目錄的應用程序: 一個遠程的 Directory 對象包含了 DirectoryEntry 對象的引用, 表現(xiàn)了電話簿的入口.

public interface Directory extends Remote {
DirectoryEntry[] getEntries();
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}
public interface DirectoryEntry extends Remote {
String getName();
String getPhoneNumber();
String getEmailAddress();
}

  現(xiàn)在假設你想在一個 GUI email 程序中使用 Directory 的東西. 程序首先調(diào)用getEntries() 來得到入口的列表, 接著在每個入口中調(diào)用 getName(), 計算結(jié)果的列表,當用戶選擇一個時, 應用程序在相應的入口調(diào)用 getEmailAdress() 來得到 email 地址.

  在你能夠?qū)懸环?email 之前有多少遠程方法調(diào)用必須發(fā)生? 你必須調(diào)用 getEntries() 一次, 地址簿中每個入口調(diào)用一次 getName(), 一次 getEmailAddress(). 所以如果在地址中有 N 個入口, 你必須進行 N + 2 次遠程調(diào)用. 注意你也需要創(chuàng)建 N + 1 個遠程對象引用, 也是一個代價很高的操作. 如果你的地址簿有許多入口的話, 不僅僅是打開 email 窗口的時候非常慢, 也造成了網(wǎng)絡阻塞, 給你的目錄服務程序造成高負載, 導致可擴展性的問題.

  現(xiàn)在考慮增強的 Directory 接口:

public interface Directory extends Remote {
String[] getNames();
DirectoryEntry[] getEntries();
DirectoryEntry getEntryByName(String name);
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}

  這將減少多少你的 email 程序所造成的花費呢? 現(xiàn)在你可以調(diào)用 Directory.getNames()一次就可以同時得到所有的名字, 只需要給你想要發(fā)送 email 的容器調(diào)用 getEntryByName() .這個過程需要 3 個遠程方法調(diào)用, 而不是 N + 2, 和兩個遠程對象, 而不是 N + 1 個.如果地址簿有再多一點的名字, 這個調(diào)用的減少在程序的響應和網(wǎng)絡負載和系統(tǒng)負載有很大的不同.

  用來減少遠程調(diào)用和引用傳遞的代價的技術(shù)叫做使用次要對象標識符. 使用一個對象的標屬性, -- 在這個例子中, 是 name -- 而不是傳回一個遠程對象, 作為對象的一個輕量級曄斗?次要標識符包含了它描述的對象足夠的信息, 這樣你只需要獲取你實際需要的遠程對象.在這個目錄系統(tǒng)的例子中, 一個人的名字是一個好的次要標識符. 在另一個例子中, 一個安全皮包管理系統(tǒng), 一個采購標識號可能是一個好的次要標識符.

  另一個減少遠程調(diào)用數(shù)量的技巧是塊獲取. 你可以進一步給 Directory 接口加個方法, 來一次獲取多個需要的 DirectoryEntry 對象:

public interface Directory extends Remote {
String[] getNames();
DirectoryEntry[] getEntries();
DirectoryEntry getEntryByName(String name);
DirectoryEntry[] getEntriesByName(String names[]);
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}

  現(xiàn)在你不僅可以得到需要的遠程 DirectoryEntry , 也可以用單獨一個遠程方法調(diào)用得到要的所有的入口. 雖然這并不減少匯集的代價, 但極大地較少了網(wǎng)絡往返的次數(shù). 如果網(wǎng)延遲很重要的話, 就可以產(chǎn)生一個響應更快的系統(tǒng)(也能減少這個網(wǎng)絡的使用).

  照亮去向 RMI 層次的路徑的第三的技巧是不把 DirectoryEntry 作為一個遠程對象, 而把它定義為一個通常的對象, 帶有訪問 name, address, email address 和其他域的訪問函數(shù).(在 CORBA 系統(tǒng)中, 我可能要使用類似的 object-by-value 機制.) 然后, 當 email 應用程序調(diào)用 getEntryName() 時, 它會獲取一個 entry 對象的值 -- 不需要創(chuàng)建一個stub對象或者skeleton對象, getEmailAddress() 的調(diào)用也是一個本地的調(diào)用而不是一個遠程的.

  當然, 所有這些技巧都都依賴于對遠程對象實際上是怎樣使用的理解上的, 但是對于這個理解, 你甚至不需要看一看遠程類的實現(xiàn)就可以找出一些潛在的嚴重性能問題.

  結(jié)論

  分布式的應用程序的性能特性本質(zhì)上和本地程序不同. 許多對于本地程序代價很小的操作對于遠程應用程序來說代價非常高, 設計不好的遠程接口導致一個程序有嚴重的擴展性和能問題.

  幸運的是, 很容易在設計時候, 為那些高代價的操作(象遠程調(diào)用和遠程對象創(chuàng)建), 通過平常的用例和分析它們, 確定和解決許多通常的分布式的性能問題, 正確使用這里提到的技巧,次要的對象標識符, 塊獲取和 return-by-value -- 可以本質(zhì)上提高用戶響應時間和整個統(tǒng)的吞吐量.

[@more@]

文章標題:為JAVA性能而設計(三)
分享路徑:http://bm7419.com/article24/jdecje.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設、網(wǎng)站策劃品牌網(wǎng)站設計、網(wǎng)頁設計公司、微信小程序、網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設