Hadoop RPC 遠程過程調用是Hadoop中的核心概念。在深入研究RPC之前,先看看遠程調用的鼻祖Java RMI.
1.什麼是RMI
Java遠程方法調用,即Java RMI(Java Remote Method Invocation)是Java編程語言裏,一種用於實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分佈操作。RMI全部的宗旨就是儘可能簡化遠程接口對象的使用。
Java RMI極大地依賴於接口。在需要創建一個遠程對象的時候,程序員通過傳遞一個接口來隱藏底層的實現細節。客戶端得到的遠程對象句柄正好與本地的根代碼連接,由後者負責透過網絡通信。這樣一來,程序員只需關心如何通過自己的接口句柄發送消息。
接口的兩種常見實現方式是:
(1)最初使用JRMP(Java Remote Message Protocol,Java遠程消息交換協議)實現;
(2)此外還可以用與CORBA兼容的方法實現。
RMI一般指的是編程接口,也有時候同時包括JRMP和API(應用程序編程接口),而RMI-IIOP則一般指RMI接口接管絕大部分的功能,以支持CORBA的實現。最初的RMI API設計爲通用地支持不同形式的接口實現。後來,CORBA增加了傳值(pass by value)功能,以實現RMI接口。然而RMI-IIOP和JRMP實現的接口並不完全一致。
2.RMI實例
RMI相關的類和接口都在jdk
java.rmi
包中。
RMI的基礎是接口,RMI構架基於一個重要的原理:定義接口和定義接口的具體實現是分開的。
下面我們通過具體的例子,建立一個簡單的遠程計算服務和使用它的客戶程序
一個正常工作的RMI系統由下面幾個部分組成:
遠程服務的接口定義
遠程服務接口的具體實現
Stub 和 Skeleton 文件
一個運行遠程服務的服務器
一個RMI命名服務,它允許客戶端去發現這個遠程服務
類文件的提供者(一個HTTP或者FTP服務器)
一個需要這個遠程服務的客戶端程序
下面我們一步一步建立一個簡單的RMI系統。首先在你的機器裏建立一個新的文件夾,以便放置我們創建的文件,爲了簡單起見,我們只使用一個文件夾存放客戶端和服務端代碼,並且在同一個目錄下運行服務端和客戶端。如果所有的RMI文件都已經設計好了,那麼你需要下面的幾個步驟去生成你的系統:
1、 編寫並且編譯接口的Java代碼
2、 編寫並且編譯接口實現的Java代碼
3、 從接口實現類中生成 Stub 和 Skeleton 類文件
4、 編寫遠程服務的主運行程序
5、 編寫RMI的客戶端程序
6、 安裝並且運行RMI系統
2.1 接口
第一步就是建立和編譯服務接口的Java代碼。這個接口定義了所有的提供遠程服務的功能,下面是源程序:
/**
*
*/
package com.renren;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @author root
*
*/
public interface CalculatorRemoteInterface extends Remote {
//! 注意,這個接口繼承自Remote,每一個定義的方法都必須拋出一個RemoteException異常對象
public abstract int add(int a, int b) throws RemoteException;
}
注意,這個接口繼承自Remote,每一個定義的方法都必須拋出一個RemoteException異常對象
2.2 實現類
package com.renren;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
@SuppressWarnings("serial")
// 繼承 UnicastRemoteObject 遠程對象 這個一定要寫 否則 服務器啓動報異常
public class CalculatorRemoteObject extends UnicastRemoteObject implements
CalculatorRemoteInterface {
public CalculatorRemoteObject() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
/**
*
*/
private static final long serialVersionUID = 1L;
// @Override
public int add(int a, int b) {
// TODO Auto-generated method stub
return a + b;
}
}
2.3 打包
將上述兩個java文件編譯打包爲rmi-test.jar
在編寫server和client程序時需要依賴該jar包
2.4 Server
Server做的事很簡單,代碼註釋很詳細...
package com.test;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class Server {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
// 定義遠程接口CalculatorRemoteInterface對象 用於綁定在服務器註冊表上 該接口由CalculatorRemoteObject類實現
CalculatorRemoteInterface remoteObject = new CalculatorRemoteObject();
// 定義一個端口號
int port = 9999;
// 創建一個接受對特定端口調用的遠程對象註冊表 註冊表上需要接口一個指定的端口號
LocateRegistry.createRegistry(port);
// 定義服務器遠程地址URL格式
String serverAddress = "rmi://10.2.185.197:" + port+"/calculator" ;
// 綁定遠程地址和接口對象
Naming.bind(serverAddress, remoteObject);
// 如果啓動成功 則彈出如下信息
System.out.println(">>>服務器啓動成功");
System.out.println(">>>請啓動客戶端進行連接訪問");
} catch (MalformedURLException e) {
System.out.println("地址出現錯誤!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.out.println("重複綁定了同一個遠程對象!");
e.printStackTrace();
} catch (RemoteException e) {
System.out.println("創建遠程對象出現錯誤!");
e.printStackTrace();
}
}
}
2.5 Client
package com.test;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
CalculatorRemoteInterface calculator=
(CalculatorRemoteInterface) Naming.lookup("rmi://10.2.185.197:9999/calculator");
System.out.println("5+3="+calculator.add(5, 3));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NotBoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.6 測試
1.在一臺機器上啓動Server
2.在兩外一臺機器上啓動Client,可以看到Client成功調用了Server的CalculatorRemoteObject對象的add方法.
2.7 名詞解釋 Stub Skeleton
http://blog.sina.com.cn/s/blog_4ab0d57401009n4a.html
Stub(存根)和Skeleton(骨架)
Stub和Skeleton是經過rmic命令生成的,我們的程序要通過遠程調用,底層一定是套接字的字節傳輸,要一個對象序列化成爲一個字節數組,傳輸到服務器或者客戶端的對端之後,再把該對象反序列化成爲對應的對象,這些網絡傳輸的過程要求安全,穩定等等非常麻煩的操作,Stub駐留客戶端,承擔着代理遠程對象的實現者的角色,Skeleton類幫助遠程對象與Stub再RMI連接上進行通信。RMI的客戶與Stub進行交換,Stub與Skeleton通信,Skeketon負責與服務器進行交互,因此有了Stub和Skeleton之後我們就不需要實現底層通信的細節,我們進行的遠程調用,只需要通過接口對方法進行操作即可,使分佈式調用實現了位置上的透明,即:遠程調用就像本地調用一樣。
通俗的說,RMI的代理模式迫使方法調用必須通過充當替身的代理對象,即Stub和Skeleton,由這些代理對象將方法傳遞給實際的對象。
在遠程虛擬機中,每個遠程對象都有一個Skeleton對應,Skeleton負責將調用分配給實際的遠程對象來實現,他的工作是在服務器端的。在JDK1.2版本以上的環境中不需要Skeleton,因爲Jdk1.2推出了附加的Skeleton,並且對底層的協議也進行了修改,性能上有很大提升,默認的rmic命令是不會產生Skeleton類的,如果要想生成Skeleton類可以使用rmic-v1.1 即可。
Stub的操作任務:
a、初始化與包含遠程對象遠程虛擬機的連接
b、與遠程虛擬機參數進行編組,也就是調度,包括參數的寫入以及傳輸
c、等待方法的調用結果
d、解編返回值和返回的異常。也就是讀取服務器上的返回值,也稱爲反調度
e、將結果返回給調用程序。
Skeleton對應的任務:
a、讀取遠程方法的參數,也就是解編
b、調用實際遠程對象上的方法
c、將結果或者異常組編(寫入並傳輸)給調用程序。
Stub與Skeleton的關係以及操作是對應的關係。只要我們有編譯好的遠程對象的類,就可以調用jdk的rmic命令來生成stub和skeleton了,關鍵是我們需要理解Stub是客戶端應用程序的代理,Skeleton是服務器端應用程序的代理,他們之間協作完成客戶端與服務器之間的方法調用時的通信。
瞭解者兩個概念之後,我們接下來需要了解JNDI以及RMI註冊表兩個概念,等剩下的兩個概念瞭解了,我們基本上對RMI就可以入門了。
參考:
http://blog.csdn.net/wangxingbao4227/article/details/6842951
http://blog.csdn.net/tin591/article/details/8117198
http://hi.baidu.com/dl_linfeng/item/330036304422c65c80f1a778