目录
(一)概述
在海量数据的业务场景中,我们每天采集的数据通常是GB级别的,系统所存储的数据通常是TB级别的,甚至是PB级别。为了应为数据存储的管理和扩展问题,大数据集群通常采用横向扩展的方式来满足数据增长的需求,即以网络互连的节点为单位扩大存储容量。为了构建横向扩展的分布式文件系统,通常需要解决如下几个问题:
1.数据能备份:由于横向扩展的集群节点通常采用廉价的服务器,因而出现故障的几率较大,需要分布式文件系统能够很好的应对各种问题,也就是有良好的容错性;
2.存储大文件:在分布式文件系统中,存储GB级别的文件很常见,也可能存储大量的KB级小文件,这与传统文件系统的场景很不同,要求大数据集群在IO方面有良好的性能;
3.一次写入多次读取:大多数场景下,文件是追加写入的,且写入完成后不会随机修改,只会读取。因此,文件的追加操作需要重点保障读写性能和原子操作。
在2003年,Google发布了分布式文件系统GFS的相关论文,在此基础上,Apache开源了顶级项目:Hadoop,而HDFS正是Hadoop中的分布式文件系统,也是GFS的开源实现。
(二)文件系统的应用
构建分布式文件系统,通常有两种方式:文件级别和块级别。
这里首先介绍文件级别,通常采用的方案是基于现有文件系统的主从架构:Master/Slave,也就是给定N个网络节点,每个节点都装有Linux操作系统,选出一个节点作为Master,记录文件的元信息,其他节点作为Slave,存储实际文件。
该方案是初级的分布式文件系统实现,但存在如下两个难以解决的问题:
1.难以负载均衡:文件级别的分布式文件系统,以文件为单位进行存储,由于用户的文件大小不统一,因此难以保证每个节点的数据都是均衡的;
2.难以并行处理:如果以文件为单位进行存储,当多个分布在不同节点上的并行任务读取同一文件时,存储该文件的节点网络带宽便会成为瓶颈,从而制约上层框架的并行处理效率。
文件级别的分布式文件系统结构图如下:
为了解决文件级别的分布式文件系统存在的问题,我们提出了块级别的分布式文件系统,其核心思想是将文件分成等大的数据块,例如HDFS默认是128MB,并以数据块为单位存储到不同的节点上,进而解决负载均衡和并行处理的问题。如下图所示:
(三)HDFS基本架构
HDFS是典型的块级别分布式文件系统,主节点被称为Namenode,负责管理元信息和子节点,子节点被称为Datanode,负责存储实际的数据块,如下图所示:
Namenode是HDFS的管理者,主要包括如下功能:
1.管理元信息:Namenode以目录树的形式维护整个文件系统目录、文件的数据块等信息;
2.管理Datanode:Datanode需要周期性的向Namenode汇报心跳信息,一旦Namenode发现Datanode出现了问题,会在其他存活的Datanode节点上重构丢失的数据块;
3.HA高可用性:为了防止Namenode出现问题之后集群不可用,通常会启动一个备用的Namenode,实现HA的高可用性;
4.状态同步:主从Namenode并不是通过强一致的协议保证状态一致的,而是通过第三方的文件共享存储系统,主Namenode将EditLog日志写入共享存储系统,从Namenode读取这些日志并执行修改操作。
Datanode主要是用来对文件数据块的读写。存储数据块和块的校验,因为数据在网络的传输中可能存在数据丢失的问题,所以需要校验。同时,DataNode要保持与NameNode的通信,一般每3秒发送一次心跳包。一般来说,数据是以block存储在DataNode节点上,在DataNode节点上提交文件,那么第一个块就是存储在这个节点上(选择最近的一个),如果不是在DataNode节点上提交的,就随即挑选一个磁盘合适CPU负荷的节点。第二个block存储在不同机架上的一个节点,假设副本数为3,那么第三个block存储在与第二个block相同机架上的不同节点。
(四)HDFS关键技术
HDFS在实现时采用了大量分布式技术,典型的有如下几种:
1.容错性设计
HDFS有良好的容错性设计,以降低节点故障情况下数据丢失的可能性。当Namenode故障时,会导致整个文件系统数据不可用,因此HDFS会为每个Active Namenode分配一个Standby Namenode,用于故障时备用;当Datanode故障时,由于数据块的多副本策略,Namenode可以在其他节点上重构故障Datanode上的数据块;当数据块损坏时,也就是校验码不一致时,Namenode会通过其他节点上的正常副本来重构首受损的数据块。
2.副本放置策略
数据块副本放置策略决定了每个数据块多个副本存放节点的选择,在保证读写性能的前提下,尽可能的提高数据的可靠性。副本放置策略与集群物理拓扑结构直接相关,通常情况下,一个集群由多个机架构成,每个机架由16~64个物理节点组成,机架内部的节点时通过内部交换机通信的,机架之间的节点时通过外部节点通信的。由于机架间的节点通信需要多层交换机,相比于机架内节点通信,读写延迟要高一些。相同机架内部的节点通常是绑定在一起的,多种资源可能是共享的,例如插座、交换机等,因此同时不可用的概率要比不同机架节点高很多。
考虑到集群物理拓扑结构特点,HDFS默认采用三副本放置策略。当客户端与Datanode同节点时,上层计算框架处理HDFS数据时,每个任务实际上就是一个客户端,运行在与Datanode相同的计算节点上,三副本放置策略为:第一个副本写到同Datanode上,另外两个副本写到相同机架的不同Datanode上。当客户端与Datanode不同节点时,也就是HDFS之外的应用程序会向HDFS写数据,例如Flink Sink,HDFS会随机选择一个Datanode作为第一个副本放置节点,其他两个副本写到另一个相同机架不同的Datanode上。
3.异构存储介质
随着HDFS的不断完善,已经从最初的单存储介质(磁盘)的单一文件系统,演化为支持异构存储介质的综合性分布式文件系统,使得HDFS能够很好的利用新型存储介质,使得HDFS变成了一个提供混合存储方式的存储系统,用户能够根据自己的业务特点,选择不同的存储介质。HDFS目前支持的存储介质主要包括:
Disk:默认的磁盘介质;
Archive:高存储密度但耗电较少,通常用来存储冷数据;
SSD:固态硬盘,新型存储介质,速度较快;
Ram Disk:数据首先被写道内存中,同时向该存储介质中再异步写一份。
4.集中式缓存管理
HDFS允许用户将一部分目录或文件缓存在off-heap内存中,以加速对这些数据的访问效率,该机制被称为集中式缓存管理,包括如下几方面的有点:
提高集群的内存利用效率:当使用操作系统的缓存时,对一个数据块的重读读会导致所有的副本都会被放到缓冲区中,造成内存浪费,使用集中式缓存,用户可以指定n个副本的m个被缓存,节约n-m的内存;
防止被频繁使用的数据从内存中清除:当使用操作系统缓存时,操作系统使用自带的内存置换算法管理内存,此时容易让热数据不断写入内存,之后从内存中清除,导致数据访问速度的不稳定;
提高读取效率:Datanode缓存统一由Namenode来管理,上层计算框架的调度器查询数据块的缓存列表,并通过一定的调度策略将任务尽可能调度到缓存块所在节点上,以提高数据读性能;当数据块被Datanode缓存后,HDFS会使用一个高效的、支持zero-copy的新API加快读速度,客户端的开销基本是0。
(五)HDFS访问方式
HDFS提供了两类shell命令:用户命令和管理员命令,具体如下:
1.用户命令
HDFS提供了大量用户命令,常用的有文件操作命令dfs,文件一致性检查命令fsck和分布式文件复制命令distcp,具体如下:
(1)文件操作命令dfs
文件操作命令是文件系统交互的命令,可以是HDFS,也可以是Hadoop支持的文件系统,语法如下:
$HADOOP_HOME/bin/hadoop fs <args>
所有命令均会接收文件uri作为参数,如果直接操作HDFS上的文件,也可以使用如下命令:
$HADOOP_HOME/bin/hdfs dfs <args>
除了执行命令的前缀不同,HDFS大部分文件操作命令与Linux自带命令类似,例如mkdir、rm、mv等。
(2)文件一致性检查命令fsck
fsck命令的用法和参数如下:
(3)分布式文件复制命令distcp
分布式文件复制命令distcp主要功能包括集群内文件并行复制和集群间文件并行复制,例如将/user/gai/从集群nm1复制到集群nm2上,加入两个集群的版本相同,那么命令如下:
bin/hadoop/ distcp hdfs://nm1:8020/user/gai hdfs://nm2:8020/user/gai
2.管理员命令
管理员命令主要是针对服务生命周期管理的,比如启动/关闭Namenode/Datanode,HDFS份额管理等,例如启动和关闭Namenode:
sbin/hadoop-daemon.sh start namenode
sbin/hadoop-daemon.sh stop namenode
(六)HDFS Java API
例子如下:
/**
* hdfs入门程序:
* 面向对象:一切皆对象
* 文件系统也可以看做是一类事物、FileSystem
*
* 一定有共同的行为和属性:
* 1.属性--就是--URL
* 本地文件系统的URL: file:///c:myProgram
* HDFS文件系统的URL: fs.defaultFS=hdfs://hadoop02:9000
* 2.行为/方法--就是--上传和下载
*
* FileSystem类的相关方法:
* .get()----->静态方法,用来获取FileSystem类的这个实例对象的,而不是 做下载的,此方法最少传一个参数否则要传三个参数
*/
public class HelloHDFS {
/**
* 从windows上传和下载到HDFS
*/
public static void main(String[] args) throws Exception {
/**
* 插曲:创建对象的方式有五种:
* 1.构造方法(一般用这种)
* 2.静态方法(一般用这种)
* 3.反射
* 4.克隆
* 5.反序列化
*/
//Configuration是配置对象,conf可以理解为包含了所有配置信息的一个集合,可以认为是Map
//在执行这行代码的时候底层会加载一堆配置文件 core-default.xml;hdfs-default.xml;mapred-default.xml;yarn-default.xml
Configuration conf = new Configuration();
//相当于通过配置文件的key获取到value的值
conf.set("fs.defaultFS","hdfs://hadoop02:9000");
/**
* 更改操作用户有两种方式:(系统会自动识别我们的操作用户,如果我们设置,将会报错会拒绝Administrator用户(windows用户))
* 1.直接设置运行环境中的用户名为hadoop,此方法不方便因为打成jar包执行还是需要改用户,右键Run As--Run Configurations--Arguments--VM arguments--输入-DHADOOP_USER_NAME=hadoop
* 2.直接在代码中进行声明
*/
//更改操作用户为hadoop
System.setProperty("HADOOP_USER_NAME","hadoop");
//获取文件系统对象(目的是获取HDFS文件系统)
FileSystem fs=FileSystem.get(conf);
//直接输出fs对象是org.apache.hadoop.fs.LocalFileSystem@70e8f8e
//这说明是本地文件系统对象。代码在eclipse所嵌入的jvm中执行的,jvm是安装在Windows下的,所以是windows文件系统对象
//所以要返回来指定HDFS
System.out.println(fs);
//上传的API
fs.copyFromLocalFile(new Path("c:/ss.txt"), new Path("/a"));
//下载的API 不改名就不用写文件名字也行
fs.copyToLocalFile(new Path("/a/qqq.txt"), new Path("c:/qqq.txt"));
fs.close();
/**
* .crc 是校验文件
* 每个块的元数据信息都只会记录合法的数据起始偏移量。
* 如果进行了非法的数据追加,最终是能够下载正确的数据的。
* 如果在数据的中间更改了数据,造成了采用CRC算法计算出来的校验值和最初存入HDFS的校验值不一致。HDFS就认为当前这个文件被损坏了。
*/
}
}