DFSClient | Namenode | Datanode 源码分析顺序图:
DFSClient
|-------ClientProtocol
|-------DFSInputStream
|-------LocatedBlocks
|-------BlockReader
|-------DFSInputStream
|-------DFSOutputStream
|--------Packet
|--------pipeline
|--------DataStreamer
|--------ResponseProcessor
Datanode
|-------BlockSender
|-------DataXceiverServer
|-------- BlockReceiver
|-------DatanodeProtocol
|-------InterDatanodeProtocol
|-------ClientDatanodeProtocol
Namenode
|-------FSNameSystem
|-------FSDirectory
|-------FsImage
|-------LeaseManager
|-------HeartbeatManager
|-------HeartbeatThread
|------Monitor
DFSClient
|-------ClientProtocol
|-------DFSInputStream
|-------LocatedBlocks
|-------BlockReader
|-------DFSInputStream
|-------DFSOutputStream
|--------Packet
|--------pipeline
|--------DataStreamer
|--------ResponseProcessor
DFSClient类的介绍源码:
* DFSClient can connect to a Hadoop Filesystem and
* perform basic file tasks. It uses the ClientProtocol
* to communicate with a NameNode daemon, and connects
* directly to DataNodes to read/write block data.
DFSClient这个类的作用是用于客户端连接Hadoop的HDFS文件系统,并在HDFS文件系统上做基本的文件操作(任务)。这个类和通过RPC机制和HDFS文件系统通信的,具体的RPC通信协议接口类是:org.apache.hadoop.hdfs.protocol.ClientProtocol。
DFSClient通过PRC和namenode节点和datanode节点进行通信以及实现在datanode上执行读/写 block的操作
重要方法源码:
static LocatedBlocks callGetBlockLocations(ClientProtocol namenode,
String src, long start, long length)
throws IOException {
try {
return namenode.getBlockLocations(src, start, length);
} catch(RemoteException re) {
throw re.unwrapRemoteException(AccessControlException.class,
FileNotFoundException.class,
UnresolvedPathException.class);
}
}
这个函数主要是通过跟namenode的交互,来完成从namenode取得用户请求的file的元数据信息
ClientProtocol介绍
客户端和namenode进行rpc通信的协议接口,实现类是org.apache.hadoop.hdfs.protocol.ClientProtocol。
DFSClient通过RPC机制和NameNode通信并获得文件的元数据信息(数据的存放位置,校验和,等等一些关键的信息),然后DFSClient再与DataNode通信(集群中的其它机器)通过数据I/O流来获取到指定的文件信息。
DFSInputStream、BlockReader 介绍
DFSInputStream是DFSClient用于读取datanode上文件的输入流。当DFSClient向namenode发送读文件请求之后,namenode会将此file的元数据信息返回: LocatedBlocks。这个类,封装了文件块的信息(每个文件块的大小、所在的datanode节点信息)。该输入流根据这些信息找到对应的DataNode,然后调用BlockReader类的read()方法,读取datanode节点上文件块里的内容。
重要变量源码:
public class DFSInputStream {
private final DFSClient dfsClient;
private BlockReader blockReader = null;
private LocatedBlocks locatedBlocks = null;
private DatanodeInfo currentNode = null;
}
DFSOutputStream、Packet 、DataStreamer、ResponseProcessor介绍
DFSoutputStream类是一个输出流,用于提供客户端将本地文件(在客户端的电脑上存的文件)上传到HDFS上。当客户端发送一个
写请求后(create请求),比如上传一个大文件。首先会通过DFSclient去找namenode,namenode首先会创建对应的目录,然后返回给DSFClient输出流。拿到输出输出流之后, 根据namenode返回的块信息blocksize,将数据变成64kb的packet存储到dataqueue队列里
DataStreamer会从dataQueue里取出一个一个的packet进行数据传输,然后形成一个数据流管道 pipeline。然后将
数据流管道输出给管道里的第一个datanode节点。第一个datanode节点将数据流管道信息交给管道中的第二个节点。直到管道里最后一个datanode完成存储并返回ack确认后,通知resoponse线程,然后DataStreamer发送下一个packet。
DFSOutputStream里有2个队列和2个线程
两个队列相关代码:
dataQueue是数据队列,用于保存等待发送给datanode的数据包
private final LinkedList
ackQueue是确认队列,保存还没有被datanode确认接收的数据包
private final LinkedList
两个线程:
streamer线程,不停的从dataQueue中取出数据包,发送给datanode
private DataStreamer streamer = new DataStreamer();
response线程,用于接收从datanode返回的反馈信息
private ResponseProcessor response = null;
在向DFSOutputStream中,写入数据(通常是byte数组)的时候,实际的传输过程是:
1、文件数据以字节数组进行传输,每个byte[]被封装成64KB的Packet,然后扔进dataQueue中
2、DataStreamer线程不断的从dataQueue中取出Packet,通过socket发送给datanode(向blockStream写数据)
发送前,将当前的Packet从dataQueue中移除,并addLast进ackQueue
3、ResponseProcessor线程从blockReplyStream中读出从datanode的反馈信息
反馈信息很简单,就是一个seqno,再加上每个datanode返回的标志(成功标志为 DataTransferProtocol.OP_STATUS_SUCCESS)
通过判断seqno(序列号,每个Packet有一个序列号),判断datanode是否接收到正确的包。
只有收到反馈包中的seqno与ackQueue.getFirst()的包seqno相同时,说明正确。否则可能出现了丢包的情况。
4、如果一切OK,则从ackQueue中移出:ackQueue.removeFirst(); 说明这个Packet被datanode成功接收了。
DataStreamer类重要源码:
private DataStreamer() {
isAppend = false;
stage = BlockConstructionStage.PIPELINE_SETUP_CREATE;
}
在DatStreamer开始发送block时,创建pipeline数据流管道
Datanode
|-------BlockSender
|-------DataXceiverServer
|-------- BlockReceiver
|-------DatanodeProtocol
|-------InterDatanodeProtocol
|-------ClientDatanodeProtocol
BlockSender介绍
1.当用户(客户端)向HDFS读取某一个文件时,客户端会根据数据所在的位置转向到具体的DataNode节点请求对应数据块的数据,此时DataNode节点会用BlockSender向该客户端发送数据;
2.当NameNode节点发现某个Block的副本不足时,它会要求某一个存储了该Block的DataNode节点向其它DataNode节点复制该Block,当然此时仍然会采用流水线的复制方式,只不过数据来源变成了一个DataNode节点;
3.HDFS开了一个调节DataNode负载均衡的工具Balancer,当它发现某一个DataNode节点存储的Block过多时,就会让这个DataNode节点转移一部分Blocks到新添加到集群的DataNode节点或者存储负载轻的DataNode节点上;
sh start-balancer.sh -t %10
百分数是磁盘使用偏差率,一般调节的范围在10%~20%间。
4.DataNode会定期利用BlockSender来检查一个Block的数据是否损坏。
DataXceiverServer介绍
datanode端是如何接受传来的数据文件呢?
在datanode类里,有一个线程类:
org.apache.hadoop.hdfs.server.datanode.DataXceiver。每当有client连接到datanode时,datanode会new一个DataXceiver,负责数据的传输工作。
在这个DataXceiver线程类里:
相关代码:
@Override
public void writeBlock(){
/** A class that receives a block and writes to its own disk, meanwhile
* may copies it to another site. If a throttler is provided,
* streaming throttling is also supported.
**/
BlockReceiver blockReceiver = null; // responsible for data handling
}
会调用 BlockReceiver 这个类,这个类是用于接收数据并写入本地磁盘,还负责将数据传输给管道里下一个datanode节点。
DatanodeProtocol介绍
* Protocol that a DFS datanode uses to communicate with the NameNode.
* It's used to upload current load information and block reports.
这个类是datanode和namenode 实现RPC通信的接口协议,作用是datanode通过RPC机制连接namenode并汇报自身节点状态信息。在这个接口类里,一个非常重要的方法是:
相关代码:
public HeartbeatResponse sendHeartbeat(){
}
这个是datanode向namenode发送心跳的方法,datanode周期性通过RPC调用sendHeartbeat向namenode汇报自身的状态。
Namenode
|-------FSNameSystem
|-------DFSConfigKeys
|-------FSDirectory
|-------FsImage
|-------LeaseManager
|-------HeartbeatManager
|-------HeartbeatThread
|------Monitor
Namenode介绍
* NameNode serves as both directory namespace manager and
* "inode table" for the Hadoop DFS. There is a single NameNode
* running in any DFS deployment. (Well, except when there
* is a second backup/failover NameNode, or when using federated NameNodes.)
*
* The NameNode controls two critical tables:
* 1) filename->blocksequence (namespace)
* 2) block->machinelist ("inodes")
*
* The first table is stored on disk and is very precious.
* The second table is rebuilt every time the NameNode comes up.
*
* 'NameNode' refers to both this class as well as the 'NameNode server'.
* The 'FSNamesystem' class actually performs most of the filesystem
* management.
Namenode类是Hadoop DFS文件系统的管理者类,用来管理HDFS的元数据信息。实际控制的是两张表信息,分别是:
1.文件——文件块信息
2.文件块信息——存储这个文件块的机器列表信息
第一张表信息存储在namenode节点的磁盘上,并且访问是非常高效的(因为namenode在启动之后,会将数据加载到内存里供快速访问)
第二张表信息,在每次namenode重启工作后,会重新建立。(这么做目的是为了确保块信息存储的准备性,实现机制时,当namenode重启工作后,每个datanode节点通过rpc心跳向namenode汇报自身存储的文件块信息,然后namenode根据这些信息,重建第二张表信息,在此过程中,HDFS是处于安全模式的,即只能对外提供读服务。当第二表信息重建完后,确认文件块数量正确且都完整,则退出安全模式)
实际上,namenode更多的是充当一个领导角色或更像是一个协议类,它定义了需要做哪些事,而真正干活的是FSNamesystem这个类。
在这个类里,format()这个方法我们应该会很熟悉,这是namenode的格式化方法
format()方法:
/** Format a new filesystem. Destroys any filesystem that may already
* exist at this location. **/
public static void format(Configuration conf) throws IOException {
format(conf, true, true);
}
格式化方法的定义:生成一个全新的文件系统(DFS文件系统)。并且摧毁已经存在的旧数据信息。
format()方法源码骨架:
private static boolean format(Configuration conf, boolean force boolean isInteractive) throws IOException {
//调用Fsnamesystem,获取用户配置的元数据信息存放路径。如果不配置,则默认使用linux的/tmp/hadoop/dfs/name这个目录。
//但是这个目录是临时目录,非常危险,所以需要更改。
Collection
List
//然后调用FSImage类,在对应的目录下,创建新的Fsimage文件和Edits文件
FSImage fsImage = new FSImage(conf, nameDirsToFormat, editDirsToFormat);
fsImage.format(fsn, clusterId);
}
FSimage介绍
把文件和目录的元数据信息持久化地存储到fsimage文件中,每次启动时从中将元数据加载到内存中构建目录结构树,之后的操作记录在edits 中
定期将edits与fsimage合并刷到fsimage中
loadFSImage(File curFile)用于从fsimage中读入Namenode持久化的信息。
HeartbeatManager介绍
* Manage the heartbeats received from datanodes.
* The datanode list and statistics are synchronized
* by the heartbeat manager lock.
namenode通过这个类来管理各个datanode传来的心跳。具体工作的线程是 HeartbeatThread这个线程类
Monitor介绍
这是HeartbeatManager类里一个私有线程类,这个类的作用是周期性检测各个Datanode的心跳,
在这个线程类里run方法里,有一个方法比较重要,就是 heartbeatCheck();这个方法的作用:
* Check if there are any expired heartbeats, and if so,
* whether any blocks have to be re-replicated.
* While removing dead datanodes, make sure that only one datanode is marked
* dead at a time within the synchronized section. Otherwise, a cascading
* effect causes more datanodes to be declared dead.
检查是否有超时的datanode心跳。如果有的话,先判断下在这个死亡节点上是否有数据块需要进行复制。然后对这个datanode进行死亡标记。如果这个节点上有文件需要复制备份,则进行数据备份(满足一个block在HDFS上存3份),复制完后然后再删除这个死掉的datanode节点。从slaves列表中移除
此外,在进行死亡节点数据复制过程中,HDFS对外不提供写服务,即客户端此时是不能上传文件到HDFS系统上的。直到数据复制完成(比如一个block达到3份),最后删除此死亡节点后,才对外提供写服务。此过程中,不影响HDFS的查询文件操作
相关代码:
private class Monitor implements Runnable {
@Override
public void run() {
heartbeatCheck();
}
}