个性签名: 世界上最遥远的距离不是天涯,也不是海角,而是我站在妳的面前,妳却感觉不到我的存在
技术方向: Flume+Kafka+Storm+Redis/Hbase+Hadoop+Hive+Mahout+Spark ... 云计算技术
转载声明: 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明,谢谢合作!
qq交流群: 214293307 (期待与你一起学习,共同进步)
# DataNode源码分析
# datanode注释翻译
/**********************************************************
* DataNode is a class (and program)that stores a set of
* blocks for a DFS deployment. A single deployment can
* have one or many DataNodes. Each DataNode communicates
* regularly with a singleNameNode. It also communicates
* with client code and otherDataNodes from time to time.
*
* DataNodes store a series of namedblocks. The DataNode
* allows client code to read theseblocks, or to write new
* block data. The DataNode may also, in response toinstructions
* from its NameNode, delete blocksor copy blocks to/from other
* DataNodes.
*
* The DataNode maintains just onecritical table:
* block-> stream of bytes (of BLOCK_SIZE or less)
*
* This info is stored on a localdisk. The DataNode
* reports the table's contents tothe NameNode upon startup
* and every so often afterwards.
*
* DataNodes spend their lives in anendless loop of asking
* the NameNode for something todo. A NameNode cannot connect
* to a DataNode directly; aNameNode simply returns values from
* functions invoked by a DataNode.
*
* DataNodes maintain an open serversocket so that client code
* or other DataNodes can read/writedata. The host/port for
* this server is reported to theNameNode, which then sends that
* information to clients or otherDataNodes that might be interested.
*
**********************************************************/
# 首先看datanode结构,实现了Runnable接口(run方法)。
public class DataNode extendsConfigured
implements InterDatanodeProtocol,ClientDatanodeProtocol, FSConstants, Runnable, DataNodeMXBean {
# 找到main方法,进入
public static void main(String args[]) {
secureMain(args, null);
}
# 进入scureMain
public static void secureMain(String [] args, SecureResourcesresources) {
try {
StringUtils.startupShutdownMessage(DataNode.class, args,LOG);
DataNode datanode = createDataNode(args,null, resources);
if (datanode !=null)
datanode.join();
创建datanode跟调用datanode.join()(Java Thread中, join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码。)
# 进入createDataNode
/** Instantiate & Start a single datanode daemon and wait for it tofinish.
* If this thread is specifically interrupted, it will stop waiting.
* LimitedPrivate for creating secure datanodes
*/
public static DataNode createDataNode(Stringargs[],
Configuration conf,SecureResources resources) throws IOException {
DataNode dn = instantiateDataNode(args,conf, resources);
runDatanodeDaemon(dn);
return dn;
}
注释说:实例化和开始一个datanode守护进程(runDatanodeDaemon(dn)),等待它完成。如果专门打断这个线程,它将停止等待。创建安全datanodes LimitedPrivate。
# startDataNode方法,一直跟进去(省略中间的代码)最后进入到这么一个主要的方法,该方法代码很多
void startDataNode(Configuration conf,
AbstractList<File> dataDirs, SecureResources resources
) throws IOException {
// connect to name node
this.namenode = (DatanodeProtocol)
RPC.waitForProxy(DatanodeProtocol.class,
DatanodeProtocol.versionID,
nameNodeAddr,
conf);
在datanode中起了一个RPC的客户端,得到一个服务端的代理对象,这里被强转为DatanodeProtocol,实际上就是NameNode这个类,因为NameNode类实现了DatanodeProtocol接口,然后就可以调用NameNode里面的方法了
第二个重要的地方:
this.threadGroup =new ThreadGroup("dataXceiverServer");
this.dataXceiverServer =new Daemon(threadGroup,
new DataXceiverServer(ss, conf,this));
下面我们可以来开始分析DataNode上的动态行为。首先我们来分析DataXceiverServer和DataXceiver。DataNode上数据块的接受/发送并没有采用我们前面介绍的RPC机制,原因很简单,RPC是一个命令式的接口,而DataNode处理数据部分,往往是一种流式机制。DataXceiverServer和DataXceiver就是这个机制的实现。其中,DataXceiver还依赖于两个辅助类:BlockSender和BlockReceiver。 DataXceiverServer很简单,它打开一个端口,然后每接收到一个连接,就创建一个DataXceiver,服务于该连接,DataXceiver是一个线程读一次操作请求进行操作之后就返回,并记录该连接的socket,对应的实现在DataXceiverServer的run方法里。当系统关闭时,DataXceiverServer将关闭监听的socket和所有DataXceiver的socket,这样就导致了DataXceiver出错并结束线程。DataXceiverServer接受到的数据主要有操作码+操作数据+用户名。 (1)BlockSender用来发送block数据,返回给用户的是:成功与否+校验类型+实际offset(因为校验块的原因和用户请求的offset不一致)。BlockSender有配置参数corruptChecksumOk(校验数据读入出错忽略,出错用零填充),chunkOffsetOK(是否要告知实际的offset,如上所述),verifyChecksum(是否要求在把校验数据和实际数据读入包缓存中时校验数据,也就是在发送之前),向客户端传包的时候第一、二个参数为true,第三为false,为的是尽快发送数据。而用来校验已有数据时使用第一二参数为false,第三参数为true,为了及时发现错误数据。readBlock完成实际读数据的操作,比较简单。sendChunks方法中,对于客户端传包的包只有校验和而实际数据通过管道传输,具体见函数。
第三个重要的地方:
//create a servlet to serve full-file content
InetSocketAddress infoSocAddr = DataNode.getInfoAddr(conf);
String infoHost =infoSocAddr.getHostName();
int tmpInfoPort = infoSocAddr.getPort();
this.infoServer = (secureResources ==null)
? new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0,
conf, SecurityUtil.getAdminAcls(conf,DFSConfigKeys.DFS_ADMIN))
: new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0,
conf, SecurityUtil.getAdminAcls(conf,DFSConfigKeys.DFS_ADMIN),
secureResources.getListener());
这里new了一个infoServer,new HttpServer里面是一个jetty的server,就是为了向用户提供web界面的,然后再后面再启动infoServer。
第四个重要的地方:
//init ipc server
InetSocketAddress ipcAddr =NetUtils.createSocketAddr(
conf.get("dfs.datanode.ipc.address"));
ipcServer = RPC.getServer(this,ipcAddr.getHostName(), ipcAddr.getPort(),
conf.getInt("dfs.datanode.handler.count", 3),false, conf,
blockTokenSecretManager);
这里datanode中起了一个RPC的服务端(暂时不知给谁调用的)
# 接下来就会调用dtanode.join()方法,datanode的run方法
/**
* No matter what kind ofexception we get, keep retrying to offerService().
* That's the loop that connectsto the NameNode and provides basic DataNode
* functionality.
*
* Only stop when"shouldRun" is turned off (which can only happen at shutdown).
*/
public void run() {
LOG.info(dnRegistration +"InDataNode.run, data = " +data);
// start dataXceiveServer
dataXceiverServer.start();
ipcServer.start();
while (shouldRun) {
try {
startDistributedUpgradeIfNeeded();
offerService();
} catch (Exception ex) {
LOG.error("Exception: " + StringUtils.stringifyException(ex));
if (shouldRun) {
try {
Thread.sleep(5000);
} catch (InterruptedExceptionie) {
}
}
}
}
LOG.info(dnRegistration +":Finishing DataNode in: "+data);
shutdown();
}
# 我们主要看offerService();这个方法
/**
* Main loop for theDataNode. Runs until shutdown,
* forever calling remote NameNodefunctions.
*/
public void offerService() throws Exception {
里面有一个死循环,主要是调用了一个方法sendHeartbeat
DatanodeCommand[] cmds = namenode.sendHeartbeat(dnRegistration,
data.getCapacity(),
data.getDfsUsed(),
data.getRemaining(),
xmitsInProgress.get(),
getXceiverCount());
myMetrics.addHeartBeat(now() - startTime);
//LOG.info("Just sent heartbeat, with name " + localName);
if(!processCommand(cmds))
continue;
这里的namenode在上面已经解释过了,其实就是DataNode,这里会调用DataNode的sendHeartbeat方法,将自己的状态作为参数(容量,空间使用了多少,剩余多少等等)传递给了namenode。然后namenode调用方法得到一些返回值给datanode,datanode处理这些命令,然后再处理这些指令processCommand(cmds),放送心跳基本分析完毕。
# 接下来看发送心跳以及处理的namenode的指令后datanode还干了些什么
DatanodeCommand cmd= namenode.blockReport(dnRegistration,
BlockListAsLongs.convertToArrayLongs(bReport));
看到这里了,namenode是DatanodeProtocol接口也就是NameNode类。这里是RPC客户端的远程调用,datanode会扫描其机器上对应保存hdfs block的目录下(dfs.data.dir)所保存的所有文件块,然后通过namenode的rpc调用将这些block的信息以一个long数组的方式发送给namenode,namenode在接收到一个datanode的blockReport rpc调用后,从rpc中解析出block数组,并将这些接收到的blocks插入到BlocksMap表中,由于此时BlocksMap缺少的仅仅是每个block对应的datanode信息,而namenoe能从report中获知当前report上来的是哪个datanode的块信息,所以,blockReport过程实际上就是namenode在接收到块信息汇报后,填充BlocksMap中每个block对应的datanodes列表的三元组信息的过程。其流程如下图所示:
当所有的datanode汇报完block,namenode针对每个datanode的汇报进行过处理后,namenode的启动过程到此结束。此时BlocksMap中block->datanodes的对应关系已经初始化完毕。如果此时已经达到安全模式的推出阈值,则hdfs主动退出安全模式,开始提供服务。
// If we have sent the firstblock report, then wait a random
// time before we start the periodic blockreports.
如果我们把第一块报告发送了之后,在我们开始定期块报告之前会等待一个随机时间。
发送完毕之后NameNode会给DataNode发送一些指令,然后DataNode会处理这些指令
processCommand(cmd);
基本上也就分析完了
# DataNode启动过程分析
DataNode一启动,会对DataNode进行初始化,最终进入到startDataNode方法中,该方法
主要有4个重要的地方。
1:起了一个RPC客户端,namenode,实现的接口是DataProtocol,主要是DataNode跟NameNode通信用的,DataNode将自己的一些状态(容量,使用,未使用等)告诉NameNode,然后NameNode返回DataNode一些指令,告诉DataNode要去做什么。
2:dataXceiverServer的启动,是一个线程组(流服务)
3:起了一个jetty服务器,提供web方式的访问
4:起了一个RPC的服务端,给NameNode调用(应该是这样)
接着会实例化datanode和开始一个datanode守护进程(runDatanodeDaemon(dn)),然后会调用datanode的join方法,进入到run方法,这个方法将刚刚startDataNode方法中的RPC服务端开启,然后调用了一个offerService方法,里面是一个死循环,最先开始是datanode RPC远程调用namenode.sendHeartbeat方法,这里的namenode在上面已经解释过了,其实就是DataNode,这里会调用DataNode的sendHeartbeat方法,将自己的状态作为参数(容量,空间使用了多少,剩余多少等等)传递给了namenode。然后namenode调用方法得到一些返回值给datanode,datanode处理这些命令,然后再处理这些指令processCommand(cmds)。
接下来datanode会调用namenode.blockReport的方法,datanode开始扫描自己目录下所保存的所有文件块,然后通过namenode的rpc调用将这些block信息以一个long数组的方式发送给namenode,namenode在接收到一个datanode的blockReport rpc调用后,从rpc中解析出block数组,并将这些接收到的blocks插入到BlocksMap表中,由于NameNode启动时BlocksMap缺少的仅仅是每个block对应的datanode信息,而namenoe能从blocReport中获知当前blockReport上来的是哪个datanode的块信息,所以,blockReport过程实际上就是namenode在接收到块信息汇报后,填充BlocksMap中每个block对应的datanodes列表的三元组信息的过程。当所有的datanode汇报完block,namenode针对每个datanode的汇报进行过处理后,namenode的启动过程到此结束。此时BlocksMap中block->datanodes的对应关系已经初始化完毕。如果此时已经达到安全模式的退出阈值,则hdfs主动退出安全模式,开始提供服务。
调用完namenode.blockReport方法之后 ,namenode会给一些指令给datanode,然后datanode再处理这些指令。
在这个过程中是datanode主动与namenode通信,然后namenode传给datanode一些函数的返回值,告诉datanode该做什么。
DataProtocol中的注释:
* The only way aNameNode can communicate with a DataNode is by
*returning values from thesefunctions.
The you smile until forever 、、、、、、、、、、、、、、、、、、、、、