Hadoop源码剖析--HDFS的数据存储
一、HDFS内存存储原理
HDFS的数据存储包括两块:(1)HDFS内存存储;(2)HDFS异构存储。
HDFS内存存储是一种十分特殊的存储方式,将会对集群数据的读写带来不小的性能提升,而HDFS异构存储则能帮助我们更加合理地把数据存到应该存的地方。
HDFS的LAZY_PERSIST内存存储策略用的是下面的这种方法,
其中第4步写数据到内存中,第6步异步地将数据写到磁盘,前面几步是如何设置StorageType的操作,在下文中会具体提到。所以异步存储的大体步骤可以归纳如下:
- 对目标文件目录设置StoragePolicy为LAZY_PERSIST的内存存储策略。
- 客户端进程向NameNode发起创建/写文件的请求。
- 客户端请求到具体的DataNode后DataNode会把这些数据块写入RAM内存中,同时启动异步线程服务将内存数据持久化写到磁盘上。
LAZY_PERSIST名称的源由就是数据不是马上落盘,而是懒惰的、延时地进行处理。
那么什么叫做虚拟内存盘呢?在Linux中,的确有将内存模拟为一块盘的技术,叫虚拟内存盘。这是一种模拟的盘,实际数据都是存放在内存中的。因此我们将机器内存利用起来,作为一块独立的虚拟盘供DataNode使用了。
二、HDFS内存存储流程分析
要想让文件数据存储到内存中,我们就需要设置一个存储策略,就是上面提到的LAZY_PERSIST,而不是默认的存储策略:StoragePolicy.DEFAULT,默认的存储策略是DISK类型。设置存储策略有以下3种方法:
- 通过命令行的方式,调用如下命令:
hdfs storagepolicies –setStoragePolicy –path <path> -policy LAZY_PERSIST
- 第二种方法是调用对应的程序方法,比如调用暴露在外部的create文件方法,但是得带上参数CreateFlag.LAZY_PERSIST。
- 还有一种方法是通过FileSystem的setStoragePolicy方法,不过这个方法在2.8版本之后会提供,如下所示:
fs.setStoragePolicy(path,”LAZY_PERSIST”);
设置好了LAZY_PERSIST存储策略之后,我们可以想一个问题,当DataNode内存不足的时候怎么办呢?这里采用的是LRU(Least Recently Used)机制,意为最近最少使用算法。其中里面有一个getNextCandidateForEviction方法会采用LRU算法把最近很久没有访问过的块给移除掉,并且在内存中移除与候选块属于同一副本信息的块并释放内存空间。
三、LAZY_PERSIST内存存储的使用
上面说到内存存储使用虚拟内存盘来存储,首先需要将机器中已经完成好的虚拟内存盘配置到dfs.datanode.data.dir中,其次还要带上RAM_DISK标签,以此表明此目录对应的存储介质为RAM_DISK,配置样例如下:
<property>
<name>dfs.datanode.data.dir</name>
<value>/grid/0,/grid/1,/grid/2,[RAM_DISK]/mnt/dn-tmpfs</value>
</property>
注意,这个标签是必须是打上的,否则HDFS默认的都是DISK。
其实,在HDFS异构存储方式中,除了内存存储之外,就是HDFS的Archival Storage。它一般用于冷数据的存储。
四、HDFS异构存储
异构存储类型在HDFS中声明了一下几种Storage Type:
- RAM_DISK
- SSD
- DISK
- ARCHIVE
那么我们如何让HDFS知道集群中的数据存储目录分别是哪种类型的存储介质呢?这就需要在配置属性时主动声明,HDFS并没有自动检测识别的功能。配置属性dfs.datanode.data.dir可以对本地对应存储目录进行设置,同时带上一个存储类型标签,声明此目录用的是哪种类型的存储介质,例子如下,
[SSD]file:///grid/dn/ssd0
了解完异构存储类型后,我们来了解一下HDFS异构存储的实现原理。HDFS异构存储可总结为以下三点:
- DataNode通过心跳汇报自身数据存储目录的StorageType给NameNode。
- 随后NameNode进行汇总并更新集群内各个节点的存储类型情况。
- 待复制文件根据自身设定的存储策略信息向NameNode请求拥有此类型存储介质的DataNode作为候选节点。
那么第一步我们是如何知道他的数据存储目录呢?这里使用了一个方法来获取数据存储目录,
conf.getTrimmedStringCollection(DFS_DATANODE_DATRA_DIR_KEY);
当然,我们也很关心如何解析配置并最终得到对应存储类型的过程,即下面执行的内容(这里解析的是上面红色配置的部分,使用代码对它进行解析即可):
location = StorageLocation.parse(locationString);
StorageLocation的解析方法如下:
public static StorageLocation parse(String rawLocation)
throws IOException,SecurityException{
Matcher matcher = regex.matcher(rawLocation);
StorageType storageType = StorageType.DEFAULT;
String location = rawLocation;
if(matcher.matches()){
String classString = matcher.group(1);
location = matcher.group(2);
if(!classString.isEmpty()){
storageType =
StorageType.valueOf(StringUtils.toUpperCase(classString));
}
}
return new StorageLocation(storageType,new Path(location),toUrl());
}
后续这些解析好的存储目录以及对应的存储介质类型会加入到storageMap中。storageMap存储了目录到类型的映射关系,可以说是非常细粒度的。更重要的是,这些信息会被DataNode组织成StorageReport通过心跳的形式上报给NameNode。StorageReport里面包括StorageMap和磁盘存储信息。它最终被BPServiceActor的sendHeartBeat调用发送给NameNode。因此NameNode会收到StorageMap、坏磁盘数据信息、DataNode自身存储的容量信息。
接下来就是第二阶段的心跳处理过程。心跳处理在DataNodeManager的handlerHeartbeat中进行。最终在heartbeatManager中会调用到DatanodeDescription对象的updateHeartbeatState方法,该方法会更新Storage的信息,这里面还会统计心跳的更新次数,如下所示:
//进行统计计数的更新统计
totalCapacity += report.getCapacity();
totalRemaining += report.getRemaining;
totalBlockPoolUsed += report.getBlockPoolUsed();
totalDfsUsed += report.getDfsUsed();
接下来就进入的第三阶段,会存储一些副本节点的存储类型。有目标存储介质需求的待复制文件块就会向NameNode请求DataNode,这部分处理在FSNamesystem的getAdditionDatanode中进行。它的实现步骤是:
- 获取策略id—》存储策略(RAM,DISK,….)
- 寻找对应的存储目录
- 通过StoragePolicyID选择满足需求的节点
最后,对大数据以及ios感兴趣的朋友可以关注“大数据及周边技术”公众号或者加小编微信进群交流。
END:
推一本小编近期要上的一本书《Hive数据仓库企业级应用》