Hadoop源码分析(16)
1、 RPC解析
在文档(15)中解析了实际进行远程调用client类的call方法。在该文档中提到的其本质是使用java的NIO与远端的服务器进行连接。并通过channel将需要执行的方法的数据发送过去,然后等待远端将执行结果返回给客户端。
hdfs的组件中很多都有RPC的server端,之前文档分析到namenode启动的时候会从journalnode中读取其存储的editlog,因此需要使用RPC获取其存储的editlog列表。所以这里按照分析的思路,以journalnode的RPC服务端为例。
这里先以journalnode的启动为入口,分析其如何启动RPC服务的。
在文档(3)中提到了在文件hdfs中会有hdfs所有组件所用的类。其中启动journalnode所用的类为:org.apache.hadoop.hdfs.qjournal.server.JournalNode。这个类的main方法如下:
public static void main(String[] args) throws Exception {
StringUtils.startupShutdownMessage(JournalNode.class, args, LOG);
try {
System.exit(ToolRunner.run(new JournalNode(), args));
} catch (Throwable e) {
LOG.error("Failed to start journalnode.", e);
terminate(-1, e);
}
}
重点在第4行,这行的重点是ToolRunner类的run方法,这个方法会闯入一个新建的journalnode对象和传入main方法args。这个run方法如下:
public static int run(Configuration conf, Tool tool, String[] args)
throws Exception{
if(conf == null) {
conf = new Configuration();
}
GenericOptionsParser parser = new GenericOptionsParser(conf, args);
//set the configuration back, so that Tool can configure itself
tool.setConf(conf);
//get the args w/o generic hadoop args
String[] toolArgs = parser.getRemainingArgs();
return tool.run(toolArgs);
}
这里的重点在于最后一行,调用tool的run方法。这里的tool实际是传入的journalnode对象。其run方法如下:
public int run(String[] args) throws Exception {
start();
return join();
}
这里主要调用的是start方法,方法内容如下:
public void start() throws IOException {
Preconditions.checkState(!isStarted(), "JN already running");
validateAndCreateJournalDir(localDir);
DefaultMetricsSystem.initialize("JournalNode");
JvmMetrics.create("JournalNode",
conf.get(DFSConfigKeys.DFS_METRICS_SESSION_ID_KEY),
DefaultMetricsSystem.instance());
InetSocketAddress socAddr = JournalNodeRpcServer.getAddress(conf);
SecurityUtil.login(conf, DFSConfigKeys.DFS_JOURNALNODE_KEYTAB_FILE_KEY,
DFSConfigKeys.DFS_JOURNALNODE_KERBEROS_PRINCIPAL_KEY, socAddr.getHostName());
registerJNMXBean();
httpServer = new JournalNodeHttpServer(conf, this);
httpServer.start();
httpServerURI = httpServer.getServerURI().toString();
rpcServer = new JournalNodeRpcServer(conf, this);
rpcServer.start();
}
与RPC相关的在第22行,这里创建了一个JournalNodeRpcServer对象。这个方法的构造方法如下:
JournalNodeRpcServer(Configuration conf, JournalNode jn) throws IOException {
this.jn = jn;
Configuration confCopy = new Configuration(conf);
// Ensure that nagling doesn't kick in, which could cause latency issues.
confCopy.setBoolean(
CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_KEY,
true);
InetSocketAddress addr = getAddress(confCopy);
RPC.setProtocolEngine(confCopy, QJournalProtocolPB.class,
ProtobufRpcEngine.class);
QJournalProtocolServerSideTranslatorPB translator =
new QJournalProtocolServerSideTranslatorPB(this);
BlockingService service = QJournalProtocolService
.newReflectiveBlockingService(translator);
this.server = new RPC.Builder(confCopy)
.setProtocol(QJournalProtocolPB.class)
.setInstance(service)
.setBindAddress(addr.getHostName())
.setPort(addr.getPort())
.setNumHandlers(HANDLER_COUNT)
.setVerbose(false)
.build();
// set service-level authorization security policy
if (confCopy.getBoolean(
CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
server.refreshServiceAcl(confCopy, new HDFSPolicyProvider());
}
}
首先是第12行的setProtocolEngine方法,这个方法在文档(14)中介绍过,其主要作用是设置接口使用的engine类。然后是第14行到第17行,创建一个service用来处理远程调用的方法。最后是第19行到第26行创建rpc server。
这里RPC server的方式与文档(13)使用的方式相同。首先创建一个Builder类,然后向builder中设置需要的参数,最后再调用build方法创建server。其中重点在build方法。其内容如下:
public Server build() throws IOException, HadoopIllegalArgumentException {
if (this.conf == null) {
throw new HadoopIllegalArgumentException("conf is not set");
}
if (this.protocol == null) {
throw new HadoopIllegalArgumentException("protocol is not set");
}
if (this.instance == null) {
throw new HadoopIllegalArgumentException("instance is not set");
}
return getProtocolEngine(this.protocol, this.conf).getServer(
this.protocol, this.instance, this.bindAddress, this.port,
this.numHandlers, this.numReaders, this.queueSizePerHandler,
this.verbose, this.conf, this.secretManager, this.portRangeConfig);
}
}
首先是第2行到第10行,这里主要是检查一些参数。重点在第12行这里先用getProtocolEngine方法获取engine类,然后调用engine类的getServer方法。getProtocolEngine方法在文档(14)中也提到过,它最终获取的类是是上文设置的ProtobufRpcEngine类。这个类的getserver方法内容如下:
@Override
public RPC.Server getServer(Class<?> protocol, Object protocolImpl,
String bindAddress, int port, int numHandlers, int numReaders,
int queueSizePerHandler, boolean verbose, Configuration conf,
SecretManager<? extends TokenIdentifier> secretManager,
String portRangeConfig)
throws IOException {
return new Server(protocol, protocolImpl, conf, bindAddress, port,
numHandlers, numReaders, queueSizePerHandler, verbose, secretManager,
portRangeConfig);
}
这里是直接创建了一个server对象,这个server对象是其内部类继承于RPC的server,其内容如下:
public static class Server extends RPC.Server
这个类的构造方法如下:
public Server(Class<?> protocolClass, Object protocolImpl,
Configuration conf, String bindAddress, int port, int numHandlers,
int numReaders, int queueSizePerHandler, boolean verbose,
SecretManager<? extends TokenIdentifier> secretManager,
String portRangeConfig)
throws IOException {
super(bindAddress, port, null, numHandlers,
numReaders, queueSizePerHandler, conf, classNameBase(protocolImpl
.getClass().getName()), secretManager, portRangeConfig);
this.verbose = verbose;
registerProtocolAndImpl(RPC.RpcKind.RPC_PROTOCOL_BUFFER, protocolClass,
protocolImpl);
}
这里的重点有两个,第一个是第7行的super方法,这个方法会调用父类的构造方法。第二个是第11行的registerProtocolAndImpl方法,这两个方法会将远程调用的接口与其实现类进行封装存储。
这里先解析registerProtocolAndImpl方法,其内容如下:
void registerProtocolAndImpl(RpcKind rpcKind, Class<?> protocolClass,
Object protocolImpl) {
String protocolName = RPC.getProtocolName(protocolClass);
long version;
try {
version = RPC.getProtocolVersion(protocolClass);
} catch (Exception ex) {
LOG.warn("Protocol " + protocolClass +
" NOT registered as cannot get protocol version ");
return;
}
getProtocolImplMap(rpcKind).put(new ProtoNameVer(protocolName, version),
new ProtoClassProtoImpl(protocolClass, protocolImpl));
LOG.debug("RpcKind = " + rpcKind + " Protocol Name = " + protocolName + " version=" + version +
" ProtocolImpl=" + protocolImpl.getClass().getName() +
" protocolClass=" + protocolClass.getName());
}
这段代码实际不复杂,首先是第3行拿到接口的名称,然后是第8行拿到接口的版本号。然后是第16行先用getProtocolImplMap方拿到存储该接口信息的map,然后使用put方法将数据设置到map中。其中key是封装了接口名称与版本的ProtoNameVer类对象,value是封装了接口与实现类的ProtoClassProtoImpl类对象。这两个类都是单纯用来存储数据的,没有其他特殊方法。
用来获取map的getProtocolImplMap方法,也很简单,其内容如下:
Map<ProtoNameVer, ProtoClassProtoImpl> getProtocolImplMap(RPC.RpcKind rpcKind) {
if (protocolImplMapArray.size() == 0) {// initialize for all rpc kinds
for (int i=0; i <= RpcKind.MAX_INDEX; ++i) {
protocolImplMapArray.add(
new HashMap<ProtoNameVer, ProtoClassProtoImpl>(10));
}
}
return protocolImplMapArray.get(rpcKind.ordinal());
}
首先是第2行,如果protocolImplMapArray中没有数据,便根据RpcKind的最大值来向其中添加hashmap。这里的protocolImplMapArray是一个ArrayList。最后是第8行根据传入的rpcKind类获取对应的map。
然后再看这个server的super方法,这个方法调用的是其父类RPC的server。其内容如下:
protected Server(String bindAddress, int port,
Class<? extends Writable> paramClass, int handlerCount,
int numReaders, int queueSizePerHandler,
Configuration conf, String serverName,
SecretManager<? extends TokenIdentifier> secretManager,
String portRangeConfig) throws IOException {
super(bindAddress, port, paramClass, handlerCount, numReaders, queueSizePerHandler,
conf, serverName, secretManager, portRangeConfig);
initProtocolMetaInfo(conf);
}
这里可以看见第7行又调用了父类的构造方法。这个类继承于ipc的Server类,定义如下所示:
public abstract static class Server extends org.apache.hadoop.ipc.Server
这里创建的server的继承关系如下:首先是engine类ProtobufRpcEngine,在这个类的getServer方法会创建server对象。它创建的server是其内部类Server类的对象,这个类有继承于RPC类的内部类Server类。然后这个Server又继承于ipc包下的Server类。