Hadoop 源码解析-rpc修改
最近因为实验室的项目需求,需要修改Hadoop,在Datanode与Namenode之间增加一个通讯接口以供项目调用。看过一段时间的源码之后,对自己看过的部分做一个总结,以下说法不一定正确,如果有错误还请大家指正。
Hadoop现在默认使用google的protobuf来完成序列化的工作,且Hadoop的RPC机制也是基于Protobuf实现的。那么我们要在Hadoop中加一个RPC的调用来实现Datanode与Namenode之间的信息传递或者其他的目的的时候应该怎么做?
Hadoop的最主要的几个通讯协议是ClientProtocol,NamenodeProtocol,ClientDatanodeProtocol,DatanodeProtocol协议,各协议的用途与通讯的两端的对象如下图与表所示:
协议名 | 作用 |
---|---|
ClientDatanodeProtocol | Client与Datanode之间的通讯协议接口 |
ClientProtocol | Client与Namenode之间的交互的接口(创建文件,删除文件等操作) |
NamenodeProtocol | SecondaryNamenode和Namenode之间的通讯的接口 |
DatanodeProtocol | Datanode与Namenode之间的通讯的接口(心跳,Blockreport等) |
从上面的图表可以看出如果我们要在Datanode与Namenode之间增加一个接口供我们使用那么我们主要的注意力就在DatanodeProtocol上,相应的,如果你们的项目需要在Client与Namenode之间添加一个通讯接口那么就主要集中在ClientProtocol的修改。
我们知道基于Protobuf实现的Rpc机制需要客户端和服务端实现共同的一个接口,以Datanode与Namenode之间的通讯为例,DatanodeProtocol.java中定义的接口就是Datanode与Namenode需要共同实现的接口,查看DatanodeProtocol.jave中接口DatanodeProtocol的定义如下:
@InterfaceAudience.Private
public interface DatanodeProtocol {
/**
* This class is used by both the Namenode (client) and BackupNode (server)
* to insulate from the protocol serialization.
*
* If you are adding/changing DN's interface then you need to
* change both this class and ALSO related protocol buffer
* wire protocol definition in DatanodeProtocol.proto.
*
* For more details on protocol buffer wire protocol, please see
* .../org/apache/hadoop/hdfs/protocolPB/overview.html
*/
……
}
注释中已经很好的说明了如果要对现有的Rpc作扩展要修改的地方:DatanodeProtocol.java,DatanodeProtocol.proto,其中后面这个是protobuf的文件,对protobuf语法还不是很了解的童鞋移步http://colobu.com/2015/01/07/Protobuf-language-guide/。里面定义了message,以及rpc call。而DatanodeProtocol.java中定义了Namenode和Datanode两端要实现的接口interface DatanodeProtocol。两个关键文件在项目中的继承关系如下图所示。我们对Datanode与Namenode之间的RPC的扩展时,需要修改的地方也就是下图中所展示的类。
这里以Datanode向Namenode的注册的RPC函数来作为例子来展示如何添加一个新的RPC 函数。从上图看出我们修改主要分为两条线一条是DatanodeProtocol.java线以及DatanodeProtocol.proto线,所以以Datanode注册的例子分析的时候也分为两条线分析:
DatanodeProtocol.proto线
首先DatanodeProtocol.proto中定义了用于注册的rpc函数如下所示,里面定义了Rpc函数,以及函数发送的消息以及返回的消息的构成。
message RegisterDatanodeRequestProto {
required DatanodeRegistrationProto registration = 1; // Datanode info
}
message RegisterDatanodeResponseProto {
required DatanodeRegistrationProto registration = 1; // Datanode info
}
service DatanodeProtocolService {
/**
* Register a datanode at a namenode
*/
rpc registerDatanode(RegisterDatanodeRequestProto)
returns(RegisterDatanodeResponseProto);
……
}
在DatanodeProtocolPB中对使用protoc对DatanodeProtocol.proto编译产生的类进行了继承:
public interface DatanodeProtocolPB extends
DatanodeProtocolService.BlockingInterface {
}
最后在DatanodeProtocolServerSideTranslatorPB对继承下来的进行了实现:
@Override
public RegisterDatanodeResponseProto registerDatanode(
RpcController controller, RegisterDatanodeRequestProto request)
throws ServiceException {
DatanodeRegistration registration = PBHelper.convert(request
.getRegistration());
DatanodeRegistration registrationResp;
try {
registrationResp = impl.registerDatanode(registration);//!!!!!
} catch (IOException e) {
throw new ServiceException(e);
}
return RegisterDatanodeResponseProto.newBuilder()
.setRegistration(PBHelper.convert(registrationResp)).build();
}
从代码中我们可以看出,在DatanodeProtocolServerSideTranslatorPB中实现的registerDatanode函数中最终是调用了impl.registerDatanode(),追到这个impl赋值地方发现
public DatanodeProtocolServerSideTranslatorPB(DatanodeProtocol impl,
int maxDataLength) {
this.impl = impl;
this.maxDataLength = maxDataLength;
}
看到了吗,在DatanodeProtocolServerSideTranslatorPB的构造函数中有一个参数是DatanodeProtocol 类型,也就是一个DatanodeProtocol的子类,这个子类里面实现了DatanodeProtocol接口中定义的registerDatanode函数,而实际Datanode向Namenode注册的功能也是在这个impl类实现的,下面我们会从Datanodeprotocol.java线进行讲。然后你就会发现在DatanodeProtocolServerSideTranslatorPB构造函数中传进来的DatanodeProtocol类的实现实际上是NamenodeRpcServer。NamenodeRpcServer中实际实现了注册的功能。
DatanodeProtocol.java线
interface DatanodeProtocol中定义了如下的接口:
public DatanodeRegistration registerDatanode(DatanodeRegistration registration
) throws IOException;
NamenodeProtocols继承了所有Namenode这边所有需要实现的协议,其中包含了DatanodeProtocol:
@InterfaceAudience.Private
public interface NamenodeProtocols
extends ClientProtocol,
DatanodeProtocol,
DatanodeLifelineProtocol,
NamenodeProtocol,
RefreshAuthorizationPolicyProtocol,
RefreshUserMappingsProtocol,
RefreshCallQueueProtocol,
GenericRefreshProtocol,
GetUserMappingsProtocol,
HAServiceProtocol,
TraceAdminProtocol {
}
NamenodeRpcServer继承实现了NamenodeProtocols,其中关于注册部分的实现如下所示:
@Override // DatanodeProtocol
public DatanodeRegistration registerDatanode(DatanodeRegistration nodeReg)
throws IOException {
checkNNStartup();
verifySoftwareVersion(nodeReg);
namesystem.registerDatanode(nodeReg);
return nodeReg;
}
值的一提的是NamenodeRpcServer中的registerDatanode中最后调用了namesystem.registerDatanode(),这里的namesystem是FSNamesystem 类型的变量,实际上维护注册的Datanode列表,维护Active Datanode列表等操作都是在这个namesystem中具体进行操作,当然这些不属于我们要更改的RPC的部分。继续在NamenodeRPCServer查看我们可以看到下面的代码:
DatanodeProtocolServerSideTranslatorPB dnProtoPbTranslator =
new DatanodeProtocolServerSideTranslatorPB(this, maxDataLength);
BlockingService dnProtoPbService = DatanodeProtocolService
.newReflectiveBlockingService(dnProtoPbTranslator);
我们看到NamenodeRpcServer把this传入DatanodeProtocolServerSideTranslatorPB的构造函数中,这印证了我们上面说的,DatanodeProtocolServerSideTranslatorPB中调用的RegisterDatanode上通过调用NamenodeRpcServer中实现的注册的方法实现注册。这样两条线就串起来了。
最后看一下DatanodeProtocolClientSideTranslator中是如何实现的RegisterDatanode的
public DatanodeProtocolClientSideTranslatorPB(InetSocketAddress nameNodeAddr,
Configuration conf) throws IOException {
RPC.setProtocolEngine(conf, DatanodeProtocolPB.class,
ProtobufRpcEngine.class);
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
rpcProxy = createNamenode(nameNodeAddr, conf, ugi);//通过RPC.getProxy从Namenode获取
}
@Override
public DatanodeRegistration registerDatanode(DatanodeRegistration registration
) throws IOException {
RegisterDatanodeRequestProto.Builder builder = RegisterDatanodeRequestProto
.newBuilder().setRegistration(PBHelper.convert(registration));
RegisterDatanodeResponseProto resp;
try {
resp = rpcProxy.registerDatanode(NULL_CONTROLLER, builder.build());
} catch (ServiceException se) {
throw ProtobufHelper.getRemoteException(se);
}
return PBHelper.convert(resp.getRegistration());
}
依据Datanode注册的例子,大家应该也看懂了如果自己添加一个RPC 调用该如何作,在DatnodeProtocol.java中添加接口,在DatanodeProtocol.proto中添加rpc函数以及需要的消息的定义,然后在上述的继承图中的类中是实现相关函数与接口,实现方式可以参照RegisterDatanode()