HDFS简介
HDFS(Hadoop Distributed File System,Hadoop分布式文件系统),它是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,适合那些有着超大数据集(large data set)的应用程序。
HDFS的设计特点
- 大数据文件,非常适合上T级别的大文件或者一堆大数据文件的存储。
- 文件分块存储,HDFS会将一个完整的大文件平均分块存储到不同计算机上,它的意义在于读取文件时可以同时从多个主机取不同区块的文件,多主机读取比单主机读取效率要高得多得多。
- 流式数据访问,一次写入多次读写,这种模式跟传统文件不同,它不支持动态改变文件内容,而是要求让文件一次写入就不做变化,要变化也只能在文件末添加内容。
- 廉价硬件,HDFS可以应用在普通PC机上,这种机制能够让给一些公司用几十台廉价的计算机就可以撑起一个大数据集群。
- 硬件故障,HDFS认为所有计算机都可能会出问题,为了防止某个主机失效读取不到该主机的块文件,它将同一个文件块副本分配到其它某几个主机上,如果其中一台主机失效,可以迅速找另一块副本取文件。
特性
- HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M(可通过配置文件修改,一般由磁盘的传输速率决定)
- HDFS文件系统会给客户端提供一个统一的抽象目录树,客户端通过路径来访问文件
- 目录结构及文件分块信息(元数据)的管理由namenode节点承担——namenode是HDFS集群主节点,负责维护整个hdfs文件系统的目录树,以及每一个路径(文件)所对应的block块信息(block的id,及所在的datanode服务器)
- 文件的各个block的存储管理由datanode节点承担----datanode是HDFS集群从节点,每一个block都可以在多个datanode上存储多个副本(副本数量也可以通过参数设置dfs.replication)
- HDFS是设计成适应一次写入,多次读出的场景,且不支持文件的修改
优点
- 高容错性:数据自动保存多个副本,某个副本丢失以后,会自动恢复。
- 适合处理大数据:能够处理GB,TB,PB级别的数据
- 可以构建在廉价的机器上
缺点
- 不适合低延时数据访问
- 不适合处理小文件
- 不支持并发写入,文件随机修改:HDFS是一次写(支持追加,不支持修改),并且不允许多个线程同时写。
为什么不适合处理小文件:
存储大量小文件,会占用NameNode大量的内存目录和块信息(即文件存储在哪里),NameNode的存储空间是有限的。
小文件寻址时间长,有可能寻址时间超过读取数据的时间,违反了HDFS设计的初衷。
HDFS架构
NameNode(即Master)
- 管理HDFS的名称空间
- 配置副本策略(每个文件存储多少个副本)
- 管理数据块(Block)映射信息,即每个数据块在哪里
- 处理客户端读取请求
- 存储FsImage和EditLog。
DataNode(即Slave)
- 存储实际的数据块
- 执行数据块的读写操作
- 需要不断的和NameNode进行通信,告诉其自己的信息,以便于NameNode进行管理。
Client(即客户端)
- 文件切分:文件上传HDFS时,将文件切分成一个个Block(数据块),然后再上传
- NameNode交互,获取文件的位置信息
- DataNode交互,读取或者写入数据;
- Client提供一些命令来管理HDFS,比如NameNode格式化
- Client可以通过一些命令来访问HDFS,比如对HDFS增删查改操作;
Secondary NameNode
- 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits,并推送给NameNode
- 在紧急情况下,可辅助恢复NameNode。
注:当NameNode挂掉时Secondary NameNode并不能马上替换NameNode
块为什么不能过大也不能过小
- HDFS的块设置太小,会增加寻址时间,程序一直在找块的开始位置;
- 如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。导致程序在处理这块数据时,会非常慢。
HDFS块的大小设置主要取决于磁盘的传输速率
注:下图来自官网
HDFS的shell操作
基本语法
bin/hadoop fs 具体操作 或bin/hdfs dfs 操作
hadoop fs -help rm //查看rm命令的用法
hadoop fs -ls / //查看“/”目录下的文件列表
hadoop fs -ls -R / //递归查询文件列表
hadoop fs -mkdir -p /sanguo/shuguo //创建多级目录
hadoop fs -moveFromLocal ./liuchan.txt /sanguo/shuguo/ //将本地文件上移动到hdfs
hadoop fs -appendToFile ./liubei.txt /sanguo/shuguo/liushan.txt //将liubei.txt文件中的内容追加到liushan.txt中
hadoop fs -copyFromLocal ./guanyu.txt /sanguo/shuguo/ //将guanyu.txt复制到hdfs的指定目录
hadoop fs -copyToLocal /sanguo/shuguo/liushan.txt ./ //将liushan.txt复制到本地当前路径下
hadoop fs -cp /sanguo/shuguo/liushan.txt /sanguo/liushan.txt //hdfs之间的复制
hadoop fs -mv /sanguo/shuguo/liushan.txt /sanguo/liushan.txt //hdfs之间的移动
hadoop fs -get /sanguo/liushan.txt ./ //将liushan.txt下载到本地当前目录下
hadoop fs -getmerge /sanguo/shuguo/* ./hebing.tzt //将多个文件下载到本地并合并在一起
hadoop fs -put ./hebing.tzt /samguo/ //本地文件上传hdfs
hadoop fs -rm /sanguo/shuguo/liubei.txt //删除一个文件
hadoop -fs -du / //统计文件信息
客户端操作
参数优先级
hdfs-default.xml=>hdfs-site.xml=>在项目资源目录下的配置文件=>代码中的配置
操作流程
- 获取一个客户端对象
- 执行相关的操作命令
- 关闭资源
获取一个客户端对象
public void init() throws URISyntaxException, InterruptedException, IOException {
//连接的集群nn地址
URI uri = new URI("hdfs://localhost:9000");
//创建一个配置文件
Configuration Configuration = new Configuration();
//用户
String user="Administrator";
//获取客户端对象
fs = FileSystem.get(uri,Configuration,user);
}
关闭资源
public void close() throws IOException {
fs.close();
}
创建目录
public void testmkdir() throws URISyntaxException, IOException , InterruptedException{
fs.mkdirs(new Path("/xiyou"));
}
上传文件
public void testPut() throws IOException {
//参数一:表示删除原数据,参数二:是否允许覆盖,参数三:原数据路径。参数四:目的地路径
fs.copyFromLocalFile(true,true,new Path("D:\\hadoop-3.1.3\\sunwukong.txt"),new Path("hdfs:/xiyou/"));
}
文件下载
public void get() throws IOException {
//参数解读,参数一:原文件是否删除,参数二:原文件的路径,参数三:目标地址路径,参数四:
fs.copyToLocalFile(false,new Path("hdfs:/xiyou/sunwukong.txt"),new Path("D:\\"),true);
}
删除文件,目录
public void textRm() throws IOException {
//参数解读,参数一:删除路径,参数二:是否递归删除
fs.delete(new Path("hdfs:/xiyou"),true);
}
文件的更名和移动
public void mv() throws IOException {
//修改文件名,参数一:原文件路径,参数二:目标文件路径
fs.rename(new Path("/word.txt"),new Path("/wor.txt"));
}
获取文件详细信息
public void fileDetail() throws IOException{
RemoteIterator<LocatedFileStatus> FileStatus = fs.listFiles(new Path("/"),true);
while(FileStatus.hasNext()){
LocatedFileStatus fileStatus = FileStatus.next();
System.out.println(fileStatus.getPath());
System.out.println(fileStatus.getPermission());
System.out.println(fileStatus.getOwner());
System.out.println(fileStatus.getLen());
System.out.println("================");
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(Arrays.toString(blockLocations));
}
}
判断是文件夹还是文件
public void testfile() throws IOException {
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
for (FileStatus status:fileStatuses){
if(status.isFile()){
System.out.println("文件"+status);
}else {
System.out.println("不是文件");
}
}
}
HDFS的数据流
写流程
- 客户端创建一个新文件,并向NameNode请求上传文件。
- NameNode相应可以上传
- 客户端请求上传地址
- NameNode返回存储数据的节点信息
- 客户端向DataNode请求建立块的传输通道
- DataNode进行应答
- 把块数据传输到第一个DataNode第一个DataNode传输到第二个DataNode以此类推。
- 关闭写入流
节点距离计算
节点距离:两个节点到达最近的共同祖先节点的距离和。
机架感知(副本存储节点选择)
- 第一个副本在client所处的节点上,如果客户在集群外,测随机选一个
- 第二个副本和第一个副本存放在相同机架,随机节点
- 第三个副本位于不同于另外两个副本的机架的随机节点
NameNode和secondary NameNode
Secondary NameNode的工作流程
- 定期和NameNode通信,请求停止使用Editlog文件,暂时用户edit.new替代
- 获取Fslmage和Editlog文件,并下载到本地
- 合并Fslmage和Editlog文件:将Fslmage文件载入到内存,然后执行Editlog中的各种操作,对Fslmage进行更新
- 将更新后的Fslmage发送到NameNode
- NameNode进行替换Fslmage,同时用edit.new替换Editlog文件
NameNode的元数据存储位置
元数据一般都存储在内存中为了提高访问速度)。由于内存中的数据容易丢失(比如停电),因此在磁盘中会进行备份(FsImage镜像文件)。这样元数据更新的同时又要更新FsImage,就会导致效率过低,如果不更新FsImage,就会发生一致性问题(一旦NameNode断电,就会产生数据丢失)。因此引入Edits文件(编辑日志,只进行追加)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。在NameNode节点断电后,通过FsImage和Edits的合并,合成元数据。长时间添加数据到Edits中,会导致文件数据过大,导致效率降低,而且合并事件过长,所以FsImage和Edits需要定时进行合并。
FsImage和EditLog
FsImage用于维护文件系统树以及文件树中所有的文件和文件夹的元数据
操作日志文件EditLog中记录了所有针对文件的创建、删除、重命名等操作
注意,这个两个都是文件,也会加载解析到内存中。
SecondaryNameNode工作过程
- SecondaryNameNode会定期和NameNode通信,请求其停止使用EditLog文件,暂时将新的写操作写到一个新的文件edit.new上来,这个操作是瞬间完成,上层写日志的函数完全感觉不到差别;
- SecondaryNameNode通过HTTP GET方式从NameNode上获取到FsImage和EditLog文件,并下载到本地的相应目
- SecondaryNameNode将下载下来的FsImage载入到内存,然后一条一条地执行EditLog文件中的各项更新操作,使得内存中的FsImage保持最新;这个过程就是EditLog和FsImage文件合并;
- SecondaryNameNode执行完(3)操作之后,会通过post方式将新的FsImage文件发送到NameNode节点上
- NameNode将从SecondaryNameNode接收到的新的FsImage替换旧的FsImage文件,同时将edit.new替换EditLog文件,通过这个过程EditLog就变小了