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架构设计与实现原理】一书,仅作为学习笔记,用于技术交流,其商业版权由原作者保留,推荐大家购买图书研究,转载请保留原作者,谢谢!