hadoop RPC 技术架构和demo实例
一,Hadoop RPC 概述
相信每个在学习分布式应用时候,都会好奇节点之间的通信是怎样的。节点之间的交互在整个Hadoop生态系统中扮演非常通用和基础的角色,包括HDFS、MapReduce、HBase以及YARN内部各组件之间的RPC交互几乎都依赖大致相同的IPC框架实现,比如心跳机制,NN对于DN的管理指令, Client 和 DN节点的数据流传输等等。
分析Hadoop的IPC实现原理对理解整个Hadoop生态系统内各系统都会有极大的帮助。
RPC具有以下特点:
- 透明性。所有 RPC 框架的基本特性,对用户屏蔽了网络通信过程。
- 高性能。Hadoop 各个子系统均采用 Master/Slave 架构,Master 作为一个 RPC Server,负责处理所有 Slave 发送的请求,需要能够高效的处理多个并发 RPC 请求。
- 可控性。JDK 自带的 RPC 框架(RMI)过于重量级,用户可控之处太少,如:网络连接、超时和缓存等难以修改。因此 Hadoop 实现了轻量级的可控性更强的 RPC 框架。
二, 具体的调用实现
既然说到了Hadoop集群内的RPC,具体的调用有哪些呢? RPC 的具体调用的功能,都是ProtocalVersioned 接口的实现类,类中的方法实现了调用逻辑。Hadoop有多个模块,这里拿HDFS 文件系统中几个协议举例:
1, ClientProtoclo 定义客户端与namenode节点间的接口,用于客户端对文件系统的所有操作,读写都需要与该接口交互。包括hdfs文件读写的相关操作,hdfs命名空间,快照,缓存相关的操作。
2,ClientDatanodeProtocol 主要用户客户端获取datanode节点信息
3,DatanodeProtocol datanode与namenode间的通信接口,包括namenode通过该接口中的方法返回向datanode下发指令。datanode则是通过该接口向namenode进行注册,汇报块信息和缓存信息。比如调用blockReport()汇报datanode上存储的所有数据块信息,最后调用cacheReport()汇报datanode缓存的所有数据块。
三,技术架构
通俗的语句概括就是: A进程调用远程机器 B进程里的类方法,并返回结果。A进程把 需要调用的 “类的方法名和参数 ” 放入一个对象(CALL对象),并序列化,通过TCP协议传输到其他节点的进程中,服务器接收后,反序列化为Java对象,通过反射,调用服务器端的该类的方法。返回值然后返回给A进程。
其中使用的底层技术包括:
1,序列化,反序列化过程模型 : Hadoop内部自己的序列化框架
2,TCP连接通信模型,( 数据网络IO模型 Socket -> IO模型升级Nio ) TCP协议
3,Java动态代理 和反射 功能是在运行状态中,获取类的方法功能。
4,Reactor线程模型 该模型处理大量远程 IO(RPC)任务的读取,保存和处理过程,实现高并发 。通俗的说,在SERVER端 ,有多个接待员 (Receiver)和多多个服务员Handler 。 服务端(ipc.Server)基于Reactor设计模式,作为自己的高并发解决方案。
5,Java NIO : 多路复用IO模型 (NIO 是 网络IO任务的高并发解决方案; 服务器端Reactor模型是请求达到Server后,处理“请求”的高并发方案)
( 以上RPC的底层依赖技术,本文不详细讲解,主要理解RPC上层实现过程)
图一:组件图
图二:总体运行图
三,应用层代码实例
这里直接使用Hadoop-common 2.7.0包,开发一个使用Hadoop RPC功能入门实例。
引入包: 只要一个
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.0</version> </dependency>
1,自定义的协议接口
public interface ClientProtocol extends VersionedProtocol { //建立自己的协议接口,定义RPC行为 public static final long versionID=1111L; String echo(String value); }
2,协议接口实现类
package org.example; import org.apache.hadoop.ipc.ProtocolSignature; import java.io.IOException; public class ClientProtocolImpl implements ClientProtocol { @Override public long getProtocolVersion(String arg0, long arg1) throws IOException { // TODO Auto-generated method stub return ClientProtocol.versionID; } @Override public ProtocolSignature getProtocolSignature(String arg0, long arg1, int arg2) throws IOException { return new ProtocolSignature(ClientProtocol.versionID,null); } @Override public String echo(String value) { //实现自定义的协议接口,具体化行为方法
return "hello "+value; }
}
3, 客户端
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ipc.RPC; import java.io.IOException; import java.net.InetSocketAddress; public class Client { public static void main(String[] args) throws IOException { ClientProtocol proxy = (ClientProtocol) RPC.getProxy( //建立proxy 代理对象,包含协议实现类。 ClientProtocol.class, ClientProtocol.versionID ,new InetSocketAddress("127.0.0.1",8787),new Configuration()); String result = proxy.echo("sam"); // 代理类执行远程调用,并返回值。 System.out.println(result); } }
4,服务端
import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ipc.RPC; import org.apache.hadoop.ipc.Server; import java.io.IOException; public class MyRpcServer { public static void main(String[] args) throws HadoopIllegalArgumentException, IOException { Server server =new RPC.Builder(new Configuration()).setProtocol(ClientProtocol.class) .setInstance(new ClientProtocolImpl()).setBindAddress("127.0.0.1").setPort(8787) .setNumHandlers(5).build(); //建立服务器,绑定ip和端口。设置客户端协议接口和实现类 server.start(); } }
5,执行结果如下:
先开启Server, 在开启Client进程,发送请求。 返回echo()方法调用结果。