本篇博文主要讲讲HDFS的一些基础性的原理以及应用。
HDFS:Hadoop Distributed File System
HDFS概述
HDFS 优点:
高容错性
- 数据自动保存多个副本(不同的副本放在不同的节点上)
- 副本丢失后,自动恢复
适合批处理
适合大数据处理
- GB、TB、甚至PB级数据
- 百万规模以上的文件数量
- 10K+节点规模
流式文件访问
- 一次性写入,多次读取(只能一次写入,不能修改已有文件,只能追加)
- 保证数据一致性(多个副本)
可构建在廉价机器上
HDFS 缺点:
不适合低延迟数据访问
- 比如毫秒级
- 低延迟与高吞吐率
追求的是高吞吐率,牺牲的是低延迟,是一个折中的办法。
不太适合小文件存取
- 占用NameNode大量内存
每个文件都有一些元信息,比如这个文件在哪个节点上,有哪些block构成等,小文件太多会有很多元信息,而这些元信息会占用大量的内存。
- 寻道时间超过读取时间
例如:拷贝一个1G的文件比拷贝1G的许多小文件要快得多。当存在许多小文件时,大量时间都花在寻道上面。
不支持并发写入、文件随机修改
HDFS基本架构与原理
HDFS 设计思想

一个文件传到集群中,先把这个文件切分成等大,这里假设是64MB的block。然后以块为单位存到不同的节点上。再用两张表来记录,第一张表记录这个文件由多少block组成,注意这个有先后顺序;第二张表记录每个block在哪些节点上。有了这两张表就很容易追踪这个文件是怎样存的。用户是感知不到文件的切分和拼装。
这种存储方式有两点好处:
①很容易做负载均衡:块是等大的,一个节点最多比其他节点多存一个block。这样每个节点的利用率差不多,不会存在一个节点利用率很大,而另一个节点利用率很小。
②很容易和上层的计算框架做结合:因为文件以多个块分在多个节点上,这里我们假设起五十个任务来并行处理这些文件,那么这个五十个任务并行的跑在存储这些文件的节点上,这样就分摊了每个节点的带宽,充分的并行。
HDFS 架构(master-slave架构)

这里面master就是namenode,slave就是这些datanode。namenode里面存的就是这些block的元信息,就是存储上面的那两张表。namenode有两个,一个是active namenode,一个是standby namenode,一旦active namenode挂了,standby namenode就来接管他。datanode就是用来存储实际的数据块,datanode会定期的向namenode汇报心跳信息,例如不断告诉namenode我还活着,我存储了多少数据块等信息。如果在指定的时间内没有汇报信息,那么namenode会认为这个datanode挂了。
Active Namenode
- 主Master(只有一个)
- 管理HDFS的名称空间(管理目录树,哪些文件在哪些目录下等)
- 管理数据块映射信息(每个文件由哪些block构成,每个block存在哪个节点下)
- 配置副本策略
- 处理客户端读写请求
Standby NameNode(定期同步namnode里面的元信息)
- NameNode 的热备;
- 定期合并 fsimage 和fsedits ,推送给NameNode ;
- 当 Active NameNode 出现故障时,快速切换为新的 ActiveNameNode 。
Datanode
- Slave (有多个)
- 存储实际的数据块
- 执行行数据块读 / 写
Client
HA 与 Federation

上图中NN是namenode缩写,DN是datanode缩写,如果此时要管理的文件数非常非常多,此时一对的namenode(active和standby,其实只有一个active namenode在管理,active namenode挂了standby namenode才会管理)就管理不了了,这时可以再搞一对namenode,相当于这对namenode管理这部分文件,另外一对namenode管理另一部分的namenode。然后一对的namenode,里面选谁做active namenode,谁做standby namenode呢?avtive namenode挂了怎么切换到standby namenode呢?这个是有Zookeeper来管理的。有两个namenode时需要Zookeeper做主从的选择以及协调。
NameNode两个重要文件:
- fsimage:元数据镜像文件(保存文件系统的目录树)
- edits:元数据操作日志(针对目录树的修改操作),被写入共享存储系统中 ,比如NFS、JournalNode
元数据镜像:
- 内存中保存一份最新的
- 内存中的镜像=fsimage+edits
合并fsimage与edits
- Edits文件过大将导致NameNode重启速度慢
- Standby Namenode负责定期合并它们
HDFS 数据块( block ):
文件被切分成固定大小的数据块
- 默认数据块大小为128MB,可配置
- 若文件大小不到128MB,则单独存成一个block
- 一个block只能来自一个文件
为何数据块如此之大
- 数据传输时间超过寻道时间(高吞吐率)
数据越大,时间就不会浪费在寻道上,而是更多的用在数据传输上。便于大数据处理。
一个文件存储方式
- 按大小被切分成若干个block,存储到不同节点上
- 默认情况下每个block有三个副本,注意存储的粒度是block,而不是文件
那么为什么是三个副本呢?为什么不是两个不是四个?两个的话可靠性肯定比较低,两个可不可靠取决于两个节点同时挂的可能性高不高,通过实际的经验知道,两个节点同时挂的可能性很大。那么如果四副本呢?其实三副本可靠性可以接受了,四副本增大了对存储空间的要求,但是提高的可靠性并不多,所以三副本是对存储空间要求和可靠性的一个折中。
HDFS 内部机制 — 写流程:

上图中有四个节点,没有部署standby namenode,当客户端向namenode发送一个写的请求信息,namenode收到写请求会查看他内存里面的元信息,看看存不存在这个目录或文件,如果存在就拒绝,如果不存在就接受。客户端首先会把这个文件切成多个block,先写第一个block,写的过程中和namenode通信,领取三个datanode地址,就是要存三个副本的地址。首先客户端会向第一个节点写一个小的package(不是128MB,这里假设是64k),并且会源源不断的写,同时的话,第一个节点会源源不断的把这个小的package发送到其他的副本节点。直到写完一个block。
HDFS 内部机制 — 读流程:

同样客户端向namenode发送一个读的请求,namenode查看内存里信息元,如果文件或目录存在,则可读,然后向其返回三个block地址。客户段根据地址读取文件,然后再读取第二个block直到把文件读完,客户端会把这些block(根据读取的先后顺序)拼装成一个完整的文件,返回给用户。
HDFS 内部机制 — 物理拓扑(也即副本放置策略)

读数据尽量在一个机柜上读,在不同的机柜之间读数据要通过交换机,延迟比较高。
同一个机柜内两个节点同时出故障的概率要比不同机柜内两个节点出故障高很多。因为同一个机柜的两个节点共享一个交换机,物理上许多东西都是共享的,那么同时出故障概率要高。
HDFS 内部机制 — 副本放置策略
一个文件划分成多个block,每个block存多份,如何为每个block选择节点存储这几份数据

- 副本1: 同Client的节点上
- 副本2: 不同机架中的节点上
- 副本3: 与第二个副本同一机架的另一个节点上
- 其他副本:随机挑选
那么可能会有人问,为什么不把三个副本放在三个不同的机架上?这样容错能力岂不是更强?上面已经说了不同机架之间的读写效率低,因为要通过一个交换机,而交换机的速率可能有瓶颈。所以把三个副本放在两个不同的机架上基于性能和可靠性的一个折中。
HDFS 内部机制 — 可靠性策略

文件完整性
Heartbeat
- Datanode 定期向Namenode发heartbeat
datanode会定期向namenode发送心跳,namenode会指定datanode的状态,如果在指定时间内没收到datanode心跳,namenode就会认为datanode已死亡,将其标记为死亡,然后把存储在这个死亡datanode上的数据块在其他节点上进行重构。始终保证每个数据块的副本数为3或者是设置的副本数。
元数据信息
- FSImage(文件系统镜像)、Editlog(操作日志)
- 多份存储
- 主备NameNode实时切换
HDFS程序设计方法
HDFS 访问方方式:
- HDFS Shell命令
- HDFS Java API
- HDFS REST API
- HDFS Fuse:实现了fuse协议
- HDFS lib hdfs:C/C++访问接口
- HDFS 其他语言编程API
使用thrift实现
HDFS Shell 命令
文件操作命令:

将本地文件上传到HDFS上
hdfs dfs -put /local/data /hdfs/data
删除文件/目录
hdfs dfs -rm /hdfs/data
创建目录
hdfs dfs ‐mkdir /hdfs/data
HDFS Shell 命令 — 管理命令

- 打印拓扑图命令:哪些节点在哪些机架上
hdfs dfsadmin -printTopology
HDFS Shell 命令 — 管理脚本:
在sbin目录下
- start-all.sh
- start-dfs.sh
- start-yarn.sh
- hadoop-deamon(s).sh
单独启动某个服务:
- hadoop-deamon.sh start namenode
- hadoop-deamons.sh start namenode(通过SSH登录到各个节点)
HDFS Shell 命令 — 文文件管理命令 fsck:

- 检查hdfs中文件的健康状况
- 查找缺失的块以及过少或过多副本的块
- 查看一个文件的所有数据块位置
删除损坏的数据块
例如查看/tmp文件的block的状态信息。

HDFS Shell 命令 — 数据均衡器 balancer:
数据块重分布
start-balancer.sh -threshold < percentage of disk capacity >
percentage of disk capacity(当节点之间负载不均衡时,比如新买了几台电脑,把负载很高的节点block搬运到其他节点)
HDFS达到平衡状态的磁盘使用率偏差值
值越低各节点越平衡,但消耗时间也更长
HDFS Shell 命令 — 设置目目录份额:
HDFS Shell 命令 — 增加 / 移除节点:
加入新的datanode
- 步骤1:将已存在datanode上的安装包(包括配置文件等)拷贝到新datanode上;
- 步骤2:启动新datanode:
hadoop-deamon.sh start datanode
移除旧datanode
- 步骤1:将datanode加入黑名单,并更新黑名单,在NameNode上,将datanode的host或者ip加入配置选项dfs.hosts.exclude指定的文件中
- 步骤2:移除datanode
hadoop dfsadmin -refreshNodes
HDFS Java API 介绍
Configuration类:该类的对象封装了配置信息,这些配置信息来自core-*.xml;
FileSystem类:文件系统类,可使用该类的方法对文件/目录进行操作。一般通过FileSystem的静态方法get获得一个文件系统对象;
FSDataInputStream和FSDataOutputStream类:HDFS中的输入输出流。分别通过FileSystem的open方法和create方法获得。
以上类均来自java包:org.apache.hadoop.fs
HDFS Java 程序举例
将本地文件拷贝到HDFS上
Configuration config = new Configuration();
FileSystem hdfs = FileSystem.get(config);
Path srcPath = new Path(srcFile);
Path dstPath = new Path(dstFile);
hdfs.copyFromLocalFile(srcPath, dstPath);
创建HDFS文件
Configuration config = new Configuration();
FileSystem hdfs = FileSystem.get(config);
Path path = new Path(fileName);
FSDataOutputStream outputStream = hdfs.create(path);
outputStream.write(buff, 0, buff.length);
关键操作步骤:
- 在创建的目录内用命令行创建一个maven项目。
- 再将项目导入到eclipse里面。
- 在pom.xml里面增加hadoop依赖。
例如:注意我的hadoop版本是2.7.3
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.3</version>
</dependency>
然后进入这个maven项目目录内使用命令:mvn clean install,下载依赖。
5.在src/main/java目录下创建一个App.java文件:
package my.hadoopstudy;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class App {
public static void testMkdirPath(String path) throws Exception {
FileSystem fs = null;
try {
System.out.println("Creating " + path + " on hdfs...");
Configuration conf = new Configuration();
Path myPath = new Path(path);
fs = myPath.getFileSystem(conf);
fs.mkdirs(myPath);
System.out.println("Create " + path + " on hdfs successfully.");
} catch (Exception e) {
System.out.println("Exception:" + e);
} finally {
if(fs != null)
fs.close();
}
}
public static void testDeletePath(String path) throws Exception {
FileSystem fs = null;
try {
System.out.println("Deleting " + path + " on hdfs...");
Configuration conf = new Configuration();
Path myPath = new Path(path);
fs = myPath.getFileSystem(conf);
fs.delete(myPath, true);
System.out.println("Deleting " + path + " on hdfs successfully.");
} catch (Exception e) {
System.out.println("Exception:" + e);
} finally {
if(fs != null)
fs.close();
}
}
public static void main(String[] args) {
try {
String path = "/test/mkdirs-test";
testMkdirPath(path);
} catch (Exception e) {
System.out.println("Exceptions:" + e);
}
System.out.println("timestamp:" + System.currentTimeMillis());
}
}
上面代码中就是用在本地对HDFS进行文件操作,比如创建文件,删除文件,写文件等操作等。
6.如果在eclipse里面直接run as java Application,那么是在本地生成这个文件。
7.如果想在hadoop节点上生成文件,那么可以把这个项目打成jar包,然后在节点上用命令hadoop jar xx.jar。因为这个时候会把所有的hadoop环境变量放进去运行。
具体操作步骤可参考
使用Maven搭建Hadoop开发环境,
用Maven构建Hadoop项目
win 10 + maven + idea 15 + Hadoop 2.7.3开发环境配置
HDFS优化小技巧
HDFS 优化小小技巧:原始日日志存储格式选择
文本文件
- 不便于压缩,选择合适的压缩算法很重要;
- 不建议将日志直接存成文本格式
SequenceFile
- 二进制格式,便于压缩,压缩格式作为元信息存到文件中;
- 建议采用该格式存储原始日志
- flume默认的输出格式就是SequenceFile
HDFS 优化小技巧:小文件优化
分布式日志收集系统:文件管理模块
日志分析系统: 文文件存储模块注意事项
日志分析系统:数据格式选择

日志分析系统:增大“热点文件”的副本数
通过程序API修改
FileSystem fs = FileSystem.get(path, conf);
fs.setReplication(path, (short) 4);//默认是3
通过配置参数修改
在hdfs-site.xml修改dfs.replication: 4,这样的话所有文件的block都是4,故不建议使用。
通过命令行
增加文件的副本数:hadoop dfs -setrep -w 4 /path/to/file // 这里面file是一个文件
递归增加目录下文件的的副本数:hadoop dfs –setrep -R -w 4 /path/to/file // file是一个目录。
日志分析系统:分析冷热数据:
“冷热”数据
冷数据:过去半年内没访问过的数据
冷数据可进行特殊处理,包括高压缩,小文件合并等
找出“冷热”数据
hdfs oiv -i /home/hadoop/data/hdfs/name/current/fsimage_0000000000001619538 -p XML -o fsimage.xml
找出上次访问时间为半年之前的文件:< atime>1475047293212< /atime>
就是利用hdfs里面的命令oiv命令将hdfs里面的一些元信息(故oiv后面要设置一个路径,就是hadoop安装目录里面的配置文件hdfs-site.xml里面配置的dfs.namenode.name.dir对应的value值就是保存元信息的位置信息)拉出来然后保存为xml格式,然后xml文件内有一个atime就是上一次访问的时间。
日志分析系统:处理“冷”数据