Hadoop遠程過程調用
遠程過程調用(RPC)
作爲分佈式系統,Hadoop中各個實體間存在着大量的交互,遠程過程調用(Remote Procedure Call,RPC)讓用戶可以像調用本地方法一樣調用另外一個應用程序提供的服務,而不必設計和開發相關的信息發送、處理和接收等具體代碼,它提高了程序的互操作性。
簡要來說,RPC就是允許程序調用位於其他機器上的過程(也可以是同一臺機器的不同進程)。但機器A上的進程調用機器B上的進程時,A上的進程被掛起,而B上的被調用的進程開始執行。調用方使用參數將信息傳送給被調用方。然後通過傳回的結果得到信息。在這個過程中A是PRC客戶,B是RPC服務器。同時,編程人員看不到任何消息的傳遞,這個過程對用戶來說是透明的。其行爲如同一個過程到另一個過程的調用一樣。
RPC引入客戶存根(Client Stub)和服務器骨架(Server Skeleton)來解決系統在有差異的情況下進行參數和結果的傳遞,並對通信雙方的狀態進行監控。
Java遠程方法調用(RMI)
在某種程度上來看,RMI可以看成是RPC的Java升級版。和RPC一樣,包含RMI的Java應用程序通常包括服務器程序和客戶端程序。它提供了和PRC中類似的
標準的Stub/Skeleton機制。Stub代表可以被客戶端引用的遠程對象,位於客戶端,並保持着遠程對象的接口和方法列表。客戶端應用調用遠程對象時,Stub將調用請求,通過RMI的基礎結構轉發到遠程對象上。接受到調用請求時,服務器端的Skeleton對象處理相關調用“遠方”對象中的所有細節並調用Skeleton對象。
Java遠程方法調用依賴於Java對象的序列化機制,他將調用的參數和返回值序列化並在網絡中傳遞。
Hadoop遠程過程調用
Hadoop遠程過程調用實現使用Java動態代理和新輸入/輸出系統(NIO),Hadoop沒有使用前面的Java RMI,而是實現了一套自己獨有的節點間通信機制,理由和Hadoop使用Writable形式的序列化機制類似,有效的IPC(Inter-Process Communication,進程間通信)對於Hadoop來說是至關重要的,Hadoop需要精確控制進程間通信中比如連接、超時、緩存等通信細節,顯然Java RMI達不到這些需求,Hadoop進程間通信機制,結合數據輸出流(DataOutputStram)和數據輸入流(DataInputStram)的Writable序列化機制,以及一個簡潔的、低消耗的遠程過程調用機制。
Java RMI的開發從遠程接口的定義開始,遠程接口必須繼承java.rmi.Remote;在Hadoop遠程過程調用中,也是通過一個IPC接口開始進行開發。Hadoop IPC接口必須繼承自org.apche.hadoop.ipc.VersionedProtocol接口,代碼如下:
public interface VersionedProtocol {
/**
* Return protocol version corresponding to protocol interface.
* @param protocol The classname of the protocol interface
* @param clientVersion The version of the protocol that the client speaks
* @return the version that the server will speak
*/
public long getProtocolVersion(String protocol,
long clientVersion) throws IOException;
}
在Hadoop中,這個接口不是一個聲明性接口,實現該接口對應的接口都必須實現這個方法。它有兩個參數,分別是協議對應的接口名字和客戶端期望的協議的版本號。方法則返回服務器端的接口實現的版本號。在建立IPC時,getProtocolVersion()方法用戶檢查通信的雙方,保證他們使用了相同版本的接口。下面貼上一段利用Hadoop IPC接口實現自己的IPC應用。
//需要序列化的類
public class IPCFileStatus implements Writable {
private String filename;
private long time;
static { // register IPCFileStatus
WritableFactories.setFactory
( IPCFileStatus.class,
new WritableFactory() {
public Writable newInstance() { return new IPCFileStatus(); } } );
}
public IPCFileStatus() {
}
public IPCFileStatus(String filename) {
this.filename=filename;
this.time=(new Date()).getTime();
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String toString() {
return "File: "+filename+" Create at "+(new Date(time));
}
@Override
public void readFields(DataInput in) throws IOException {
this.filename = Text.readString(in);
this.time = in.readLong();
}
@Override
public void write(DataOutput out) throws IOException {
Text.writeString(out, filename);
out.writeLong(time);
}
}
//接口
public interface IPCQueryStatus extends VersionedProtocol {
IPCFileStatus getFileStatus(String filename);
}
//實現類
public class IPCQueryStatusImpl implements IPCQueryStatus {
protected IPCQueryStatusImpl() {
}
@Override
public IPCFileStatus getFileStatus(String filename) {
IPCFileStatus status=new IPCFileStatus(filename);
System.out.println("Method getFileStatus Called, return: "+status);
return status;
}
@Override
public long getProtocolVersion(String protocol, long clientVersion) throws IOException {
System.out.println("protocol: "+protocol);
System.out.println("clientVersion: "+clientVersion);
return IPCQueryServer.IPC_VER;
}
}
//服務器端
public class IPCQueryServer {
public static final int IPC_PORT = 32121;
public static final long IPC_VER = 5473L;
public static void main(String[] args) {
try {
ConsoleAppender append=new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN));
append.setThreshold(Level.DEBUG);
BasicConfigurator.configure();
IPCQueryStatusImpl queryService=new IPCQueryStatusImpl();
Server server = RPC.getServer(queryService,
"0.0.0.0", IPC_PORT,
1, true,
new Configuration());
server.start();
System.out.println("Server ready, press any key to stop");
System.in.read();
server.stop();
System.out.println("Server stopped");
} catch (Exception e) {
e.printStackTrace();
}
}
}
//客戶端
public class IPCQueryClient {
public static void main(String[] args) {
try {
System.out.println("Interface name: "+IPCQueryStatus.class.getName());
System.out.println("Interface name: "+IPCQueryStatus.class.getMethod("getFileStatus", String.class).getName());
InetSocketAddress addr=new InetSocketAddress("localhost", IPCQueryServer.IPC_PORT);
IPCQueryStatus query=(IPCQueryStatus) RPC.getProxy(IPCQueryStatus.class,
IPCQueryServer.IPC_VER,
addr,
new Configuration());
IPCFileStatus status=query.getFileStatus("/tmp/testIPC");
System.out.println(status);
RPC.stopProxy(query);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Hadoop IPC的代碼結構
Hadoop中與IPC相關的代碼都在org.apache.hadoop.ipc包中,類簡介如下:
RemoteExecption:遠程異常,應用於IPC客戶端,表示遠程過程調用中的錯誤。
Status:枚舉類,定義了遠程過程調用的返回結果,包括SUCCESS、ERROR、FATAL等情況。
VersionedProtocol接口:前面已經介紹過,Hadoop IPC的遠程接口都擴展自VersionedProtocol.
ConnectionHeader:IPC客戶端與服務器端建立連接時發送的消息頭。
Client:包含了與IPC客戶端相關的代碼。它的內部類包括:Client.Connection、Client.ConnectionId和Client.Call、Client、ParallelCall等類
Server:包含了與IPC服務端相關的代碼。它的內部類包括:Server.Connection與Server.Call。【Listener、Handler、Responder】這三個類是對遠程調用的處理它們都繼承自java.lang.Thread類,在各自的線程中運行。
RPC類:它在Client和Server類的基礎上面實現了Hadoop IPC的功能。
版權申明:本文部分摘自【蔡斌、陳湘萍】所著【Hadoop技術內幕 深入解析Hadoop Common和HDFS架構設計與實現原理】一書,僅作爲學習筆記,用於技術交流,其商業版權由原作者保留,推薦大家購買圖書研究,轉載請保留原作者,謝謝!