Hadoop RPC机制的使用
一、RPC基础概念
1.1 RPC的基础概念
RPC,即Remote Procdure Call,中文名:远程过程调用;
(1)它允许一台计算机程序远程调用另外一台计算机的子程序,而不用去关心底层的网络通信细节,对我们来说是透明的。因此,它经常用于分布式网络通信中。
RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
(2)Hadoop的进程间交互都是通过RPC来进行的,比如Namenode与Datanode直接,Jobtracker与Tasktracker之间等。
因此,可以说:Hadoop的运行就是建立在RPC基础之上的。
1.2 RPC的显著特点
(1)透明性:远程调用其他机器上的程序,对用户来说就像是调用本地方法一样;
(2)高性能:RPC Server能够并发处理多个来自Client的请求;
(3)可控性:jdk中已经提供了一个RPC框架—RMI,但是该PRC框架过于重量级并且可控之处比较少,所以Hadoop RPC实现了自定义的PRC框架。
1.3 RPC的基本流程
(1)RPC采用了C/S的模式;
(2)Client端发送一个带有参数的请求信息到Server;
(3)Server接收到这个请求以后,根据发送过来的参数调用相应的程序,然后把自己计算好的结果发送给Client端;
(4)Client端接收到结果后继续运行;
1.4 Hadoop中的RPC机制
同其他RPC框架一样,Hadoop RPC分为四个部分:
(1)序列化层:Clent与Server端通信传递的信息采用了Hadoop里提供的序列化类或自定义的Writable类型;
(2)函数调用层:Hadoop RPC通过动态代理以及java反射实现函数调用;
(3)网络传输层:Hadoop RPC采用了基于TCP/IP的socket机制;
(4)服务器端框架层:RPC Server利用java NIO以及采用了事件驱动的I/O模型,提高RPC Server的并发处理能力;
Hadoop RPC在整个Hadoop中应用非常广泛,Client、DataNode、NameNode之间的通讯全靠它了。例如:我们平时操作HDFS的时候,使用的是FileSystem类,它的内部有个DFSClient对象,这个对象负责与NameNode打交道。在运行时,DFSClient在本地创建一个NameNode的代理,然后就操作这个代理,这个代理就会通过网络,远程调用到NameNode的方法,也能返回值。
1.5 Hadoop RPC设计技术
(1)动态代理
About:动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实,代理对象对客户隐藏了实际对象。目前Java开发包中提供了对动态代理的支持,但现在只支持对接口的实现。
(2)反射——动态加载类
(3)序列化
(4)非阻塞的异步IO(NIO)
Java NIO原理请参考阅读:http://weixiaolu.iteye.com/blog/1479656
二、如何使用RPC
2.1 Hadoop RPC对外提供的接口
Hadoop RPC对外主要提供了两种接口(见类org.apache.hadoop.ipc.RPC),分别是:
(1)public static <T> ProtocolProxy <T> getProxy/waitForProxy(…)
构造一个客户端代理对象(该对象实现了某个协议),用于向服务器发送RPC请求。
(2)public static Server RPC.Builder (Configuration).build()
为某个协议(实际上是Java接口)实例构造一个服务器对象,用于处理客户端发送的请求。
2.2 使用Hadoop RPC的四大步凑
(1)定义RPC协议
RPC协议是客户端和服务器端之间的通信接口,它定义了服务器端对外提供的服务接口。
(2)实现RPC协议
Hadoop RPC协议通常是一个Java接口,用户需要实现该接口。
(3)构造和启动RPC SERVER
直接使用静态类Builder构造一个RPC Server,并调用函数start()启动该Server。
(4)构造RPC Client并发送请求
使用静态方法getProxy构造客户端代理对象,直接通过代理对象调用远程端的方法。
三、RPC应用实例
3.1 定义RPC协议
如下所示,我们定义一个IProxyProtocol 通信接口,声明了一个Add()方法。
public interface IProxyProtocol extends VersionedProtocol { static final long VERSION = 23234L; //版本号,默认情况下,不同版本号的RPC Client和Server之间不能相互通信 int Add(int number1,int number2); }
需要注意的是:
(1)Hadoop中所有自定义RPC接口都需要继承VersionedProtocol接口,它描述了协议的版本信息。
(2)默认情况下,不同版本号的RPC Client和Server之间不能相互通信,因此客户端和服务端通过版本号标识。
3.2 实现RPC协议
Hadoop RPC协议通常是一个Java接口,用户需要实现该接口。对IProxyProtocol接口进行简单的实现如下所示:
public class MyProxy implements IProxyProtocol { public int Add(int number1,int number2) { System.out.println("我被调用了!"); int result = number1+number2; return result; } public long getProtocolVersion(String protocol, long clientVersion) throws IOException { System.out.println("MyProxy.ProtocolVersion=" + IProxyProtocol.VERSION); // 注意:这里返回的版本号与客户端提供的版本号需保持一致 return IProxyProtocol.VERSION; } }
这里实现的Add方法很简单,就是一个加法操作。为了查看效果,这里通过控制台输出一句:“我被调用了!”
3.3 构造RPC Server并启动服务
这里通过RPC的静态方法getServer来获得Server对象,如下代码所示:
public class MyServer { public static int PORT = 5432; public static String IPAddress = "127.0.0.1"; public static void main(String[] args) throws Exception { MyProxy proxy = new MyProxy(); final Server server = RPC.getServer(proxy, IPAddress, PORT, new Configuration()); server.start(); } }
这段代码的核心在于第5行的RPC.getServer方法,该方法有四个参数,第一个参数是被调用的java对象,第二个参数是服务器的地址,第三个参数是服务器的端口 。获得服务器对象后,启动服务器。这样,服务器就在指定端口监听客户端的请求。到此为止,服务器就处于监听状态,不停地等待客户端请求到达。
3.4 构造RPC Client并发出请求
这里使用静态方法getProxy或waitForProxy构造客户端代理对象,直接通过代理对象调用远程端的方法,具体如下所示:
public class MyClient { public static void main(String[] args) { InetSocketAddress inetSocketAddress = new InetSocketAddress( MyServer.IPAddress, MyServer.PORT); try { // 注意:这里传入的版本号需要与代理保持一致 IProxyProtocol proxy = (IProxyProtocol) RPC.waitForProxy( IProxyProtocol.class, IProxyProtocol.VERSION, inetSocketAddress, new Configuration()); int result = proxy.Add(10, 25); System.out.println("10+25=" + result); RPC.stopProxy(proxy); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
以上代码中核心在于RPC.waitForProxy(),该方法有四个参数,第一个参数是被调用的接口类,第二个是客户端版本号,第三个是服务端地址。返回的代理对象,就是服务端对象的代理,内部就是使用java.lang.Proxy实现的。
经过以上四步,我们便利用Hadoop RPC搭建了一个非常高效的客户机–服务器网络模型。
3.5 查看运行结果
(1)启动服务端,开始监听客户端请求
(2)启动客户端,开始向服务端发请求
(3)查看服务端状态,是否被调用
SUMMARY:从上面的RPC调用中,可以看出:在客户端调用的业务类的方法是定义在业务类的接口中的。该接口实现了VersionedProtocal接口。
(4)现在我们在命令行执行jps命令,查看输出信息,会出现如下图所示的:
从上图中可以看到一个java进程,是“MyServer”,该进程正是我们刚刚运行的RPC的服务端类MyServer。因此,大家可以联想到我们搭建Hadoop环境时,也执行过该命令用来判断Hadoop的相关进程是否全部启动。
SUMMARY:那么可以判断,Hadoop启动时产生的5个java进程也应该是RPC的服务端。
下面我们观察NameNode的源代码,如下图所示,可以看到NameNode确实创建了RPC的服务端。
private void initialize(Configuration conf) throws IOException { ...... // create rpc server InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf); if (dnSocketAddr != null) { int serviceHandlerCount = conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY, DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT); this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(), dnSocketAddr.getPort(), serviceHandlerCount, false, conf, namesystem.getDelegationTokenSecretManager()); this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress(); setRpcServiceServerAddress(conf); } this.server = RPC.getServer(this, socAddr.getHostName(), socAddr.getPort(), handlerCount, false, conf, namesystem .getDelegationTokenSecretManager()); ...... }