溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

為JAVA性能而設(shè)計(三)

發(fā)布時間:2020-08-13 21:46:54 來源:ITPUB博客 閱讀:120 作者:rainytag 欄目:編程語言

  第三部分: 遠(yuǎn)程接口

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

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

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

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

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

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

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

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

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

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

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

  實際遠(yuǎn)程應(yīng)用程序的一些重要的性能指導(dǎo):

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

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

  一個例子, 考慮下面假定的管理一個組織目錄的應(yīng)用程序: 一個遠(yuǎn)程的 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)在假設(shè)你想在一個 GUI email 程序中使用 Directory 的東西. 程序首先調(diào)用getEntries() 來得到入口的列表, 接著在每個入口中調(diào)用 getName(), 計算結(jié)果的列表,當(dāng)用戶選擇一個時, 應(yīng)用程序在相應(yīng)的入口調(diào)用 getEmailAdress() 來得到 email 地址.

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

  現(xiàn)在考慮增強(qiáng)的 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 個遠(yuǎn)程方法調(diào)用, 而不是 N + 2, 和兩個遠(yuǎn)程對象, 而不是 N + 1 個.如果地址簿有再多一點的名字, 這個調(diào)用的減少在程序的響應(yīng)和網(wǎng)絡(luò)負(fù)載和系統(tǒng)負(fù)載有很大的不同.

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

  另一個減少遠(yuǎn)程調(diào)用數(shù)量的技巧是塊獲取. 你可以進(jìn)一步給 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)在你不僅可以得到需要的遠(yuǎn)程 DirectoryEntry , 也可以用單獨一個遠(yuǎn)程方法調(diào)用得到要的所有的入口. 雖然這并不減少匯集的代價, 但極大地較少了網(wǎng)絡(luò)往返的次數(shù). 如果網(wǎng)延遲很重要的話, 就可以產(chǎn)生一個響應(yīng)更快的系統(tǒng)(也能減少這個網(wǎng)絡(luò)的使用).

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

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

  結(jié)論

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

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

[@more@]
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI