代码改变世界

Hadoop学习(2)-- HDFS

2014-10-08 09:51  hduhans  阅读(955)  评论(0编辑  收藏  举报

  随着信息技术的高度发展,数据量越来越多,当一个操作系统管辖范围存储不下时,只能将数据分配到更多的磁盘中存储,但是数据分散在多台磁盘上非常不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,因此诞生了分布式文件系统。HDFS(Hadoop Distribute File System)是一种能运行在通用硬件上的分布式文件系统,具有高度容错的特点,适合部署在廉价的机器上。

由于hadoop1和hadoop2版本差异较大,本文以下部分如未标记特殊说明则默认指的是hadoop2版本

HDFS基本概念

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不支持并发写的情况,且不适合小文件

NameNode元数据解析

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无法使用
View Code

   在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.dirfs.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);
View Code

   其中的checkpointCheckPeriod和checkpointPeriod变量是通过获取hdfs-site.xml中的dfs.namenode.checkpoint.perioddfs.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 }
View Code

   当上述needCheckpoint被设置成true的时候,StandbyCheckpointer类的doWork()函数将会调用doCheckpoint()函数正式处理checkpoint。当fsimage和edits的合并完成之后,它将会把合并后的fsimage上传到Active NameNode节点上,Active NameNode节点下载完合并后的fsimage,再将本节点上旧的fsimage删掉,同时清除旧的edits文件。

   合并步骤大概如下:

   1) 配置好HA后,客户端所有的更新操作将会写到JournalNodes节点的共享目录中(通过dfs.namenode.shared.edits.dirdfs.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操作命令

   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中

HDFS运行配置

1、默认配置文件路径

   hdfs-default.xml:hadoop-2.6.0-src\hadoop-hdfs-project\hadoop-hdfs\src\main\resources\

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

   在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 }
View Code

   更多资料等待进一步研究....