系统间通信2:通信管理与远程方法调用RMI
本文引用 : https://yinwj.blog.csdn.net/article/details/49120813
RMI : Remote Method Invocation,遠(yuǎn)程方法調(diào)用
RPC : Remote Procedure Call Protocol, 遠(yuǎn)程過程調(diào)用協(xié)議
ESB : Enterprise Service Bus, 企業(yè)服務(wù)總線
SOA : Service-Oriented Architecture, 面向服務(wù)的架構(gòu)
1. 概述
在這個(gè)章節(jié)我將通過對RMI的詳細(xì)介紹,引出一個(gè)重要的系統(tǒng)間通信的管理規(guī)范RPC,并且繼續(xù)討論一些RPC的實(shí)現(xiàn);再通過分析PRC的技術(shù)特點(diǎn),引出另一種系統(tǒng)間通信的管理規(guī)范ESB,并介紹ESB的一些具體實(shí)現(xiàn)。最后我們介紹SOA:面向服務(wù)的軟件架構(gòu)。
2. RMI基本使用
RMI(Remote Method Invocation,遠(yuǎn)程方法調(diào)用),是JAVA早在JDK 1.1中提供的JVM與JVM之間進(jìn)行 對象方法調(diào)用的技術(shù)框架的實(shí)現(xiàn)(在JDK的后續(xù)版本中,又進(jìn)行了改進(jìn))。通過RMI技術(shù),某一個(gè)本地的JVM可以調(diào)用存在于另外一個(gè)JVM中的對象方法,就好像它僅僅是在調(diào)用本地JVM中某個(gè)對象方法一樣。例如RMI客戶端中的如下調(diào)用:
List< UserInfo > users = remoteServiceInterface.queryAllUserinfo();
看似remoteServiceInterface對象和普通的對象沒有區(qū)別,但實(shí)際上remoteServiceInterface對象的具體方法實(shí)現(xiàn)卻不在本地的JVM中,而是在某個(gè)遠(yuǎn)程的JVM中(這個(gè)遠(yuǎn)程的JVM可以是RMI客戶端同屬于一臺(tái)物理機(jī),也可以屬于不同的物理機(jī))
1.1 RMI使用場景
RMI是基于JAVA語言的,也就是說在RMI技術(shù)框架的描述中,只有Server端使用的是JAVA語言并且Client端也是用的JAVA語言,才能使用RMI技術(shù)(目前在codeproject.com中有一個(gè)開源項(xiàng)目名字叫做“RMI for C++”,可以實(shí)現(xiàn)JAVA To C++的RMI調(diào)用。但是這是一個(gè)第三方的實(shí)現(xiàn),并不是java的標(biāo)準(zhǔn)RMI框架定義,所以并不在我們的討論范圍中)。
RMI適用于兩個(gè)系統(tǒng)都主要使用JAVA語言進(jìn)行構(gòu)造,不需要考慮跨語言支持的情況。并且對兩個(gè)JAVA系統(tǒng)的通訊速度有要求的情況。
RMI 是一個(gè)良好的、特殊的RPC實(shí)現(xiàn):使用JRMP協(xié)議承載數(shù)據(jù)描述,可以使用BIO和NIO兩種IO通信模型。
1.2 RMI框架的基本組成
雖然RMI早在JDK.1.1版本中就開放了。但是在JDK1.5的版本中RMI又進(jìn)行改進(jìn)。所以我們后續(xù)的代碼示例和原理講解都基于最新的RMI框架特性。
要定義和使用一套基于RMI框架工作的系統(tǒng),您至少需要做一下幾個(gè)工作:
下圖描述了上述幾個(gè)概念名稱間的關(guān)系,呈現(xiàn)了JDK.5中RMI框架其中一種運(yùn)行方式(注意,是其中一種工作方式。也就是說RMI框架不一定都是這種運(yùn)行方式,后文中我們還將描述另外一種RMI的工作方式):
1.3 RMI示例代碼
在這個(gè)代碼中,我們將使用“本地RMI注冊表”(LocateRegistry),讓RMI服務(wù)的具體提供者和RMI注冊表工作在同一個(gè)JVM上,向您介紹最基本的RMI服務(wù)的定義、編寫、注冊和調(diào)用過程:
首先我們必須定義RMI 服務(wù)接口,代碼如下:
package testRMI;import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List;import testRMI.entity.UserInfo;public interface RemoteServiceInterface extends Remote {/*** 這個(gè)RMI接口負(fù)責(zé)查詢目前已經(jīng)注冊的所有用戶信息*/public List<UserInfo> queryAllUserinfo() throws RemoteException; }很簡單的代碼,應(yīng)該不用多解釋什么了。這個(gè)定義的接口方法如果放在某個(gè)業(yè)務(wù)系統(tǒng)A中,您可以理解是查詢這個(gè)系統(tǒng)A中所有可用的用戶資料。注意這個(gè)接口所繼承的java.rmi.Remote接口,是“RMI服務(wù)接口”定義的特點(diǎn)。
那么有接口定義了,自然就要實(shí)現(xiàn)這個(gè)接口:
package testRMI;import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.List;import testRMI.entity.UserInfo;/*** RMI 服務(wù)接口RemoteServiceInterface的具體實(shí)現(xiàn)<br>* 請注意這里繼承的是UnicastRemoteObject父類。* 繼承于這個(gè)父類,表示這個(gè)Remote Object是“存在于本地”的RMI服務(wù)實(shí)現(xiàn)* (這句話后文會(huì)解釋)* @author yinwenjie**/ public class RemoteUnicastServiceImpl extends UnicastRemoteObject implements RemoteServiceInterface {/*** 注意Remote Object沒有默認(rèn)構(gòu)造函數(shù)* @throws RemoteException*/protected RemoteUnicastServiceImpl() throws RemoteException {super();}private static final long serialVersionUID = 6797720945876437472L;/* (non-Javadoc)* @see testRMI.RemoteServiceInterface#queryAllUserinfo()*/@Overridepublic List<UserInfo> queryAllUserinfo() throws RemoteException {List<UserInfo> users = new ArrayList<UserInfo>();UserInfo user1 = new UserInfo();user1.setUserAge(21);user1.setUserDesc("userDesc1");user1.setUserName("userName1");user1.setUserSex(true);users.add(user1);UserInfo user2 = new UserInfo();user2.setUserAge(21);user2.setUserDesc("userDesc2");user2.setUserName("userName2");user2.setUserSex(false);users.add(user2);return users;} }還有我們定義的Userinfo信息,就是一個(gè)普通的POJO對象:
package testRMI.entity;import java.io.Serializable; import java.rmi.RemoteException;public class UserInfo implements Serializable {/*** */private static final long serialVersionUID = -377525163661420263L;private String userName;private String userDesc;private Integer userAge;private Boolean userSex;public UserInfo() throws RemoteException {}/*** @return the userName*/public String getUserName() {return userName;}/*** @param userName the userName to set*/public void setUserName(String userName) {this.userName = userName;}/*** @return the userDesc*/public String getUserDesc() {return userDesc;}/*** @param userDesc the userDesc to set*/public void setUserDesc(String userDesc) {this.userDesc = userDesc;}/*** @return the userAge*/public Integer getUserAge() {return userAge;}/*** @param userAge the userAge to set*/public void setUserAge(Integer userAge) {this.userAge = userAge;}/*** @return the userSex*/public Boolean getUserSex() {return userSex;}/*** @param userSex the userSex to set*/public void setUserSex(Boolean userSex) {this.userSex = userSex;} }RMI Server 的接口定義和RMI Server的實(shí)現(xiàn)都有了,那么編寫代碼的最后一步是**將這個(gè)RMI Server注冊到“RMI 注冊表”中運(yùn)行。這樣 RMI的客戶端就可以調(diào)用這個(gè) RMI Server了。**下面的代碼是將RMI Server注冊到“本地RMI 注冊表”中:
package testRMI;import java.rmi.Naming; import java.rmi.registry.LocateRegistry;public class RemoteUnicastMain {public static void main(String[] args) throws Exception {/** Locate registry,您可以理解成RMI服務(wù)注冊表,或者是RMI服務(wù)位置倉庫。* 主要的作用是維護(hù)一個(gè)“可以正常提供RMI具體服務(wù)的所在位置”。* 每一個(gè)具體的RMI服務(wù)提供者,都會(huì)講自己的Stub注冊到Locate registry中,以表示自己“可以提供服務(wù)”* * 有兩種方式可以管理Locate registry,一種是通過操作系統(tǒng)的命令行啟動(dòng)注冊表;* 另一種是在代碼中使用LocateRegistry類。* * LocateRegistry類中有一個(gè)createRegistry方法,可以在這臺(tái)物理機(jī)上創(chuàng)建一個(gè)“本地RMI注冊表”* */LocateRegistry.createRegistry(1099);// 以下是向LocateRegistry注冊(綁定/重綁定)RMI Server實(shí)現(xiàn)。RemoteUnicastServiceImpl remoteService = new RemoteUnicastServiceImpl();// 通過java 名字服務(wù)技術(shù),可以講具體的RMI Server實(shí)現(xiàn)綁定一個(gè)訪問路徑。注冊到LocateRegistry中Naming.rebind("rmi://127.0.0.1:1099/queryAllUserinfo", remoteService);/** 在“已經(jīng)擁有某個(gè)可訪問的遠(yuǎn)程RMI注冊表”的情況下。* 下面這句代碼就是向遠(yuǎn)程注冊表注冊RMI Server,* 當(dāng)然遠(yuǎn)程RMI注冊表的JVM-classpath中一定要有這個(gè)Server的Stub存在* * (運(yùn)行在另外一個(gè)JVM上的RMI注冊表,可能是同一臺(tái)物理機(jī)也可能不是同一臺(tái)物理機(jī))* Naming.rebind("rmi://192.168.61.1:1099/queryAllUserinfo", remoteService);* */} }這樣我們后續(xù)編寫的Client端就可以調(diào)用這個(gè)RMI Server了。下面的代碼是RMI Client的代碼:
package testRMI;import java.rmi.Naming; import java.util.List;import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator;import testRMI.entity.UserInfo;/*** 客戶端調(diào)用RMI測試* @author yinwenjie**/ public class RemoteClient {static {BasicConfigurator.configure();}/*** 日志*/private static final Log LOGGER = LogFactory.getLog(RemoteClient.class);public static void main(String[] args) throws Exception {// 您看,這里使用的是java名稱服務(wù)技術(shù)進(jìn)行的RMI接口查找。RemoteServiceInterface remoteServiceInterface = (RemoteServiceInterface)Naming.lookup("rmi://192.168.61.1/queryAllUserinfo");List<UserInfo> users = remoteServiceInterface.queryAllUserinfo();RemoteClient.LOGGER.info("users.size() = " +users.size());} }那么怎么來運(yùn)行這段代碼呢?如果您使用的是eclipse編寫了您第一個(gè)RMI Server和RMI Client,并且您使用的是“本地RMI 注冊表”。那么您不需要做任何的配置、腳本指定等工作(包括不需要專門設(shè)置JRE權(quán)限、不需要專門指定classpath、不需要專門生成Stub和Skeleton),就可以看到RMI的運(yùn)行和調(diào)用效果了:
下圖為RemoteUnicastMain的效果RMI 服務(wù)注冊和執(zhí)行效果:
可以看到,RemoteUnicastMain中的代碼執(zhí)行完成后整個(gè)應(yīng)用程序沒有退出。如下圖:
這是因?yàn)檫@個(gè)應(yīng)用程序要承擔(dān)“真實(shí)的RMI Server實(shí)現(xiàn)”的服務(wù)調(diào)用。如果它退出,RMI 注冊表就無法請求真實(shí)的服務(wù)實(shí)現(xiàn)了
我們再來看下圖,RemoteClient調(diào)用RMI 服務(wù)的效果:
很明顯控制臺(tái)將返回:
0 [main] INFO testRMI.RemoteClient - users.size() = 2
3. JAVA RMI 原理
通過上面的兩組代碼,我們大概知道了RMI框架是如何使用的。下面我們來講解一下RMI的基本原理。
3.1 Registry和Stub、Skeleton的關(guān)系
-
一定要說明,在RMI Client實(shí)施正式的RMI調(diào)用前,它必須通過LocateRegistry或者Naming方式到RMI注冊表尋找要調(diào)用的RMI注冊信息。找到RMI事務(wù)注冊信息后,Client會(huì)從RMI注冊表獲取這個(gè)RMI Remote Service的Stub信息。這個(gè)過程成功后,RMI Client才能開始正式的調(diào)用過程。
-
另外要說明的是RMI Client正式調(diào)用過程,也不是由RMI Client直接訪問Remote Service,而是由客戶端獲取的Stub作為RMI Client的代理訪問Remote Service的代理Skeleton,如上圖所示的順序。也就是說真實(shí)的請求調(diào)用是在Stub-Skeleton之間進(jìn)行的。
-
Registry并不參與具體的Stub-Skeleton的調(diào)用過程,只負(fù)責(zé)記錄“哪個(gè)服務(wù)名”使用哪一個(gè)Stub,并在Remote Client詢問它時(shí)將這個(gè)Stub拿給Client
3.2 Remote-Service線程管理
在上文中的演示我們看到了RemoteRegistryUnicastMain處理請求時(shí),使用了線程池。這是JDK1.5到JDK1.6+版本中RMI框架的做的一個(gè)改進(jìn)。包括JDK1.5在內(nèi),之前的版本都采用新建線程的方式來處理請求;在JDK1.6版本之后,改用了線程池,并且線程池的大小是可以調(diào)整的:
-
sun.rmi.transport.tcp.maxConnectionThreads:連接池的大小,默認(rèn)為無限制。無限的大小肯定是有問題,按照Linux單進(jìn)程可打開的最大文件數(shù)限制,建議的設(shè)置值為65535(生產(chǎn)環(huán)境)。如果同一時(shí)間連接池中的線程數(shù)量達(dá)到了最大值,那么后續(xù)的Client請求將會(huì)報(bào)錯(cuò)。測試環(huán)境/開發(fā)環(huán)境是否設(shè)置這個(gè)值,就沒有那么重要了。
-
sun.rmi.transport.tcp.threadKeepAliveTime:如果當(dāng)線程池中有閑置的線程資源的話,那么這個(gè)閑置線程資源多久被注銷(單位毫秒),默認(rèn)的設(shè)置是1分鐘。
如果您使用的是linux或者window的命令控制臺(tái)執(zhí)行的話,您可以通過類似如下語句進(jìn)行參數(shù)設(shè)置:
java -Dsun.rmi.transport.tcp.maxConnectionThreads=2 -Dsun.rmi.transport.tcp.threadKeepAliveTime=1000 testRMI.RemoteRegistryUnicastMain
3.3 Registry和Naming
Registry和Naming都可以進(jìn)行RMI服務(wù)的bind/rebind/unbind,都可以用lookup方法查詢RMI服務(wù)。Naming實(shí)際上是對Registry的封裝。使用完整的URL方式對已注冊的服務(wù)名進(jìn)行查找。
3.4 UnicastRemoteObject和Activatable
在JDK1.2版本中,由Ann Wollrath執(zhí)筆加入了一種新的RMI工作方式。即通過RMI“活化”模式,將Remote Service的真實(shí)提供者移植到RMI Registry注冊表所在的JVM上。要使用這種工作模式的Remote Service實(shí)現(xiàn)不再繼承UnicastRemoteObject類,而需要繼承Activatable類(其他的業(yè)務(wù)代碼不需要改變)
4. RMI:一種特殊的RPC服務(wù)實(shí)現(xiàn)
之所以介紹RMI,是因?yàn)橐ㄟ^介紹RMI引出一種重要的系統(tǒng)間通訊管理框架RPC.
總結(jié)
以上是生活随笔為你收集整理的系统间通信2:通信管理与远程方法调用RMI的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 口语3
- 下一篇: 系统间通信3:RPC的基本概念