HDFS全称Hadoop Distributed File System,是Hadoop的一套开创性的数据存储方案,人们天天吹牛逼包装出来的Data Lake(数据湖)的一种,其实说白了就是一种分布式文件系统,什么是文件系统,其实就是针对一块磁盘进行划分成一小块一小块的block来存储文件,文件也被切割成一个一个的小块存放在这些block里面,文件像萝卜,磁盘块像坑,而文件最后一块如果小于一个磁盘块的空间,就会形成磁盘碎片,所以你的电脑有整理磁盘碎片的概念,而针对磁盘划分块的规则不同,就形成了不通的文件系统,有的块大,有的块小,有的有序存储,有的无序,有的圆有的方……但最终目的都是奔着对磁盘的利用率和寻址速度去的。

HDFS基础

机型

  HDFS 是设计为用于运行在大量普通商用机集群上的,但普通商用机并非指个人PC,普通商用机也是服务器,只是不推荐用那种高性能的服务器(市场价大于100万的);

集群架构

  HDFS是主从架构,即NameNode与DataNode两部分组成,以HA模式为例就是两个NameNode,一个active另一个standby,NameNode主要负责HDFS里面的文件的元数据和DataNode的主机情况和工作情况以及发送任务指令;DataNode在一个集群中一般会有若干个,主要负责数据的具体存储和任务具体执行;相对于主从架构,还有其他的架构,典型的代表就是MPP,大家可以自行了解下。

基本存储单元

  对于HDFS来说,Block就是基本存储单元,hadoop1.X默认是64M,hadoop2.X起默认的是128M,可以修改hdfs-site.xml配置文件,增加全局参数dfs.block.size来改变块的大小,但是块的大小不是胡乱设置的,block太小,不利于寻址速度(找到磁盘内文件的位置),也加重了NameNode存储元数据的负担,block太大,则会导致NameNode接收DataNode的数据耗时过长,容易造成误判点死亡节点;Block大小也会影响MapReduce,这是后话。

HDFS数据存储位置和备份原则

  HDFS特点之一就是高可用的容错机制,即最大化数据的可靠性与可用性,最优化的利用了网络带宽资源,采用的原理就是在多个DataNode上多副本存储复制,保证namenode检测到即使其中一个副本损坏,另外的副本也能提供辅助以及再复制出新的副本出来,同时也要节约网络带宽费用和考虑到客户的相应时间,不可能随心所欲的全世界各地备份来达到所谓的绝对安全,Client在存取数据时会采用就近原则,设计原理以客户机为例子可以定义一下几种距离情况:

  • Client本身是其中一个DataNode,那恰好本身又有数据备份,此时Client到Data的距离可定义为0
  • Client 同一机架上不同节点上的存储数据 Data 距离为 3
  • Client 同一数据中心不同机架上的存储数据 Data 距离为 6
  • Client 不同数据中心的节点 Data 距离为 9
    所以综合考虑网络传输速率和数据的容错性,HDFS 数据存放策略采用同节点与同机架并行的存储方式。默认备份数是3份(可设置dfs.replication属性调整),在Client当前节点上存放第一个副本,第二个副本存放在于第一个副本不同的机架上的节点,第三个副本放置的位置与第二个副本在同一个机架上而非同一个节点。无特殊要求,一般也不会跨数据中心,如图 1 所示,这也就意味着 大数据最贵的是网络带宽,然后是计算资源,最后是资源储存,所以没有灾难性后果的情况下,大数据允许牺牲磁盘存储的冗余来减轻带宽压力的。

图1 备份数和节点分布关系

HDFS读取/写入过程详述

  HDFS数据的交互主要通过网络进行,具体交互程序可分为两种,读取和写入,先讲读取,流程如图2;

(1) 客户端或者用户通过调用 FileSystem 对象的 open()方法打开需要读取的文件;

(2) FileSystem 通过远程协议调用 NameNode 确定文件的前几个 Block 的位置。对于每一个 Block, NameNode 返回一含有那个 Block 拷贝的“元数据”,即文件基本信息;接下来,DataNode 按照上文定义的距离值进行排序,如果 Client 本身就是一个 DataNode,那么优先从本地 DataNode 节点读取数据。 HDFS 实例做完以上工作后,返回一个 FSDataInputStream给客户端,从FSDataInputStream 中读取数据。 FSDataInputStream 接着包装一个DFSInputStream,用来管理 DataNode 和 NameNode 的 I/O。

( 3) NameNode 向客户端返回一个包含数据信息的地址, 客户端根据地址创建一个FSDataInputStream 开始对数据进行读取。

( 4) FSDataInputStream 根据开始时存放的前几个 Blocks 的 DataNode 的地址,连接到最近的 DataNode 上对数据开始从头读取。客户端反复调用 read()方法,以流式方式从DataNode 读取数据。

( 5)当读到 Block 的结尾的时候, FSDataInputStream 会关闭到当前 DataNode 的链接,然后查找能够读取下一个 Block 的最好的 DataNode。这些操作对客户端是透明的,客户端感觉到的是连续的流,也就说读取的时候就开始查找下一个块所在的地址。

( 6)读取完成调用 close()方法,关闭 FSDataInputStream。

以上就是 HDFS 对数据进行读取的整个流程。对于错误处理来说,在读取期间,当 Client 与 DataNode 通信的时候如果发生错误的话,它会尝试读取下个紧接着的含有那个 Block 的 DataNode。 Client 会记住发生错误的 DataNode,

这样它就不必在读取以后的块的时候再尝试这个 DataNode 了。 Client 也验证从 DataNode 传递过来的数据的 checksum。如果错误的 Block 被发现,它将尝试从另一个 DataNode 读取数据前被报告给 NameNode。这个设计的一个重要方面是客户端可以通过 DataNodes 直接接收数据,并且客户端被NameNode 导向包含每块数据的最佳 DataNode。这样的设计可以使 HDFS 自由扩展而适应大量的客户端,因为数据传输线路是通过集群中的所有的 DataNode, NameNode 只需要提供相应块的位置查询服务即可(而 NameNode 是将块的位置信息存放在内存中的,这样效率就非常高), NameNode 不需要提供数据服务,因为数据服务随着客户端的增加将很快成为瓶颈

HDFS在大数据处理中的应用与实践_HDFS在大数据处理中的应用与实践


图2 hdfs读取流程   HDFS的写入流程如图3 (1) Client 通过调用 FileSystem 的 create()方法来请求创建文件 (2) FileSystem 通过对 NameNode 发出远程请求,在 NameNode 里面创建一个新的文件,但此时并不关联任何的块。 NameNode 进行很多检查来保证不存在要创建的文件已经存在于文件系统中, 同时检查是否有相应的权限来创建文件。如果这些检查都完成了,那么NameNode 将记录下来这个新文件的信息。FileSystem 返回一个 FSDataOutputStream 给客户端用来写入数据。和读的情形一样, FSDataOutputStream 将包装一个 DFSOutputStream 用于和 DataNode 及 NameNode 通信。 而一旦文件创建失败,客户端会收到一个 IOExpection, 标示文件创建失败,停止后续任务。 (3)客户端开始写数据。 FSDataOutputStream 把要写入的数据分成包的形式,将其写入到中间队列中。 其中的数据由 DataStreamer 来读取。 DataStreamer 的职责是让 NameNode分配新的块——通过找出合适的 DataNode——来存储作为备份而复制的数据。这些DataNode 组成一个流水线,我们假设这个流水线是个三级流水线,那么里面将含有三个节点。 此时, DataStreamer 将数据首先写入到流水线中的第一个节点。此后由第一个节点将数据包传送并写入到第二个节点,然后第二个将数据包传送并写入到第三个节点。 ( 4) FSDataOutputStream 维护了一个内部关于 packets 的队列,里面存放等待被DataNode 确认无误的 packets 的信息。这个队列称为等待队列。一个 packet 的信息被移出本队列当且仅当 packet 被流水线中的所有节点都确认无误 (5)当完成数据写入之后客户端调用流的 close 方法,在通知 NameNode 完成写入之前,这个方法将 flush 残留的 packets,并等待确认信息(acknowledgement)。 NameNode 已经知道文件由哪些块组成(通过DataStream 询问数据块的分配),所以它在返回成功前只需要等待数据块进行最小值复制。关于写入数据的时候 DataNode 发生错误的处理过程如下:发现错误之后,首先关闭流水线,然后将没有被确认的数据放到数据队列的开头,当前的块被赋予一个新的标识,这信息将发给 NameNode,以便在损坏的数据节点恢复之后删除这个没有被完成的块。然后从流水线中移除损坏的 DataNode。之后将这个块剩下的数据写入到剩下的两个节点中。NameNode 注意到这个块的信息还没有被复制完成,他就在其他一个 DataNode 上安排复制。 接下来的 Block 写入操作就和往常一样了。

HDFS在大数据处理中的应用与实践_hadoop2_02


图3 hdfs写入流程

HDFS常用命令行

常用命令,其实HDFS的命令跟shell文件操作的命令很像,只是规范不一样,多练习以下就熟练了,可以通过hadoop fs指令查看所有的指令(fs是file system的缩写),这里列几个常用的,另外hdfs 1.X和hdfs 2.X后规范略有不同,这里以2.X后为例:

1.-mkdir 新建hdfs上的文件夹
hadoop fs -mkdir /user     #新建/usr目录,看个人习惯,我喜欢这种类型
hadoop fs -mkdir hdfs:/node1/DEV #新建/DEV目录

2.-touchz 新建一个file,不常用,为啥呢,因为hdfs不喜欢直接修改文件内容,也就意味新建个空文件意义不大
hadoop fs -touchz /DEV/hello.c #新建/DEV/hello.c 文件

3.-ls 查看文件
hadoop fs -ls /     #查看hdfs根目录下的目录
hadoop fs -ls /DEV #查看/DEV下的目录

4.-cat         readonly hdfs某文件的具体内容
hadoop fs -cat /usr/hello.txt  #获取/usr/hello.txt的内容

5.-cp 局限于hdfs文件系统内部cp,不能从client copy到hdfs,也不能从hdfs copy到client
hadoop fs -cp /usr/hello.txt /DEV #把hdfs上的/usr/hello.txt  copy到/DEV下

6.-copyFromLocal 注意大小写,将client本地文件上传hdfs
hadoop fs -copyFromLocal /home/mytest/hello.txt /file  #将client本地的/home/mytest/hello.txt文件上传到 hdfs的/file 目录下

7.-put 将client本地文件上传hdfs
hadoop fs -put /home/dogs.c /DEV #效果同-copyFromLocal


8.-copyToLocal 注意大小写,将hdfs的文件下载到client本地
   fs -copyToLocal /usr/hello.txt /home/

9.-get 将hdfs的文件下载到client本地
hadoop fs -get /usr/hello.txt /home #同 fs -copyToLocal /usr/hello.txt /home/


10.-du 显示文件或目录大小,当是目录时,显示该目录下所有文件的大小,单位是B,-ls也会列出大小,所以也不常用
hadoop fs -du /usr #显示/usr文件下所有的文件大小
hadoop fs -du /usr/hadoop-2.8.5.tar.gz  #显示/usr/hadoop-2.8.5.tar.gz文件大小

12.-mv 移动hdfs的文件位子,也可以用来做rename
hadoop fs -mv /DEV/dogs.c /DEV/cat.c

13.-rm 删除文件和目录
hadoop fs -rm -r /file  #删除/file

14.-test
hadoop fs -test -[ezd] /DEV/hello.txt #检测文件是否存在
HDFS网页浏览文件

登录自己配置的http端口,可以在网页上浏览hdfs和logs,如图4。

HDFS在大数据处理中的应用与实践_HDFS在大数据处理中的应用与实践_03


图4 网页上访问hdfs

HDFS访问权限设置

HDFS的权限跟linux文件系统的权限很像,采用了POSIX模型,但是没有setuid和setgid,学习的时候我们就先保持简单的默认simple认证,或者干脆在hdfs-site.xml把密码认证关掉,如下下xml配置,至于后面PROD可能会采用的Kerberos模式认证,是个较大工程,后续再说,如果采用simple认证的话,hdfs只人你的username,如果在hdfs你的username有啥权限,hdfs就会给你什么权限,极其容易被冒名顶替,如果关闭权限认证,则完全不认证username和password。

<property>
<name>dfs.permissions</name>
<value>false</value>
</property>
HDFS的正统开发——eclipse开发
  1. 利用 hadoop-eclipse-plugins-2.8.5.jar控件来操控hdfs
      关于如何编译 hadoop-eclipse-plugins-2.8.5.jar包,可以参考玉老师的Windows环境编译 hadoop-eclipse-plugins-2.8.5.jar 文件。也可以直接网找自己hadoop版本的资源,然后把这个jar放在…\eclipse\dropins\plugins\目录下,即自己安装的eclipse目录的\dropins\plugins\下,缺少哪个目录就自己手动建立起来,然后重启eclipse windows客户端,在客户端执行如图5的数字顺序操作后,在图6的配置好hdfs的连接信息,locationname随意填写,合理即可,dfs的host会默认到前面Map/Reduce的host node1,port写的是自己hadoop集群的hdfs rpc协议的端口,配置准确后,就可以通过图7可视化界面操作hdfs了。

图5 新建hdfs配置连接

HDFS在大数据处理中的应用与实践_HDFS在大数据处理中的应用与实践_04


图6 填写配置信息

HDFS在大数据处理中的应用与实践_HDFS在大数据处理中的应用与实践_05


图7 可视化界面操作hdfs

  1. java代码操控hdfs
    a)下载一份hadoop-2.8.5.tar.gz在windows本地,并解压,配置环境变量,如图8,新增系统变量HADOOP_HOME,值就写刚刚本地解压出来的hadoop目录,同时在已存在系统变量Path上追加值%HADOOP_HOME%\bin,最后保存;

图8 配置hadoop的环境变量 b)在eclipse上新建一个Java项目hadoop_test,导入要用到的hadoop jar包,鼠标选中项目hadoop_test —> 鼠标右键Build Path —> Configure Build Path —> libraries —> Add External JARs,导入windows本地解压的hadoop-2.8.5里面包含的如下jar包(*代表全部):

hadoop-2.8.5\share\hadoop\common\hadoop-common-2.8.5.jar
hadoop-2.8.5\share\hadoop\common\lib\*
hadoop-2.8.5\share\hadoop\hdfs\hadoop-hdfs-2.8.5.jar
hadoop-2.8.5\share\hadoop\hdfs\lib\*
导入成功后,可以看到在hadoop_test项目的Referenced Libraries下面看到新导入的jar包;然后最好把集群上的core-site.xml和hdfs-site.xml文件也导入到该项目内,因为后面编程用到hdfs的配置属性时,方便查找,导入方式,在项目内新建一个files的文件夹,然后鼠标右键 —> Import —> 选xml type,把两个xml导进来。

c)在src目录下新建一个package,叫com.hdfsdev吧,具体实现的hdfs的增删查改板块我就只写一个上传文件的样例,其他的功能可以参考gznc_pcc的博客的
Hadoop入门之eclipse对HDFS的常用操作
,写的很详细,就是有些重复的工作没有抽出来封装成一块函数,有点背离面向对象的宗旨吧,哈哈;
上传文件样例,在package com。hdfsdev下新建一个MkdirHdfsFile class,具体代码如下:

package com.hdfsdev;

import java.net.URI;

import javax.imageio.IIOException;
import javax.naming.InitialContext;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class MkdirHdfsFile {
	FileSystem fs = null;

	public void init() throws Exception {
		Configuration conf = new Configuration();  //新建一个环境变量的对象
		fs = FileSystem.get(new URI("hdfs://node1:8020"), conf, "root"); //参数1的node1是active的namenode hostname,也可以是ip,8020是配置的hdfs rpc协议端口,可以查阅hdfs-site.xml,第二个参数conf为环境变量对象,第三个参数root为hdfs的用户名,用于集群权限认证

	}

	public void testAddFileToHdfs() {

		try {
		    //第一步真实应用场景需要先check要上传文件的属性,如文件是否存在本地,多大,什么格式,几行数据等重要属性,这里就偷懒了
			Path src = new Path("D:\\rs_etl_area.txt");  //本地上传的source文件
			Path dst = new Path("/DEV"); //hdfs上的target文件夹
			fs.copyFromLocalFile(false, src, dst); //上传文件copyFromLocalFile
			//copyFromLocalFile步骤操作后,真实应用场景需要先check上传后的文件的属性,如文件是否存在hdfs上了,多大,什么格式,几行数据等重要属性,需要和上传前去对比检测data quality,保证数据上传hdfs的可靠性, 这里就偷懒了;
			System.out.println("unload successful!");
			fs.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

}

然后主函数引用代码如下:

package com.hdfsdev;

public class filehdfs {
	public static void main(String[] args) throws Exception {
		MkdirHdfsFile myMkdirHdfsFile=new MkdirHdfsFile();
		myMkdirHdfsFile.init();
		myMkdirHdfsFile.testAddFileToHdfs();
	}

}

操作完后,Run As(最好选择 2 Run on Hadoop) main函数, ,有bug排查bug,输出的WARN基本都可以忽略,特殊的一些WARN除外,成功的话会输出“unload successful”,如图9

HDFS在大数据处理中的应用与实践_hdfs2_06


图9 运行结果 最后,就可以在hdfs上人工去验证下是否上传成功,如图10,11,可以分别在eclipse上或网页上查看。

HDFS在大数据处理中的应用与实践_hadoop2_07


图10 eclipse利用插件人工验证

HDFS在大数据处理中的应用与实践_eclipse_08


图11 浏览器http协议人工验证