TinyRMI—RMI的封裝、擴充套件及踩到的坑的解決

悠悠悠然然發表於2016-05-09

在Tiny的平行計算中,引用了遠端方法呼叫工程,就是這裡說的TinyRMI,當時在寫測試用例的時候,只是在單機進行了測試,一切安好,但是Dawn在使用時,在多機進行試用,結果就出現了問題,最後花了不下一人周,才解決了Dawn發現的問題,最終解決了問題,也發現了RMI中的一些坑。可能有的人已經走過了,有的人如果沒有碰到,也可能會掉同樣的坑,因此把它成文,以饗讀者,避免上同樣的當。

此文的形成離不開Dawn的深入測試與分析,在此表示深深感謝!

功能需求

期望對Jdk中的Rmi進行一定的封裝提供以下特性:

  1. 支援本地物件註冊與登出
  2. 支援遠端物件註冊與登出
  3. 支援斷線重連

    由於網路故障,導致連線斷開,在網路故障恢復之後,可以繼續正常訪問

    由於RmiServerLocal停止,重新啟動後,客戶端註冊的物件要繼續可以正常訪問

    由於RmiServerRemote停止 ,重新啟動後,客戶端註冊的物件需要重新註冊
  4. 支援物件校驗

    如果某些物件已經失效,伺服器端可以把它從登錄檔去除,以避免別人拿到失效的物件

介面設計

 

可以看到RmiServer是繼承了Serializable和Remote介面的一個介面,它提供了註冊物件及取消註冊物件的多個方法。當然也提供了一些輔助方法,看起來還是非常簡單的。

另外還有一個輔助介面Verifiable,對於加入的遠端物件,如果實現了此介面,則可以對其有效性進行驗證,如果已經失效,將被自動從註冊無中去除。

1
2
3
4
5
6
7
8
9
10
11
/**
 * 是否可驗證,實現了此介面的類,可以進行校驗
 */
public interface Verifiable {
    /**
     * 校驗,如果校驗時不出現異常,就表示是OK的
     *
     * @throws RemoteException
     */
    void verify() throws RemoteException;
}

當然,要加入到RmiServer中,也要有一定的約束,因此設定了介面RemoteObject

1
2
public interface RemoteObject extends Serializable, Remote {
}

好的,至此為止,介面就算設計完了。

程式碼實現

第一版程式碼實現,偶預想的非常簡單,如何獲取Registry作為一個抽象方法由子類實現,其它都是針對Registry進行的操作,就放在抽象類中實現,分分鐘寫好,然後本地測試通過,洋洋自得中,卻濛濛然不知犯下了嚴重的錯誤…..

在單機環境下,測試都是好的,不管是RMI自己的測試用例還是複用它的平行計算工程。

但是Dawn在使用的時候,採用了Linux機器兩個物理機進行測試,問題出現了,錯誤資訊如下:

1
java.rmi.AccessException: Registry.Registry.bind disallowed; origin / 192.168.xxx.xxx is non-local host

我們用物理的兩臺計算機進行測試,也有同樣的問題。

於是Baidu、谷歌都用上了,查詢了終於找到原因:

Registry只有本地的才可以對登錄檔進行修改,遠端的只能用來檢視。

於是把RmiServerLocal作為一個遠端物件提供出來,讓RmiServerRemote來呼叫,心想這樣總可以了吧??但是還是不行,還是同樣的問題。

仔細閱讀JavaDoc文件和找到的一些材料,才理解了,這個bind過程只能在RmiServerLocal所在的機器中執行,即使是通過遠端服務呼叫,它還是認為是在RmiServerRemote中呼叫的……這尼瑪的坑爹了,這個RMI這麼難用,設計者知道麼?

這次改進,遠端機註冊的時候,只是新增到RmiServerLocal中的一個Queue中,然後在RmiServerLocal所在機器開一個掃描執行緒,來進行bind,unbind操作,這樣總保證是在Local中執行了。

測試一下,確實OK了……還沒有高興半下,Dawn報過來,又出問題了。

Windows作Server,Linux做客戶端的時候是OK的,但是Linux只要做伺服器端就還是不行。

繼續查詢原因,看到資料說是因為Linux查詢到IP與外部訪問的IP不對應導致,需要修改/etc/hosts檔案中的127.0.0.1到外部方面的IP地址。

修改之後,問題得到解決。

測試過程

初步跑通之後,接下來就是各種各樣的測試及各種各樣的問題。

由於採用了非同步註冊的關係,導致註冊時間過後一小段時間才可以訪問註冊到的物件。

有物件訪問衝突問題,等等等等,總之就是測了改,改了測,最後終於修正完畢,終於有一個穩定的可用的版本出現了。

最後,由於原來的設想的程式碼共用基本上沒有複用的價值了,因此RmiServerLocal和RmiServerRemote都是各自的實現,不再有共同的基類。

經驗總結

  1. 測試用例的編寫一定要充分覆蓋。
  2. 涉及到網路方面的測試,不能僅在本地測試通過就可以,一定要用真實的環境進行測試。
  3. 不同的作業系統處理還是有一些不同,不要太迷信Java的一次編寫到處使用,事實是到處可以跑,在大多數情況下也是可以正確的跑的,但是有些外部條件不同,可能會導致故障的出現。

關於RMI:

  1. 如果是客戶端僅呼叫伺服器端提供的物件,那麼是非常簡單的。
  2. 如果是客戶端也要向伺服器註冊遠端物件,那麼就需要採用非同步的方式,搞一下注冊或登出佇列。

程式碼本身不復雜,需要的同學,請自行檢視原始碼。

https://git.oschina.net/tinyframework/tiny/tree/master/framework/org.tinygroup.rmi


相關文章