HDFS是一个分布式文件系统,具有良好的扩展性、容错性以及易用的API。核心思想是将文件切分成等大的数据块,以多副本的形式存储到多个节点上。HDFS采用了经典的主从软件架构,其中主服务被称为NameNode,管理文件系统的元信息,而从服务被称为DataNode,存储实际的数据块,DataNode与NameNode维护了周期性的心跳,为了防止NameNode出现单点故障,HDFS允许一个集群中存在主备NameNode,并通过ZooKeeper完成Active NameNode的选举工作。HDFS提供了丰富的访问方式,用户可以通过HDFS shell,HDFS API,数据收集组件以及计算框架等存取HDFS上的文件。
在大数据场景中,存在两种解决方案:纵向扩展(scale-up)和横向扩展(scale-out)。
纵向扩展利用现有的存储系统,通过不断增加存储容量来满足数据增长的需求;
横向扩展则是以网络连接的节点为单位扩大存储容量(集群)。
由于纵向扩展存在价格昂贵、升级困难以及总存在物理瓶颈等问题,大数据领域通常会采用横向扩展方案。横向扩展的难点在于如何构建一个分布式文件系统,解决以下这些问题。
基于现有文件系统的主从架构(Master/Slaves):给定N个网络互联的节点,每个节点上装有linux操作系统,且配有一定量的内存和硬盘,选出一个节点作为Master,记录文件的元信息,其他节点作为Slave,存储实际的文件。为了确保数据的可靠性,将每个文件保存到三个不同节点上。
当客户端(Client)需要写入一个文件时,首先与Master通信,获取文件存放节点列表,如果该文件是合法的(比如不存在重名文件等),则Master根据一定的负载均衡策略将三个节点位置信息发回客户端,这时客户端与这三个Slave节点建立网络连接,将文件写入对应的三个节点,读文件过程类似。
该系统从一定程度上能够解决分布式存储问题,但存在以下两个不足:
1)难以负载均衡:该分布式文件系统以文件为单位存储数据。由于用户的文件大小往往是不统一的,难以保证每个节点上的存储负载是均衡的。
2)难以并行处理:一个好的分布式文件系统不仅能够进行可靠的数据存储,还应考虑如何上层计算引擎高效的分析。由于数据是以文件为单位存储的,当多个分布在不同节点上的任务并行读取一个文件时,会使得存储文件的节点出口网络带宽成为瓶颈,从而制约上层计算框架的并行处理效率。
为了解决文件级别分布式系统存在的不足,块级别的分布式文件系统出现了,这类系统核心思想时将文件分成等大的数据块(128MB),并以数据块为单位存储到不同节点上,进而解决文件级别的分布式系统存在的负载均衡和并行处理问题。
HDFS采用了主从架构,主节点被称为NameNode,只有一个,管理元信息和所有从节点,从节点称为DataNode,通常存在多个存储实际的数据块,HDFS各组件功能如下:
NameNode是HDFS集群管理者,负责管理文件系统元信息和所有DataNode。
1)管理元信息:NameNode维护着整个文件系统的目录树,各个数据块信息等。
2) 管理DataNode:DataNode周期性向NameNode汇报心跳以表明自己活着,一旦NameNode发现某个DataNode出现故障,会在其他存活DataNode上重构丢失的数据块。
一个HDFS集群中只存在一个对外服务的NameNode,称为Active NameNode,为了防止单个NameNode出现故障后导致整个集群不可用,用户可启动一个备用NameNode,称为Standby NameNode,为了实现NameNode HA(High Availability,高可用),需解决好两者的切换和状态同步问题。
1)主/备切换:HDFS提供了手动方式和自动方式完成主备NameNode切换,手动方式是通过命令显示修改NameNode角色完成的,通常用于NameNode滚动升级。自动模式是通过ZooKeeper实现的,可在主NamNode不可用时,自动将备用NameNode提升为主NameNode,以保证HDFS不间断对外提供服务。
2)状态同步:主/备NameNode并不是通过强一致协议保证状态一致的,而是通过第三方的共享存储系统。主NameNode将EditLog(修改日志,比如创建和修改文件)写入共享存储系统,备用NameNode则从共享存储系统中读取这些修改日志,并重新执行这些操作,以保证与主NameNode的内存信息一致。
目前HDFS支持两种共享存储系统:NFS(Network File System)和QJM(Quorum Journal Manager),其中QJM是HDFS内置的高可用日志存取系统,其基本原理是用2N+1台JournalNode存储EditLog,每次写数据操作大多数(大于等于N+1)返回成功确认即认为该次写成功,该算法所能容忍的是最多有N台机器挂掉,QJM能够构建在普通商用机器之上,比NFS更加廉价,因此受众更广。
DataNode存储实际的数据块,并周期性通过心跳向NameNode汇报自己的状态信息。
用户通过客户端与NameNode和DataNode交互,完成HDFS管理(比如服务启动与停止)和数据读写等操作。文件的分块操作也是在客户端完成的。
当向HDFS写入文件时,客户端首先将文件切分成等大的数据块(默认一个数据块大小为128MB),之后从NameNode上领取三个DataNode地址,并在它们之间建立数据流水线,进而将数据块流式写入这些节点。
NameNode Federation机制:
随着数据块和访问量的增加,单个NameNode会成为制约HDFS扩展性的瓶颈,为了解决该问题,HDFS提供了NameNode Federation机制,允许一个集群中存在多个对外服务的NameNode,各自管理目录树的一部分(对目录水平分片)。需要注意的是,在NameNode Federation中,每个主NameNode均存在单点故障问题,需为之分配一个备用NameNode。为了向用户提供统一的目录命名空间,HDFS在NameNode Federation之上封装了一层文件系统视图ViewFs,可将一个统一的目录命名空间映射到多个NameNode上。
HDFS内置了良好的容错性设计策略,以降低各种故障情况下数据丢失的可能性。
数据块副本放置策略直接决定了每个数据块多个副本存放节点的选择,在保证写性能较优的情况下,尽可能提高数据的可靠性。
一个集群由多个机架构成,每个机架由16~64个物理节点组成,机架内部的节点是通过内部交换机通信的,机架之间的节点是通过外部节点通信的。
HDFS默认采用的三副本放置策略:
HDFS允许用户将一部分目录或文件缓存在off-heap内存中,以加速对这些数据的访问效率,该机制被称为集中式缓存管理,引入带来了许多显著的优势:
1)提高集群的内存利用率。当使用操作系统的缓存时,对一个数据块的重复读会导致所有副本都会被放到缓冲区当中,造成内存浪费;当使用集中式缓存时,用户可以指定n个副本中的m个被缓存,可以节约n-m的内存。
2)防止那些被频繁使用的数据从内存中清除。
3)提高数据读取效率:
HDFS提供了多种访问方式,包括HDFS Shell、HDFS API、数据收集组件(比如flume、Sqoop等)以及上层计算框架等。
HDFS提供了两类shell命令:用户命令和管理员命令。
1)用户命令
HDFS提供了文件操作命令dfs、文件一致性检查命令fsck、分布式文件复制命令distcp.
在HDFS创建目录/user/input
bin/hdfs dfs -mkdir -p /user/input
2)管理员命令
管理员命令主要是针对服务生命周期管理的,比如启动/关闭NameNode/DataNode等
sbin/hadoop-daemon.sh start namenode
sbin/hadoop-daemon.sh stop namenode
HDFS对外提供了丰富的编程API,允许用户使用Java、python等语言(Thrift)编写应用程序访问HDFS。
Sqoop:Sqoop允许用户指定数据写入HDFS的目录、文件格式(支持Text和SequenceFile两种格式)、压缩方式等
1)上层计算框架可通过InputFormat和OutputFormat两个可编程组件访问HDFS上存放的文件,其中InputFormat能够解析输入文件,将之逻辑上划分成多个可并行处理的InputSplit,并进一步将每个InputSplit解析成一系列key/value对;OutputFormat可将数据以指定的格式写入输出文件。
Hadoop为常见的数据存储格式分别设计了InputFormat和OutputFormat实现,以方便MapReduce、Spark等上层计算框架使用,比如Text文件的实现是TextInputFormat和TextOutputFormat,SequenceFile的实现是SequenceFileInputFormat和SequenceFileOutputFormat。
2)另一种访问HDFS数据的方式是SQL、HIVE、Impala等查询引擎均允许用户直接使用SQL访问HDFS中存储的文件。
<configuration>
<property>
<name>dfs.replicationname>
<value>3value>
<description>分片数量description>
property>
<property>
<name>dfs.nameservicesname>
<value>yfclustervalue>
<description>为namenode集群定义一个services namedescription>
property>
<property>
<name>dfs.ha.namenodes.yfclustername>
<value>nn1,nn2value>
<description>nameservice包含哪些namenode,为各个namenode起名description>
property>
<property>
<name>dfs.namenode.rpc-address.yfcluster.nn1name>
<value>kafka1:8020value>
<description> 名为nn1的namenode 的rpc地址和端口号,rpc用来和datanode通讯description>
property>
<property>
<name>dfs.namenode.rpc-address.yfcluster.nn2name>
<value>kafka2:8020value>
<description> 名为nn2的namenode 的rpc地址和端口号,rpc用来和datanode通讯description>
property>
<property>
<name>dfs.namenode.http-address.yfcluster.nn1name>
<value>kafka1:50070value>
<description> 名为nn1的namenode 的http地址和端口号,web客户端description>
property>
<property>
<name>dfs.namenode.http-address.yfcluster.nn2name>
<value>kafka2:50070value>
<description> 名为nn2的namenode 的http地址和端口号,web客户端description>
property>
<property>
<name>dfs.namenode.secondary.http-addressname>
<value>kafka2:50090value>
<description>命名空间和事务在本地文件系统永久存储的路径description>
property>
<property>
<name>dfs.namenode.shared.edits.dirname>
<value>qjournal://kafka1:8485;kafka2:8485;kafka3:8485/yfclustervalue>
<description>namenode间用于共享编辑日志的journal节点列表description>
property>
<property>
<name>dfs.journalnode.edits.dirname>
<value>/moudle/hadoop3/hdfs/journaldatavalue>
<description>journalnode上用于存放edits日志的目录description>
property>
<property>
<name>dfs.namenode.name.dirname>
<value>/moudle/hadoop3/hdfs/namevalue>
<description>在本地文件系统所在的NameNode的存储空间和持续化处理日志description>
property>
<property>
<name>dfs.datanode.data.dirname>
<value>/moudle/hadoop3/hdfs/datavalue>
<description>数据节点的块本地存放目录description>
property>
<property>
<name>dfs.ha.fencing.methodsname>
<value>sshfencevalue>
<description>配置隔离机制description>
property>
<property>
<name>dfs.ha.fencing.ssh.private-key-filesname>
<value>/root/.ssh/id_rsavalue>
<description>密钥认证文件description>
property>
<property>
<name>dfs.ha.automatic-failover.enabledname>
<value>truevalue>
<description>是否开启自动故障转移。建议开启,truedescription>
property>
<property>
<name>dfs.client.failover.proxy.provider.yfclustername>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvidervalue>
<description>客户端连接可用状态的NameNode所用的代理类description>
property>
<property>
<name>dfs.webhdfs.enabledname>
<value>truevalue>
property>
<property>
<name>dfs.journalnode.http-addressname>
<value>0.0.0.0:8480value>
property>
<property>
<name>dfs.journalnode.rpc-addressname>
<value>0.0.0.0:8485value>
property>
<property>
<name>dfs.permissions.enabledname>
<value>truevalue>
property>
<property>
<name>dfs.namenode.acls.enabledname>
<value>truevalue>
property>
<property>
<name>fs.permissions.umask-modename>
<value>032value>
property>
configuration>
<property>
<name>fs.defaultFSname>
<value>hdfs://yfclustervalue>
property>
<property>
<name>hadoop.tmp.dirname>
<value>/moudle/hadoop3/tmpvalue>
property>
<property>
<name>ha.zookeeper.quorumname>
<value>kafka2:2181,kafka3:2181value>
property>
<property>
<name>hadoop.http.staticuser.username>
<value>rootvalue>
property>
<property>
<name>dfs.permissions.enabledname>
<value>falsevalue>
property>
configuration>