随着信息技术的高度发展,数据量越来越多,当一个操作系统管辖范围存储不下时,只能将数据分配到更多的磁盘中存储,但是数据分散在多台磁盘上非常不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,因此诞生了分布式文件系统。HDFS(Hadoop Distribute File System)是一种能运行在通用硬件上的分布式文件系统,具有高度容错的特点,适合部署在廉价的机器上。
由于hadoop1和hadoop2版本差异较大,本文以下部分如未标记特殊说明则默认指的是hadoop2版本
1、数据块(block)
与普通文件系统类似,HDFS也有数据块的概念,默认数据块block大小为64M。不同于普通文件系统的是,在HDFS中,如果一个文件小于数据块的大小,并不占用整个数据块存储空间。
2、元数据节点(namenode)和数据节点(datanode)
hadoop1版本中只存在一个namnode节点,存在单点故障风险,在hadoop2版本中引入了HDFS联盟的概念,即包含多个HDFS系统,同时也包含多个namenode节点。
namenode节点的作用:① 接收用户操作请求;② 维护文件系统的目录结构;③ 管理文件与block的关系(哪些block属于哪些文件),block与datanode之间的关系。
datanode数据节点的作用:①存储文件数据;②存储文件数据block副本(默认存储3份)。
3、HDFS特点
HDFS允许文件通过网络在多台主机上分享,并让多机器上的多用户分享文件和存储空间,其具有下述两个特性:① 通透性,让实际上是通过网络来访问文件的动作,由程序与用户看来,就像是访问本地的磁盘一般; ② 容错性,即使系统中有某些节点脱机,整体来说系统仍然可以持续运作而不会有数据丢失。
当HDFS数据块小于一个block大小时,并不占用整个block大小,也即是说文件大小=实际占用空间。这一点与Windows操作系统的文件簇不一样,在Windows操作系统中,一个小于簇大小的文件实际占用空间也是一个簇。HDFS不支持并发写的情况,且不适合小文件。
1、namenode元数据结构
在hadoop-2.6.0中,NameNode元数据存储在${hadoop.tmp.dir}/dfs/name/current路径中。可通过dfs.namenode.name.dir配置结构,强烈建议配置多个备份路径,配置格式如:/name1/dfs/name,/name2/dfs/name,/name3/dfs/name等,特别推荐配置一个网络文件系统NFS,最大限度地保证元数据的完整性。hadoop-2.6.0的路径${hadoop.tmp.dir}/dfs/name下,有锁文件in_use.lock和文件夹current,文件夹current下存在以下文件:① VRSION属性文件;② seen_txid文件;③ 大量edits文件;④ 少量fsimage文件。
其中seen_txid是存放transactionId的一个重要文件,format之后是0,它代表的是namenode里面的edits_*文件的尾数,namenode重启的时候,会按照seen_txid的数字,循序从头跑edits_0000001~到seen_txid的数字。所以当hdfs发生异常重启的时候,一定要比对seen_txid内的数字是不是你edits最后的尾数,否则很可能丢失数据。VERSION文件是Java属性文件,其文件内容和说明如下所示:
#Thu Mar 19 19:52:29 PDT 2015
namespaceID=605992118 #namespaceID是文件系统的唯一标识符,在文件系统首次格式化之后生成的
clusterID=CID-fd97cef9-ffad-48ac-b15b-e715501d73f4 #clusterID是系统生成或手动指定的集群ID,在-clusterid选项中可以使用它
cTime=0 #cTime表示NameNode存储时间的创建时间,由于我的NameNode没有更新过,所以这里的记录值为0,以后对NameNode升级之后,cTime将会记录更新时间戳
storageType=NAME_NODE #storageType说明这个文件存储的是什么进程的数据结构信息(如果是DataNode,storageType=DATA_NODE)
blockpoolID=BP-1535557182-192.168.137.101-1426745680483 #blockpoolID是针对每一个Namespace所对应的blockpool的ID,上面的这个BP-893790215-192.168.24.72-1383809616115就是在我的ns1的namespace下的存储块池的ID,这个ID包括了其对应的NameNode节点的ip地址
layoutVersion=-60 #layoutVersion表示HDFS永久性数据结构的版本信息, 只要数据结构变更,版本号也要递减,此时的HDFS也需要升级,否则磁盘仍旧是使用旧版本的数据结构,这会导致新版本的NameNode无法使用
在current目录中,我们可以看到大量的edits文件和少量的fsimage文件,他们都是非常重要的元数据文件。fsimage文件是Hadoop文件系统元数据的一个永久性的检查点,保存了整个系统的命名空间、文件块的映射表和文件系统的配置。fsimage包含了所有的HDFS目录和文件idnode的序列化信息,对于文件来说,包含的信息有修改时间、访问时间、块大小和组成一个文件块信息等;而对于目录来说,包含的信息主要有修改时间、访问控制权限等信息。edits文件存放的是Hadoop文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到edits文件中。
fsimage和edits文件内存储的都是经过序列化的二进制数据,在NameNode启动的时候,进程会将fsimage文件中的内容加载到内存中,然后逐条执行edits文件中的记录,执行完成后,清空edits文件内容。之后客户端所有发起的文件更新操作都会同时记录在内存中的fsimage和磁盘上的edits文件中,也就是说,在任何时候,内存中的fsimage内容=磁盘中的fsimage文件内容+磁盘中的edits文件内容。那么为什么hdfs文件的修改不直接记录在磁盘的fsimage中呢?这是因为随着hdfs文件数目的增多,fsimage文件内容会变得非常巨大,假如每次更新操作都在fsimage中执行,那么执行效率是非常低下的。
2、fsimage和edits文件的合并(hadoop1)
面对不断增大的edits文件,如果不及时进行处理,那么每次在NameNode启动时会花费大量的时间来合并处理edits文件,导致HDFS集群启动非常慢。为了解决此问题,必须要有一个任务来不断地合并edits和fsimage文件,在hadoop1版本中,是由SecondaryNameNode节点来完成此工作的,而hadoop2是由HA的standby节点上的CheckpointerThread进程来执行的。
hadoop1中的SecondaryNameNode进程的功能是及时保存NameNode节点元数据信息,并定期将edits和fsimage文件进行合并,从而减少edits文件大小,缩短NameNode下次启动需要消耗的时间。出于性能和数据完整性考虑,通常将SecondaryNameNode配置在与NameNode不同的一台机子上,其合并工作流程如下图所示。
SecondaryNameNode工作流程图
1) SecondaryNamenode会定期的和NameNode通信,请求其停止使用edits文件,暂时将新的写操作写到一个新的文件edit.new上来,这个操作是瞬间完成,上层写日志的函数完全感觉不到差别;
这里的“定期”是由配置fs.checkpoint.period(还原点周期检查更新周期,默认3600s)和fs.checkpoint.size(edits文件允许的最大大小,默认64M)来决定的,当还原点周期时间到或者edits文件达到配置的最大大小时,SecondaryNameNode就会执行更新请求和操作。
2) SecondaryNamenode通过HTTP GET方式从NameNode上获取到fsimage和edits文件,并下载到本地的相应目录下(由fs.checkpoint.dir和fs.checkpoint.edits.dir决定);
3) SecondaryNamenode将下载下来的fsimage载入到内存,然后一条一条地执行edits文件中的各项更新操作,使得内存中的fsimage保存最新,这个过程就是edits和fsimage文件合并;
4) SecondaryNamenode执行完合并操作之后,会通过post方式将新的fsimage文件发送到NameNode节点上;
5) NameNode将从SecondaryNamenode接收到的新的fsimage替换旧的fsimage文件,同时将edit.new替换edits文件,一次合并工作完成。
3、fsimage和edits文件的合并(hadoop2)
在hadoop2版本中,SecondaryNameNod只存在伪分布模式下,集群模式已经取消了SecondaryNameNode角色。那么集群模式hadoop2的元数据合并工作是由谁完成的呢?
首先,在hadoop2中引入了NameNode HA的概念,每个HDFS集群中存在多个NameNode(目前版本是2个,以后版本相信会根据需要增加)。任何时候,只有一个NameNode进程是处于active状态的,另一个NameNode进程处于standby状态,而他们之间的元数据是通过奇数个JournalNode节点进行共享的。在standby节点上会运行一个叫做CheckpointerThread的线程,这个线程调用StandbyCheckpointer类的doWork()函数,而doWork函数会每隔一段时间就进行合并工作,相关代码如下:
1 try { 2 Thread.sleep(1000 * checkpointConf.getCheckPeriod()); 3 } catch (InterruptedException ie) { 4 } 5 6 public long getCheckPeriod() { 7 return Math.min(checkpointCheckPeriod, checkpointPeriod); 8 } 9 10 checkpointCheckPeriod = conf.getLong( 11 DFS_NAMENODE_CHECKPOINT_CHECK_PERIOD_KEY, 12 DFS_NAMENODE_CHECKPOINT_CHECK_PERIOD_DEFAULT); 13 14 checkpointPeriod = conf.getLong(DFS_NAMENODE_CHECKPOINT_PERIOD_KEY, 15 DFS_NAMENODE_CHECKPOINT_PERIOD_DEFAULT);
其中的checkpointCheckPeriod和checkpointPeriod变量是通过获取hdfs-site.xml中的dfs.namenode.checkpoint.period和dfs.namenode.checkpoint.check.period两个属性得到的。执行一次checkpoint的条件如下:
1 boolean needCheckpoint = false; 2 if (uncheckpointed >= checkpointConf.getTxnCount()) { 3 LOG.info("Triggering checkpoint because there have been " + 4 uncheckpointed + " txns since the last checkpoint, which " + 5 "exceeds the configured threshold " + 6 checkpointConf.getTxnCount()); 7 needCheckpoint = true; 8 } else if (secsSinceLast >= checkpointConf.getPeriod()) { 9 LOG.info("Triggering checkpoint because it has been " + 10 secsSinceLast + " seconds since the last checkpoint, which " + 11 "exceeds the configured interval " + checkpointConf.getPeriod()); 12 needCheckpoint = true; 13 }
当上述needCheckpoint被设置成true的时候,StandbyCheckpointer类的doWork()函数将会调用doCheckpoint()函数正式处理checkpoint。当fsimage和edits的合并完成之后,它将会把合并后的fsimage上传到Active NameNode节点上,Active NameNode节点下载完合并后的fsimage,再将本节点上旧的fsimage删掉,同时清除旧的edits文件。
合并步骤大概如下:
1) 配置好HA后,客户端所有的更新操作将会写到JournalNodes节点的共享目录中(通过dfs.namenode.shared.edits.dir和dfs.journalnode.edits.dir配置);
2) Active Namenode和Standby NameNode从JournalNodes的edits共享目录中同步edits到自己edits目录中;
3) Standby NameNode中的StandbyCheckpointer类会定期的检查合并的条件是否成立,如果成立会合并fsimage和edits文件,然后将合并之后的fsimage上传到Active NameNode相应目录中;
4) Active NameNode接到最新的fsimage文件之后,将旧的fsimage和edits文件清理掉,一个数据合并工作完成。
HDFS操作基本格式:“hadoop fs -命令”或“hdfs dfs -命令”,其中hdfs dfs是hadoop2版本推出的命令,此处只列举了一些常用命令,详细介绍及操作命令见《HDFS操作手册》。
1、查看文件,关键字ls
hadoop fs -ls / --查看HDFS根目录下的文件
hadoop fs -ls hdfs://hadoop:9000/ --hadoop fs -ls /的完整版写法,这里符号 / 为HDFS文件路径,默认表示hdfs://hadoop:9000/,是因为core-site.xml中配置了fs.default.name的值为hdfs://hadoop:9000/
hadoop fs -ls -R / --递归显示根目录下所有文件
hadoop fs -ls -h / --显示根目录下的文件,文件大小自动转化成合适的单位(M、K等)
显示的文件信息格式如下所示:
drwx------ - root supergroup 0 2014-07-23 03:48 /usr/local/hadoop/tmp/mapred/system
-rw------- 1 root supergroup 4 2014-07-23 03:48 /usr/local/hadoop/tmp
格式说明:① 第一列:drwx------,与linux的文件说明一致;② 第二列:目录为-,文件为数字,表示文件的副本数;③ 第三列:root,表示文件的创建者;④ 第四列:supergroup,表示文件所在组;⑤ 第五列:0,表示文件大小(单位byte),目录大小为0;⑥ 第六列:2014-07-23 03:48,表示最后修改时间;⑦ 第七列:/usr/local/hadoop/tmp/mapred/system,表示文件路径。
2、创建文件夹,关键字mkdir
hadoop fs -mkdir /hans --在根目录下创建文件夹hans
hadoop fs -mkdir hans --在当前用户路径(/user/用户名)下创建文件夹hans,使用root登录时,创建文件目录为/user/root/hans
在HDFS操作中,若文件夹或文件路径不带/,则默认表示为用户路径,即/user/用户名/...
3、上传文件,关键字put
格式:hadoop fs -put <linux source> <hdfs destination>
hadoop fs -put /root/install.log /hans --将本地文件/root/install.log上传到HDFS的目录/hans中,若/hans文件夹不存在,则默认表示将install.log文件重命名为hans,非文件夹
注:①同名文件不允许上传;②若保存的HDFS文件夹不存在,则表示将上传的文件重命名为文件夹名;③当目的路径不带/时,默认表示用户路径/user/当前用户
4、下载文件,关键字get
格式:hadoop fs -get <hdfs source> <linux destination>
hadoop fs -get /hans/install.log --将HDFS文件/hans/install.log下载到linux当前目录下
5、在线查看文件,关键字text、cat或tail
hadoop fs -text /hans/install.log --在线查看文件/hans/install.log
hadoop fs -cat /hans/install.log --在线查看文件/hans/install.log
hadoop fs -tail /hans/install.log --在线查看文件的最后1000个字节
6、删除文件,关键字rm
hadoop fs -rm /hans/install.log --删除HDFS文件/hans/install.log
hadoop fs -rm -r /hans --递归删除HDFS文件夹/hans及/hans下的所有文件
hadoop fs -rm -r hdfs://hadoop:9000/* --递归删除HDFS根目录下所有文件,其中hadoop为主机名
7、查看帮助文档,关键字help
hadoop fs -help --查看hadoop fs所有帮助命令
hadoop fs -help ls --查看ls的命令帮助
8、合并MR输出文件,关键字getmerge
格式:hadoop dfs -getmerge <hdfs souce> <linux destination>
hadoop dfs -getmerge /out /root/Download/output --将mapreduce的输出HDFS文件/out合并并复制到本地文件系统/root/Download/output中
1、默认配置文件路径
hdfs-default.xml:hadoop-2.6.0-srchadoop-hdfs-projecthadoop-hdfssrcmain esources
2、常用配置(hadoop2.6.0)
▶ dfs.namenode.name.dir(hadoop1对应配置是:dfs.name.dir),配置NameNode元数据的存储路径,可通过“,”间隔配置多个路径,如:/name1/dfs/name,/name2/dfs/name,/name3/dfs/name。此外,为确保元数据不丢失,建议配置一个NFS路径。
▶ fs.trash.interval,配置HDFS回收站功能,值为回收站自动清空的时间(分钟)。当用户通过HDFS命令删除数据后,首先会移动到Trash回收站中,默认路径:/user/{用户}/.Trash;
在Java中操作HDFS都是通过org.apache.hadoop.fs.FileSystem类来实现的,修改HDFS服务端文件是否需要关闭权限验证,即在hdfs-site.xml中配置dfs.permissions.enabled(hadoop1对应为:dfs.permissions)为false。
示例代码如下(完整代码点此下载):
1 package com.hicoor.hadoop.hdfs; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.OutputStream; 8 import java.net.URI; 9 import java.net.URISyntaxException; 10 11 import org.apache.hadoop.conf.Configuration; 12 import org.apache.hadoop.fs.FSDataInputStream; 13 import org.apache.hadoop.fs.FSDataOutputStream; 14 import org.apache.hadoop.fs.FileSystem; 15 import org.apache.hadoop.fs.Path; 16 import org.apache.hadoop.io.IOUtils; 17 import org.junit.Before; 18 import org.junit.Test; 19 20 public class HDFSDemo { 21 22 FileSystem fileSystem = null; 23 24 @Before 25 public void init() throws IOException, URISyntaxException { 26 fileSystem = FileSystem.get(new URI("hdfs://hadoop0:9000/"),new Configuration()); 27 System.setProperty("hadoop.home.dir", "D:/hadoop-2.6.0"); 28 } 29 30 @Test 31 public void uploadFile() throws IOException, 32 FileNotFoundException { 33 FSDataOutputStream out = fileSystem.create(new Path("/hadoop2.6(x64)V0.2.zip")); 34 FileInputStream in = new FileInputStream("D:\desktop\hadoop2.6(x64)V0.2.zip"); 35 IOUtils.copyBytes(in, out, 4096, true); 36 } 37 38 @Test 39 public void downloadFile() throws IOException, 40 FileNotFoundException { 41 FSDataInputStream in = fileSystem.open(new Path("/jdk7")); 42 OutputStream out = new FileOutputStream("D:\desktop\jdk1.7"); 43 IOUtils.copyBytes(in, out, 4096, true); 44 } 45 46 @Test 47 public void deleteFile() throws IllegalArgumentException, IOException { 48 fileSystem.delete(new Path("/cata"), true); 49 } 50 51 @Test 52 public void makeDir() throws IllegalArgumentException, IOException { 53 fileSystem.mkdirs(new Path("/cata")); 54 } 55 56 }
更多资料等待进一步研究....