HADOOP之HDFS
HADOOP学习之HDFS
HDFS介绍
HDFS是hadoop自带的分布式文件系统,英文名为:Hadoop Distributed Filesystem,HDFS以流式数据访问模式来存储超大文件。根据设计,HDFS具有如下特点
HDFS特点
-
支持超大文件
一般来说,HDFS存储的文件可以支持TB和PB级别的数据
-
检测和快速应对硬件故障
在集群环境中,硬件故障是常见性问题。因为有上千台服务器连在一起,故障率很高,因此故障检测和自动恢复hdfs文件系统的一个设计目标。假设某一个datanode挂掉之后,因为数据是有备份的,还可以从其他节点里找到。namenode通过心跳机制来检测datanode是否还存活
-
流式数据访问
HDFS的访问模式是一次写入,多次读取,数据集通常都是由数据源生成或者从数据源复制而来,接着长时间在此数据集上进行各种分析,因此读取整个数据集的时间延迟比读取第一条记录的时间延迟更重要
-
高容错性
数据自动保存多个副本,副本丢失后自动恢复。 可构建在廉价的机器上,实现线性扩展。当集群增加新节点之后,namenode也可以感知,将数据分发和备份到相应的节点上
-
商用硬件
Hadoop并不需要运行在昂贵且高可靠的硬件上。它是设计运行在商用硬件(在各种零售店都能买到的普通硬件)的集群上的,因此至少对于庞大的集群来说,节点故障的几率还是非常搞得。HDFS遇到上述故障时,被设计成能够继续运行且不让用户察觉到明显的中断。
HDFS不适用场景
-
不能做到低延迟数据访问
由于hadoop针对高数据吞吐量做了优化,牺牲了获取数据的延迟,所以对于低延迟数据访问,不适合hadoop。对于低延迟的访问需求,HBase是更好的选择。
-
不适合大量的小文件存储
由于namenode将文件系统的元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。根据经验,每个文件、目录和数据块的存储信息大约占150字节。因此,如果有一百万个小文件,每个小文件都会占一个数据块,那至少需要300MB内存。如果是上亿级别的,就会超出当前硬件的能力。
-
不适合多用户写入文件、修改文件
对于上传到HDFS上的文件,不支持修改文件。Hadoop2.0虽然支持了文件的追加功能,但是还是不建议对HDFS上的文件进行修改。因为效率低下。HDFS适合一次写入,然后多次读取的场景。
HDFS概念
HDFS架构图
-
namenode,名字节点。要管理元数据信息(Metadata),注意,只存储元数据信息。
namenode对于元数据信息的管理,放在内存一份,供访问查询,也会通过fsimage和edits文件,将元数据信息持久化到磁盘上。Hadoop1.0版本利用了SecondaryNamenode做fsimage和edits文件的合并,但是这种机制达不到热备的效果。Hadoop1.0的namenode存在单点故障问题。 -
datanode,数据节点。用于存储文件块。为了防止datanode挂掉造成的数据丢失,对于文件块要有备份,一个文件块有三个副本。
-
rack 机架
-
client 客户端,凡是通过API或指令操作的一端都可以看做是客户端
-
blockSize,Hadoop1.0:64MB。Hadoop2.0 :128MB。
数据块
每个磁盘都有默认的数据块大小,这是磁盘进行数据读/写的最小单位,一般磁盘块为512字节。而HDFS的块默认是128MB(hadoop2.*),和单一磁盘的文件系统相似,HDFS上的文件也被划分为块大小的多个分块,作为独立的存储单元。不同的是HDFS中一个小于块大小的文件不会占据整个块的空间。
HDFS的块比磁盘的块大,目的是为了最小化寻址开销,如果块足够大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间,因而,传输一个由多个块组成的大文件的时间取决于磁盘传输速率。
块缓存
通常datanode从磁盘中读取块,但对于访问频繁的文件,其对应的块可能被显式的缓存在datanode的内存中,以堆外块缓存的形式存在。默认情况下,一个块缓存在一个datanode的内存中。MapReduce,Spark和其他框架通过在缓存块的datanode上运行任务,可以提高读操作的性能,
HDFS读写流程
读流程图
- 客户端发出读数据请求,Open File指定读取的文件路径,去找namenode要元数据信息。
- namenode将文件的元数据信息返回给客户端。
- 客户端根据返回的元数据信息,去对应的datanode去读块数据。
假如一个文件特别大,比如1TB,会分成好多块,此时,namenode并是不一次性把所有的元数据信息返回给客户端。客户端读完此部分后,再去想namenode要下一部分的元数据信息,再接着读。 - 读完之后,通知namenode关闭流。
写流程图
- 发起一个写数据请求,并指定上传文件的路径,然后去找namenode。namenode首先会判断路径合法性,然后会判断此客户端是否有写权限。然后都满足,namenode会给客户端返回一个输出流。此外,namenode会为文件分配块存储信息。namenode也是分配块的存储信息,但不做物理切块工作。
- 客户端拿到输出流以及块存储信息之后,就开始向datanode写数据。因为一个块数据,有三个副本,所以图里有三个datanode。
pipeLine:[bl1,datanode01-datanode03-datanode-07]
- 数据块的发送,先发给第一台datanode,然后再有第一台datanode发往第二台datanode,……。实际这里,用到了pipeLine 数据流管道的思想。
- 通过ack确认机制,向上游节点发送确认,这么做的目的是确保块数据复制的完整性。
- 通过最上游节点,向客户端发送ack,如果块数据没有发送完,就继续发送下一块。如果所有块数据都已发完,就可以关流了。
- 所有块数据都写完后,关流。
HDFS相关API
@Test
public void testConnect() throws Exception{
Configuration conf=new Configuration();
//conf.set("dfs.replication","1");
//FileSystem是Hadoop的文件系统的抽象类,HDFS分布式文件系统只是Hadoop文件系统中的一 种,对应的实现类:
//org.apache.hadoop.hdfs.DistributedFileSystem。HDFS是Hadoop开发者最常用的文件 系统,
//因为HDFS可以和MapReduce结合,从而达到处理海量数据的目的
//Hftp:基于HTTP方式去访问HDFS文件提供(只读)
//本地文件系统、存档文件系统、基于安全模式的HTTP访问HDFS的文件系统。
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
fs.close();
}
@Test
public void testMkdir() throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
//创建目录
fs.mkdirs(new Path("/park07"));
fs.close();
}
@Test
public void testCopyToLocal() throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
//fs.copyToLocalFile(new Path("/park01/helloworld.txt"),new Path("1.txt"));
I nputStream in=fs.open(new Path("/park01/helloworld.txt"));
OutputStream out=new FileOutputStream(new File("2.txt"));
IOUtils.copyBytes(in, out, conf);
fs.close();
}
@Test
public void testCopyFromLocal()throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
//fs.copyFromLocalFile(new Path("3.txt"),new Path("/park01"));
InputStream in=new FileInputStream(new File("3.txt"));
//流的对接,主要要写到文件名
OutputStream out=fs.create(new Path("/park01/3.txt"));
IOUtils.copyBytes(in, out, conf);
fs.close();
}
@Test
public void testDelete() throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
//fs.delete(new Path("/park07"));
//true 递归删除
//false 只能删除为空的目录
fs.delete(new Path("/park02"),true);
fs.close();
}
@Test
public void testStatus() throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
FileStatus[] s=fs.listStatus(new Path("/park01"));
for(FileStatus status:s){
System.out.println(status);
}
}
@Test
public void testBlockLocation() throws Exception{
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(new URI("hdfs://localhost:9000"),conf);
BlockLocation[] bl=fs.getFileBlockLocations(new Path("/park01/ hadoop-2.7.1_64bit.tar.gz"),0,Integer.MAX_VALUE);
for(BlockLocation location:bl){
System.out.println(location);
}
}