概述
在以前的博客中,我简单的介绍了一下hadoop rpc框架的实现流程(http://blog.youkuaiyun.com/zhangjun5965/article/details/59653549),这一小节主要介绍一下在hdfs中,是如何运用这个rpc框架进行通讯的。
首先回顾一下使用hadoop rpc的主要流程
- 首先定义client和server交互的接口(如client和namenode的ClientProtocol)
- 服务器端实现接口(如namenode提供服务的NameNodeRpcServer类)
- 服务器端启动服务。(通过RPC.Builder(conf).build())
- 客户端获取接口的代理,执行相关的方法。
下面我们简单讲解一下rpc框架在hadoop的具体使用
namenode提供服务
namenode用于对外提供rpc服务的是NameNodeRpcServe类,这个类实现了NamenodeProtocols接口,NamenodeProtocols定义了NamenodeProtocols需要实现的所有的接口。
比如客户端和namenode交互的ClientProtocol、datanode和namenode交互的DatanodeProtocol,用于提供ha服务的HAServiceProtocol等。
/** The full set of RPC methods implemented by the Namenode. */
@InterfaceAudience.Private
public interface NamenodeProtocols
extends ClientProtocol,
DatanodeProtocol,
NamenodeProtocol,
RefreshAuthorizationPolicyProtocol,
RefreshUserMappingsProtocol,
RefreshCallQueueProtocol,
GenericRefreshProtocol,
GetUserMappingsProtocol,
HAServiceProtocol,
TraceAdminProtocol {
}
此外,我们在这个类中看到他的内部定义了两个rpc服务,一个是针对客户端的,一个是针对namenode的
/** The RPC server that listens to requests from DataNodes */
private final RPC.Server serviceRpcServer;
private final InetSocketAddress serviceRPCAddress;
/** The RPC server that listens to requests from clients */
protected final RPC.Server clientRpcServer;
protected final InetSocketAddress clientRpcAddress;
在构造方法中,实例化了这两个变量
this.serviceRpcServer = new RPC.Builder(conf)
.setProtocol(
org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
.setInstance(clientNNPbService)
.setBindAddress(bindHost)
.setPort(serviceRpcAddr.getPort()).setNumHandlers(serviceHandlerCount)
.setVerbose(false)
.setSecretManager(namesystem.getDelegationTokenSecretManager())
.build();
............................
clientNNPbService).setBindAddress(bindHost)
.setPort(rpcAddr.getPort()).setNumHandlers(handlerCount)
.setVerbose(false)
.setSecretManager(namesystem.getDelegationTokenSecretManager()).build();
在start方法中启动了这两个服务。根据代码,发现是在namenode和BackupNode在启动的时候调用了start方法,也就是说这两个服务是在namenode启动的时候初始化的。
客户端获取代理
不管是创建文件,还是删除,都是先通过FileSystem.get(conf)来首先获取具体的的文件系统,FileSystem会根据传入的conf来进行适配,比如hdfs://开头的就实例化DistributedFileSystem,最终通过FileSystem.createFileSystem(URI, Configuration)来初始化具体的文件系统。
private static FileSystem createFileSystem(URI uri, Configuration conf
) throws IOException {
Class<?> clazz = getFileSystemClass(uri.getScheme(), conf);
FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf);
fs.initialize(uri, conf);
return fs;
}
对于DistributedFileSystem来说, fs.initialize(uri, conf);当然就是调用了
DistributedFileSystem.initialize(URI, Configuration)方法,在这里构造了一个DFSClient对象,客户端所有的操作都是通过它来完成的。
@Override
public void initialize(URI uri, Configuration conf) throws IOException {
super.initialize(uri, conf);
setConf(conf);
String host = uri.getHost();
if (host == null) {
throw new IOException("Incomplete HDFS URI, no host: "+ uri);
}
homeDirPrefix = conf.get(
DFSConfigKeys.DFS_USER_HOME_DIR_PREFIX_KEY,
DFSConfigKeys.DFS_USER_HOME_DIR_PREFIX_DEFAULT);
this.dfs = new DFSClient(uri, conf, statistics);
this.uri = URI.create(uri.getScheme()+"://"+uri.getAuthority());
this.workingDir = getHomeDirectory();
}
在DFSClient的构造方法里,获取了namenode的代理对象,用于和namenode进行交互。
Preconditions.checkArgument(nameNodeUri != null,
"null URI");
proxyInfo = NameNodeProxies.createProxy(conf, nameNodeUri,
ClientProtocol.class, nnFallbackToSimpleAuth);
this.dtService = proxyInfo.getDelegationTokenService();
this.namenode = proxyInfo.getProxy();
在createProxy方法里,通过查询配置文件获取是否配置了HA,来分别获取不同的代理,为了简单理解,我们现在只说一下非HA的情况。非HA的情况是调用了NameNodeProxies.createNonHAProxy(Configuration, InetSocketAddress, Class, UserGroupInformation, boolean, AtomicBoolean)来获取相应的代理。
在这,主要是针对不同的协议来分别获取不同的代理,
public static <T> ProxyAndInfo<T> createNonHAProxy(
Configuration conf, InetSocketAddress nnAddr, Class<T> xface,
UserGroupInformation ugi, boolean withRetries,
AtomicBoolean fallbackToSimpleAuth) throws IOException {
Text dtService = SecurityUtil.buildTokenService(nnAddr);
T proxy;
if (xface == ClientProtocol.class) {
proxy = (T) createNNProxyWithClientProtocol(nnAddr, conf, ugi,
withRetries, fallbackToSimpleAuth);
} else if (xface == JournalProtocol.class) {
proxy = (T) createNNProxyWithJournalProtocol(nnAddr, conf, ugi);
} else if (xface == NamenodeProtocol.class) {
proxy = (T) createNNProxyWithNamenodeProtocol(nnAddr, conf, ugi,
withRetries);
} else if (xface == GetUserMappingsProtocol.class) {
proxy = (T) createNNProxyWithGetUserMappingsProtocol(nnAddr, conf, ugi);
} else if (xface == RefreshUserMappingsProtocol.class) {
proxy = (T) createNNProxyWithRefreshUserMappingsProtocol(nnAddr, conf, ugi);
} else if (xface == RefreshAuthorizationPolicyProtocol.class) {
proxy = (T) createNNProxyWithRefreshAuthorizationPolicyProtocol(nnAddr,
conf, ugi);
} else if (xface == RefreshCallQueueProtocol.class) {
proxy = (T) createNNProxyWithRefreshCallQueueProtocol(nnAddr, conf, ugi);
} else {
String message = "Unsupported protocol found when creating the proxy " +
"connection to NameNode: " +
((xface != null) ? xface.getClass().getName() : "null");
LOG.error(message);
throw new IllegalStateException(message);
}
return new ProxyAndInfo<T>(proxy, dtService, nnAddr);
}
对于ClientProtocol接口来说,通过createNNProxyWithClientProtocol方法来获取,进入这个方法。
private static ClientProtocol createNNProxyWithClientProtocol(
InetSocketAddress address, Configuration conf, UserGroupInformation ugi,
boolean withRetries, AtomicBoolean fallbackToSimpleAuth)
throws IOException {
RPC.setProtocolEngine(conf, ClientNamenodeProtocolPB.class, ProtobufRpcEngine.class);
final RetryPolicy defaultPolicy =
RetryUtils.getDefaultRetryPolicy(
conf,
DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_KEY,
DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_DEFAULT,
DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_KEY,
DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_DEFAULT,
SafeModeException.class);
final long version = RPC.getProtocolVersion(ClientNamenodeProtocolPB.class);
//首先获取了一个ClientNamenodeProtocolPB代理。
ClientNamenodeProtocolPB proxy = RPC.getProtocolProxy(
ClientNamenodeProtocolPB.class, version, address, ugi, conf,
NetUtils.getDefaultSocketFactory(conf),
org.apache.hadoop.ipc.Client.getTimeout(conf), defaultPolicy,
fallbackToSimpleAuth).getProxy();
//是否有重试机制
if (withRetries) { // create the proxy with retries
Map<String, RetryPolicy> methodNameToPolicyMap
= new HashMap<String, RetryPolicy>();
ClientProtocol translatorProxy =
new ClientNamenodeProtocolTranslatorPB(proxy);
return (ClientProtocol) RetryProxy.create(
ClientProtocol.class,
new DefaultFailoverProxyProvider<ClientProtocol>(
ClientProtocol.class, translatorProxy),
methodNameToPolicyMap,
defaultPolicy);
} else {
return new ClientNamenodeProtocolTranslatorPB(proxy);
}
}
我们看到最后都是通过传入ClientNamenodeProtocolPB类型的参数proxy构造了一个ClientNamenodeProtocolTranslatorPB类型的接口代理。
client和namenode交互应该是ClientProtocol,可是为什么要中间再来一个ClientNamenodeProtocolPB接口呢,这就是下面我们要讲的东西了
客户端具体的发送数据流程
ClientNamenodeProtocolPB序列化相应的方法
client和namenode交互使用的是ClientProtocol接口,但是这个接口里面的方法的参数是java的类型,是无法在网络上传输的,所以需要进行序列化操作。所以就有了在客户端序列化操作的ClientNamenodeProtocolPB接口,ClientNamenodeProtocolPB采用了适配器模式对ClientProtocol对应的方法进行了一一的适配,将方法转换成可以在网络上传输的序列化之后的格式。
我们以delete方法为例,当调用了ClientProtocol的方法进行删除文件操作的时候,是先进入了oClientNamenodeProtocolTranslatorPB.delete(String, boolean)方法,用传进来的参数构造了一个DeleteRequestProto用于网络传输的对象。然后通过ClientNamenodeProtocolPB的delete方法来执行相应的操作。
发送序列化之后的数据
然后会在ClientNamenodeProtocolPB相应的代理的invoke方法里,通过client的call方法往服务器发送数据。
服务端反序列化
同样在服务器端也有一个用于反序列化的ClientNamenodeProtocolServerSideTranslatorPB类,用于将server接收的数据进行反序列化,然后在调用NameNodeRpcServer相应的方法执行相应的方法。