简介
HDFS产生背景
随着数据量越来越大,在一个本地文件系统里边已经存不下所有的数据,需要将数据分配到更多的操作系统和文件管理系统,但是不方便管理和维护,所以需要一个系统来统一管理这些存储了所有数据的机器,这个系统也就是分布式文件管理系统,HDFS只是分布式文件管理系统的一种。

HDFS定义
HDFS(全称:Hadoop Distribute File System,Hadoop 分布式文件系统),用于文件存储和管理,通过目录树来定位文件,是分布式的。可以与Linux文件系统做类比,只不过是封装了很多机器的文件管理功能。
HDFS优缺点
优点
- 高可靠性
- 数据自动保存多个副本(默认3个,可以通多dfs.replication参数设置),通过增加副本来提高容错性
- 某个副本丢失可以自动恢复
- 适合处理大批量数据
- 数据规模:能够处理GB,TB,甚至PB级别规模的数据
- 文件规模:能够处理百万规模以上的文件数量
- 可以在廉价的机器上运行良好
缺点
- 不适合低延迟数据访问,比如秒级、毫秒级
- 对大量小文件不友好
- 存储大量小文件会占用NameNode大量的内存来存储文件目录及块信息等元数据,而NameNode的内存是有限的
- 小文件存储的寻址时间会超过读取时间,寻址时间最好在传输时间的1%
- 不支持并发写入、文件随机修改
- 一个文件只能由一个线程写,不允许多个线程同时写
- 仅支持文件内容追加(append),不支持随机修改
HDFS重要概念
HDFS 通过统一的命名空间目录树来定位文件; 另外,它是分布式的,由很多服务器联合起来实现
其功能,集群中的服务器有各自的角色(分布式本质是拆分,各司其职);
- 典型的 Master/Slave 架构
HDFS 的架构是典型的 Master/Slave 结构。
HDFS集群往往是一个NameNode(HA架构会有两个NameNode,联邦机制)+多个DataNode组成;
NameNode是集群的主节点,DataNode是集群的从节点。
- 分块存储(block机制)
HDFS 中的文件在物理上是分块存储(block)的,块的大小可以通过配置参数来规定;Hadoop2.x版本中默认的block大小是128M;
- 命名空间(NameSpace)
HDFS 支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。
Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode 记录下来。
HDFS提供给客户单一个抽象目录树,访问形式:hdfs://namenode的hostname:port/test/input
hdfs://linux121:9000/test/input
- NameNode元数据管理
我们把目录结构及文件分块位置信息叫做元数据。
NameNode的元数据记录每一个文件所对应的block信息(block的id,以及所在的DataNode节点的信息)
- DataNode数据存储
文件的各个 block 的具体存储管理由 DataNode 节点承担。一个block会有多个DataNode来存储,DataNode会定时向NameNode来汇报自己持有的block信息。
- 副本机制
为了容错,文件的所有 block 都会有副本。每个文件的 block 大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。副本数量默认是3个。
- 一次写入,多次读出
HDFS 是设计成适应一次写入,多次读出的场景,且不支持文件的随机修改。 (支持追加写入,不只支持随机更新),正因为如此,HDFS 适合用来做大数据分析的底层存储服务,并不适合用来做网盘等应用(修改不
方便,延迟大,网络开销大,成本太高)
HDFS架构

- Client:客户端
- 上传文件到HDFS时,客户端负责将文件切分为block,然后进行上传
- 请求与NameNode的交互,获取文件位置等元数据信息
- 读取或写入文件,与NameNode交互
- 使用一些命令管理和访问HDFS,比如对HDFS增删改查,NameNode格式化等操作
- NameNode(Master节点),集群的管理者
- 维护和管理HDFS的名称空间(namespace)
- 维护副本策略
- 记录block的元数据
- 负责与客户端发来的读写请求
- DataNode(Slave节点),NameNode下达指令(老大),DataNode干活(小弟)
- 保存实际的数据块
- 负责数据块读写
- Secondary NameNode
- 用来监控 HDFS 状态的辅助后台程序,每隔一段时间获取 HDFS 元数据的快照,定期合并Fsimage和Edits,并推送给NameNode。
- 最主要作用是辅助 NameNode 管理元数据信息。
- 紧急情况下,可辅助恢复NameNode元数据信息
HDFS命令
如果没有配置 hadoop 的环境变量,则在 hadoop 的安装目录下的bin目录中执行以下命令,如已配置 hadoop 环境变量,则可在任意目录下执行,命令与linux命令大同小异,可以对比记忆
- 使用某个具体命令
bin/hadoop fs 具体命令 或者 bin/hdfs dfs 具体命令
- 命令大全
[root@node101 ~]# hdfs dfs
Usage: hadoop fs [generic options]
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...]
[-checksum <src> ...]
[-chgrp [-R] GROUP PATH...]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-copyFromLocal [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
[-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] [-h] [-v] [-t [<storage type>]] [-u] [-x] <path> ...]
[-cp [-f] [-p | -p[topax]] [-d] <src> ... <dst>]
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] [-x] <path> ...]
[-expunge]
[-find <path> ... <expression> ...]
[-get [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getfacl [-R] <path>]
[-getfattr [-R] {-n name | -d} [-e en] <path>]
[-getmerge [-nl] [-skip-empty-file] <src> <localdst>]
[-help [cmd ...]]
[-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] [-safely] <src> ...]
[-rmdir [--ignore-fail-on-non-empty] <dir> ...]
[-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
[-setfattr {-n name [-v value] | -x name} <path>]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touchz <path> ...]
[-truncate [-w] <length> <path> ...]
[-usage [cmd ...]]
Generic options supported are:
-conf <configuration file> specify an application configuration file
-D <property=value> define a value for a given property
-fs <file:///|hdfs://namenode:port> specify default filesystem URL to use, overrides 'fs.defaultFS' property from configurations.
-jt <local|resourcemanager:port> specify a ResourceManager
-files <file1,...> specify a comma-separated list of files to be copied to the map reduce cluster
-libjars <jar1,...> specify a comma-separated list of jar files to be included in the classpath
-archives <archive1,...> specify a comma-separated list of archives to be unarchived on the compute machines
The general command line syntax is:
command [genericOptions] [commandOptions]
常用命令
- hdfs dfs -help 输出命令的参数
[root@node101 ~]# hdfs dfs -help rm
-rm [-f] [-r|-R] [-skipTrash] [-safely] <src> ... :
Delete all files that match the specified file pattern. Equivalent to the Unix
command "rm <src>"
-f If the file does not exist, do not display a diagnostic message or
modify the exit status to reflect an error.
-[rR] Recursively deletes directories.
-skipTrash option bypasses trash, if enabled, and immediately deletes <src>.
-safely option requires safety confirmation, if enabled, requires
confirmation before deleting large directory with more than
<hadoop.shell.delete.limit.num.files> files. Delay is expected when
walking over large directory recursively to count the number of
files to be deleted before the confirmation.
- hdfs dfs -ls 显示HDFS目录信息
[root@node101 ~]# hdfs dfs -ls /
- hdfs dfs -mkdir 在HDFS上创建目录
[root@node101 ~]# hdfs dfs -mkdir -p /bigdata
- hdfs dfs -moveFromLocal 剪切本地文件到HDFS
# 创建test.txt空文件,里边没有任何内容
[root@node101 ~]# vi test.txt
# 查询test.txt文件
[root@node101 ~]# ls test.txt
test.txt
#剪切test.txt文件到hdfs上bigdata文件夹
[root@node101 ~]# hdfs dfs -moveFromLocal ./test.txt /bigdata
#查看hdfs上/bigdata文件夹,test.txt文件已上传
[root@node101 ~]# hdfs dfs -ls /bigdata
Found 1 items
-rw-r--r-- 3 root supergroup 31 2022-07-26 18:13 /bigdata/test.txt
#查询本地test.txt文件 报错,原因已经将test.txt剪切到hdfs
[root@node101 ~]# ls test.txt
ls: cannot access test.txt: No such file or directory
- copyFromLocal 从本地文件系统中拷贝文件到hdfs路径去
# 查看本地wc.txt文件
[root@node101 ~]# ls wc.txt
wc.txt
# 从本地上传wc.txt文件到hdfs上/bigdata目录
[root@node101 ~]# hdfs dfs -copyFromLocal wc.txt /bigdata
[root@node101 ~]# hdfs dfs -ls /bigdata
Found 2 items
-rw-rw-rw- 10 root root 45 2022-07-26 18:33 /bigdata/test.txt
-rw-r--r-- 3 root supergroup 42 2022-07-26 21:10 /bigdata/wc.txt
# 查看本地还存在wc.txt文件
[root@node101 ~]# ls wc.txt
wc.txt
- hdfs dfs -cat 查看hdfs文件内容
# 查询hdfs上test.txt文件内容,此时为空
[root@node101 ~]# hdfs dfs -cat /bigdata/test.txt
- hdfs dfs -appendToFile 追加本地文件内容到hdfs文件
# 在本地创建test2.txt并输入一些单词
[root@node101 ~]# vi test2.txt
#查看test2.txt内容
[root@node101 ~]# cat test2.txt
hadoop hive
hadoop spark
bigdata flink
hbase
# 追加test2.txt 内容到hdfs上的/bigdata/test.txt文件
[root@node101 ~]# hdfs dfs -appendToFile test2.txt /bigdata/test.txt
# 再次查看hdfs上/bigdata/test.txt文件,内容已经变化
[root@node101 ~]# hdfs dfs -cat /bigdata/test.txt
hadoop hive
hadoop spark
bigdata flink
hbase
- hdfs dfs -chmod 修改hdfs文件权限
# 查看文件权限
[root@node101 ~]# hdfs dfs -ls /bigdata/test.txt
-rw-r--r-- 3 root supergroup 45 2022-07-26 18:33 /bigdata/test.txt
# 修改文件权限
[root@node101 ~]# hdfs dfs -chmod 666 /bigdata/test.txt
# 再次查看文件权限
[root@node101 ~]# hdfs dfs -ls /bigdata/test.txt
-rw-rw-rw- 3 root supergroup 45 2022-07-26 18:33 /bigdata/test.txt
- hdfs dfs -chown 修改文件所属
# 修改文件所属
[root@node101 ~]# hdfs dfs -chown root:root /bigdata/test.txt
# 再次查看文件所属
[root@node101 ~]# hdfs dfs -ls /bigdata/test.txt
-rw-rw-rw- 3 root root 45 2022-07-26 18:33 /bigdata/test.txt
- hdfs dfs -copyFromLocal或hdfs dfs -put 拷贝本地文件到HDFS
# 查看hdfs上/bigdata目录下文件
[root@node101 ~]# hdfs dfs -ls /bigdata/
Found 1 items
-rw-rw-rw- 3 root root 45 2022-07-26 18:33 /bigdata/test.txt
# 拷贝本地test2.txt文件到hdfs
[root@node101 ~]# hdfs dfs -copyFromLocal test2.txt /bigdata
# 再次查看hdfs上/bigdata目录下文件,test2.txt已经上传
[root@node101 ~]# hdfs dfs -ls /bigdata/
Found 2 items
-rw-rw-rw- 3 root root 45 2022-07-26 18:33 /bigdata/test.txt
-rw-r--r-- 3 root supergroup 45 2022-07-26 19:05 /bigdata/test2.txt
- hdfs dfs -copyToLocal或hdfs dfs -get 从hdfs上拷贝文件到本地
# 删除本地test2.txt文件
[root@node101 ~]# rm -rf test2.txt
# 查看文件已删除
[root@node101 ~]# ls test2.txt
ls: cannot access test2.txt: No such file or directory
# 从hdfs拷贝test2.txt文件到本地
[root@node101 ~]# hdfs dfs -copyToLocal /bigdata/test2.txt
# 查看文件已经被拷贝到本地
[root@node101 ~]# ls test2.txt
test2.txt
- hdfs dfs -cp 从hdfs的一个路径拷贝到另一个路径
# 创建一个hdfs新目录 /bigdata2
[root@node101 ~]# hdfs dfs -mkdir /bigdata2
# 查看/bigdata2目录下没有文件
[root@node101 ~]# hdfs dfs -ls /bigdata2
# 从/bigdata目录拷贝test2.txt文件到/bigdata2
[root@node101 ~]# hdfs dfs -cp /bigdata/test2.txt /bigdata2
# 再次查看bigdata2文件夹已经多了test2文件
[root@node101 ~]# hdfs dfs -ls /bigdata2
Found 1 items
-rw-r--r-- 3 root supergroup 45 2022-07-26 19:20 /bigdata2/test2.txt
# 查看bigdata文件夹下test2.txt文件依然在
[root@node101 ~]# hdfs dfs -ls /bigdata
Found 2 items
-rw-rw-rw- 3 root root 45 2022-07-26 18:33 /bigdata/test.txt
-rw-r--r-- 3 root supergroup 45 2022-07-26 19:05 /bigdata/test2.txt
- hdfs dfs -rm hdfs上删除文件或文件夹
# 删除bigdata2文件夹下的test2.txt文件
[root@node101 ~]# hdfs dfs -rm /bigdata2/test2.txt
Deleted /bigdata2/test2.txt
# 文件已删除,查看文件报错提示文件不存在
[root@node101 ~]# hdfs dfs -ls /bigdata2/test2.txt
ls: `/bigdata2/test2.txt': No such file or directory
- hdfs dfs -mv hdfs上移动文件
# 从bigdata文件夹下移动test2.txt文件到bigdata2文件夹
[root@node101 ~]# hdfs dfs -mv /bigdata/test2.txt /bigdata2
# 查看bigdata目录下的test2.txt文件已不存在
[root@node101 ~]# hdfs dfs -ls /bigdata
Found 1 items
-rw-rw-rw- 3 root root 45 2022-07-26 18:33 /bigdata/test.txt
# 查看bigdata2文件加下已有test2.txt文件
[root@node101 ~]# hdfs dfs -ls /bigdata2
Found 1 items
-rw-r--r-- 3 root supergroup 45 2022-07-26 19:05 /bigdata2/test2.txt
- hdfs dfs -tail 显示文件末尾
[root@node101 ~]# hdfs dfs -tail /bigdata/test.txt
hadoop hive
hadoop spark
bigdata flink
hbase
- hdfs dfs -getmerge 合并下载多个文件
# 查看hdfs上/bigdata/test.txt文件内容
[root@node101 ~]# hdfs dfs -cat /bigdata/test.txt
hadoop hive
hadoop spark
bigdata flink
hbase
# 查看hdfs上/bigdata/wc.txt文件内容
[root@node101 ~]# hdfs dfs -cat /bigdata/wc.txt
lwp spark flink lwp
hadoop flink hive
mr
# 合并/bigdata/test.txt和/bigdata/wc.txt文件内容 到本地merge.txt
[root@node101 ~]# hdfs dfs -getmerge /bigdata/* merge.txt
# 查看合并后的文件内容
[root@node101 ~]# cat merge.txt
hadoop hive
hadoop spark
bigdata flink
hbase
lwp spark flink lwp
hadoop flink hive
mr
- hdfs dfs -df 统计HDFS文件系统可用空间信息
[root@node101 ~]# hdfs dfs -df /
Filesystem Size Used Available Use%
hdfs://node101:9000 109443035136 1231085568 91952766976 1%
[root@node101 ~]# hdfs dfs -df -h /
Filesystem Size Used Available Use%
hdfs://node101:9000 101.9 G 1.1 G 85.6 G 1%
- hdfs dfs -du 统计文件夹大小
[root@node101 ~]# hdfs dfs -du -s -h /bigdata
45 /bigdata
[root@node101 ~]# hdfs dfs -du -h /bigdata
45 /bigdata/test.txt
- hdfs dfs -count 统计目录下文件数量
[root@node101 ~]# hdfs dfs -count /bigdata
1 2 87 /bigdata
目录数量 文件数量 文件总大小 统计目录
- hdfs dfs -expunge 清空hdfs垃圾桶(慎用)
[root@node101 ~]# hdfs dfs -expunge
- hdfs dfs -setrep 设置hdfs文件副本数量
[root@node101 ~]# hdfs dfs -setrep 10 /bigdata/test.txt
Replication 10 set: /bigdata/test.txt
在NameNode页面查看test.txt文件元数据信息replication为10

**需要注意:**这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看DataNode的
数量。因为目前只有3台设备,最多也就3个副本,只有节点数的增加到10台时,副本数才能达到10。
高级命令
- 文件限额配置
在多人共用HDFS的环境下,配额设置非常重要。特别是在 Hadoop 处理大量资料的环境,如果没有配额管理,很容易把所有的空间用完造成别人无法存取。HDFS 的配额设定是针对目录而不是针对账号,可以让每个账号仅操作某一个目录,然后对目录设置配置。
HDFS 文件的限额配置允许我们以文件个数,或者文件大小来限制我们在某个目录下上传的文件数量或者文件内容总量,以便达到我们类似百度网盘网盘等限制每个用户允许上传的最大的文件的量。
# 查看配额信息
[root@node101 ~]# hdfs dfs -count -q -h /
8.0 E 8.0 E none inf 82 382 386.5 M /
数量配额
# 新建hdfs上/bigdata3文件夹
[root@node101 ~]# hdfs dfs -mkdir /bigdata3
# 上传两个文件到/bigdata3目录下
[root@node101 ~]# hdfs dfs -put test2.txt wc.txt /bigdata3
# 删除/bigdata3目录下两个文件
[root@node101 ~]# hdfs dfs -rm -r -f /bigdata3/*
Deleted /bigdata3/test2.txt
Deleted /bigdata3/wc.txt
# 对/bigdata3文件夹做配额限制,限制最多上传两个文件
[root@node101 ~]# hdfs dfsadmin -setQuota 2 /bigdata3
# 再次上传两个文件,发现上传失败,有文件数量限制,只上传成功了一个文件
[root@node101 ~]# hdfs dfs -put test2.txt wc.txt /bigdata3
put: The NameSpace quota (directories and files) of directory /bigdata3 is exceeded: quota=2 file count=3
[root@node101 ~]# hdfs dfs -ls /bigdata3
Found 1 items
-rw-r--r-- 3 root supergroup 45 2022-07-26 21:43 /bigdata3/test2.txt
# 清除/bigdata3配额设置
[root@node101 ~]# hdfs dfsadmin -clrQuota /bigdata3
# 再次上传文件成功
[root@node101 ~]# hdfs dfs -put test2.txt wc.txt /bigdata3
put: `/bigdata3/test2.txt': File exists
[root@node101 ~]# hdfs dfs -ls /bigdata3
Found 2 items
-rw-r--r-- 3 root supergroup 45 2022-07-26 21:43 /bigdata3/test2.txt
-rw-r--r-- 3 root supergroup 42 2022-07-26 21:48 /bigdata3/wc.txt
空间大小配额
在设置空间配额时,设置的空间至少是 block_size * 3 大小
# 设置空间配额
[root@node101 ~]# hdfs dfsadmin -setSpaceQuota 10K /bigdata3
# 生成任意大小文件
[root@node101 ~]# dd if=/dev/zero of=test3.txt bs=1M count=2
2+0 records in
2+0 records out
2097152 bytes (2.1 MB) copied, 0.00387978 s, 541 MB/s
# test3.txt文件超过设置的空间配额,上传失败
[root@node101 ~]# hdfs dfs -put test3.txt /bigdata3
put: The DiskSpace quota of /bigdata3 is exceeded: quota = 10240 B = 10 KB but diskspace consumed = 402653184 B = 384 MB
# 请出空间配额限制,上传文件成功
[root@node101 ~]# hdfs dfsadmin -clrSpaceQuota /bigdata3
[root@node101 ~]# hdfs dfs -put test3.txt /bigdata3
[root@node101 ~]# hdfs dfs -ls /bigdata3
Found 1 items
-rw-r--r-- 3 root supergroup 2097152 2022-07-26 22:00 /bigdata3/test3.txt
- 安全模式
安全模式是hadoop的一种保护机制,用于保证集群中的数据块的安全性。当集群启动的时候,会首先进入安全模式。当系统处于安全模式时会检查数据块的完整性。
假设我们设置的副本数(即参数dfs.replication)是3,那么在datanode上就应该有3个副本存在,假设只存在2个副本,那么比例就是2/3=0.666。hdfs默认的副本率0.999。我们的副本率0.666明显小于0.999,因此系统会自动的复制副本到其他dataNode,使得副本率不小于0.999。如果系统中有5个副本,超过我们设定的3个副本,那么系统也会删除多于的2个副本。
**在安全模式状态下,文件系统只接受读数据请求,而不接受删除、修改等变更请求。**在当整个系统达到安全标准时,HDFS自动离开安全模式。30s安全模式操作命令
hdfs dfsadmin -safemode get #查看安全模式状态
hdfs dfsadmin -safemode enter #进入安全模式
hdfs dfsadmin -safemode leave #离开安全模式
HDFS java客户端操作
准备客户端操作环境
- 将Hadoop-2.9.2安装包解压到非中文路径(例如:D:\lwp\hadoop-2.9.2)。

- 配置HADOOP_HOME环境变量

- 修改Path变量,增加%HADOOP_HOME%\bin

- 在IDEA创建新的maven项目
- 添加pom.xml依赖和相关配置
<dependencies>
<!--hadoop相关-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.9.2</version>
</dependency>
<!--日志配置-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependencies>
为了便于控制程序运行打印的日志数量,需要在项目的src/main/resources目录下,新建一个文件,命
名为“log4j.properties”,文件内容:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
- 创建包名com.hadoop.hdfs和类,并测试
package com.hadoop.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class HDFSClient {
@Test
//测试创建目录
public void testMkdirs() throws URISyntaxException, IOException, InterruptedException {
//1.获取hadoop集群的configuration对象
Configuration configuration = new Configuration();
//2.根据configuration获取FileSystem对象
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node101:9000"), configuration, "root");
//3.创建文件夹
fileSystem.mkdirs(new Path("/bigdata4"));
//4.释放FileSystem对象(类似于关闭数据库连接)
fileSystem.close();
}
}
可能遇到的问题
- 如果不指定操作HDFS集群的用户信息,默认是获取当前操作系统的用户信息,出现权限被拒绝的问
题,报错如下:
2. windows解压安装Hadoop后,在调用相关API操作HDFS集群时可能会报错,这是由于Hadoop安装缺少windows操作系统相关文件所致,如下图:
解决方案:
把winutils.exe拷贝放到windows系统Hadoop安装目录的bin目录下即可!!
HDFS的API操作
测试参数优先级
- 编写文件上传代码
@Test
public void testCopyFromLocalFile() throws IOException,
InterruptedException, URISyntaxException {
// 1 获取文件系统
Configuration configuration = new Configuration();
configuration.set("dfs.replication", "2");
FileSystem fs = FileSystem.get(new URI("hdfs://node101:9000"),
configuration, "root");
// 2 上传文件
fs.copyFromLocalFile(new Path("e:/wc.txt"), new Path("/wc.txt"));
// 3 关闭资源
fs.close();
System.out.println("end");
}
- 将hdfs-site.xml拷贝到项目的根目录下
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
- 参数优先级
参数优先级排序:(1)代码中设置的值 >(2)用户自定义配置文件 >(3)服务器的默认配置
编写公用代码,文件系统对象创建和关闭等
package com.hadoop.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
public class HDFSClient {
Configuration configuration = null;
FileSystem fs = null;
@Before
public void init() throws URISyntaxException, IOException, InterruptedException {
//获取Hadoop集群的configuration对象
configuration = new Configuration();
//根据configuration获取FileSystem对象
fs = FileSystem.get(new URI("hdfs://node101:9000"), configuration, "root");
}
@After
public void destroy() throws IOException {
//释放FileSystem对象(类似数据库链接)
fs.close();
}
}
创建文件夹
//创建目录
@Test
public void testMkdirs() throws URISyntaxException, IOException, InterruptedException {
fs.mkdirs(new Path("/bigdata4"));
}
上传文件
//上传文件
@Test
public void copyFromLocalToHDFS() throws IOException {
//把D盘wc.txt文件上传到hdfs的/bigdata4文件夹
fs.copyFromLocalFile(new Path("d:/wc.txt"),new Path("/bigdata4/wc.txt"));
}
下载文件
//下载文件
@Test
public void copyFromHDFSToLocal() throws IOException {
//把/bigdata4文件夹下的wc.txt文件下载到E盘
fs.copyToLocalFile(new Path("/bigdata4/wc.txt"),new Path("e:/wc.txt"));
}
删除文件或目录
//删除文件或目录
@Test
public void deleteFile() throws IOException {
//删除hdfs的bigdata4文件夹
fs.delete(new Path("/bigdata4"),true);
}
遍历根目录获取所有文件及文件夹信息
//遍历hdfs根目录达到文件及文件夹信息:名称、权限、长度等
@Test
public void listFiles() throws IOException {
//得到一个迭代器:装有指定目录下所有文件信息
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"),true);
while(listFiles.hasNext()){
LocatedFileStatus fileStatus = listFiles.next();
// 文件名称
String name = fileStatus.getPath().getName();
//文件长度
long len = fileStatus.getLen();
//权限
FsPermission permission = fileStatus.getPermission();
//分组
String group = fileStatus.getGroup();
//所属用户
String owner = fileStatus.getOwner();
System.out.println(name+"\t"+len+"\t"+permission+"\t"+group+"\t"+owner);
//块信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
//获取块存储的节点信息
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
System.out.println("块所在主机名称"+host);
}
}
System.out.println("----------------------------------------------------------------------");
}
}
文件及文件夹判断
//文件及文件夹判断
@Test
public void isFile() throws IOException {
FileStatus[] fileStatuses = fs.listStatus(new Path("/bigdata"));
for (FileStatus fileStatus : fileStatuses) {
//判断是否是文件
boolean fileFlag = fileStatus.isFile();
if(fileFlag){
System.out.println("文件: "+fileStatus.getPath().getName());
}else{
System.out.println("文件夹:"+fileStatus.getPath().getName());
}
}
}
使用IO流操作上传文件到HDFS
//使用IO流操作上传文件到HDFS
@Test
public void uploadFileIO() throws IOException {
//1.读取本地文件的输入流
FileInputStream fileInputStream = new FileInputStream(new File("e:/wc.txt"));
//2.准备些数据到hdfs 的输出流
FSDataOutputStream fileOutputStream = fs.create(new Path("/bigdata3/wc.txt"));
//3.输入流数据拷贝到输出流:数组的大小,以及是否关闭流底层有默认值
IOUtils.copyBytes(fileInputStream,fileOutputStream,configuration);
//4.可以再次关闭流
IOUtils.closeStream(fileOutputStream);
IOUtils.closeStream(fileInputStream);
}
使用IO流下载文件到本地
//使用IO流下载文件到本地
@Test
public void downLoadFileIO() throws IOException {
//1.读取hdfs文件输入流
FSDataInputStream in = fs.open(new Path("/bigdata3/test3.txt"));
//2.获取本地文件输出流
FileOutputStream out = new FileOutputStream(new File("e:/test3.txt"));
//3.流拷贝
IOUtils.copyBytes(in, out, configuration);
//4.可以再次关闭流
IOUtils.closeStream(out);
IOUtils.closeStream(in);
}
读取文件两次
//seek定位读取hdfs指定文件,使用io流读取/bigdata3/wc.txt文件并把内容输出两次,本质就是读取文件内容两次并输出
@Test
public void seekReadFile() throws IOException {
//1.创建一个读取hdfs文件的输入流
FSDataInputStream in = fs.open(new Path("/bigdata3/wc.txt"));
//2.控制台数据System.out
//3.实现流拷贝,输入流——》控制台输出
IOUtils.copyBytes(in,System.out,4096,false);
in.seek(0);
IOUtils.copyBytes(in,System.out,4096,false);
IOUtils.closeStream(in);
}
HDFS读写流程及原理
网络拓扑
在本地网络中,两个节点被称为“彼此近邻”是什么意思?在海量数据处理中,其主要限制因素是节点之间数据的传输速率——带宽很稀缺。这里的想法是将两个节点间的带宽作为距离的衡量标准。
节点距离:两个节点到达最近的共同祖先的距离总和。
例如,假设有数据中心d1机架r1中的节点n1。该节点可以表示为/d1/r1/n1。利用这种标记,这里给出四种距离描述。
Distance(/d1/r1/n1, /d1/r1/n1)=0(同一节点上的进程)
Distance(/d1/r1/n1, /d1/r1/n3)=2(同一机架上的不同节点)
Distance(/d1/r1/n1, /d1/r3/n3)=4(同一数据中心不同机架上的节点)
Distance(/d1/r1/n1, /d2/r4/n3)=6(不同数据中心的节点)
机架结构图:

机架感知
Hadoop2.9.2 副本节点选择
第一个副本在client所处的节点上。如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于相同机架,随机节点。
第三个副本位于不同机架,随机节点。

HDFS读数据流程
 读数据流程图
- 客户端通过FileSystem向NameNode发出读(下载文件)请求,确定请求文件block在哪些DataNode上。
- NameNode通过查询元数据信息,检查是否有权限访问,找到block所在的DataNode地址,返回给客户端一个block列表,对于每个block,NameNode都会返回含有该block的副本的DataNode地址,这些DataNode地址会按照集群拓扑结构得出与客户端的距离,然后进行排序。排序规则:网络拓扑结构中距离客户端近的靠前,心跳机制中超时汇报DataNode状态为STALE的靠后。
- 客户端根据就近原则挑选一台DataNode,请求读取block。
- DataNode开始传输数据给客户端(从磁盘里读取数据输入流,以package为单位做checksum),底层上本质是建立 Socket Stream(FSDataInputStream,可见上文使用IO流操作上传文件到HDFS),重复的调用父类 DataInputStream 的 read 方法,直到这个块上的数据读取完毕。
- 当读完列表的 block 后,若文件读取还没有结束,客户端会继续向NameNode 获取下一批的 block 列表。
- 读取完一个 block 都会进行 checksum 验证,如果读取 DataNode 时出现错误,客户端会通知 NameNode,然后再从下一个拥有该 block 副本的DataNode 继续读
- 客户端以package为单位接收数据,先在本地缓存
- 最终读取的所有block合并成一个完整的文件
HDFS写数据流程
 写数据流程图
- 客户端发起上传文件请求,通过rpc与NameNode建立通信,NameNode检查目标文件是否存在,父目录是否存在,返回是否可以上传。
- client请求第一个block该传输到哪些DataNode服务器上。
- NameNode根据配置文件中指定的备份数量及机架感知原理进行文件分配,返回可用的DataNode地址:比如A,B,C三台机器。
- client请求3台DataNode中的其中一台A上传数据,A收到请求后会调用B,B再调用C,建立整个pipeline
- client开始向A上传block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位(默认64K)上传,A收到一个packet就会传给B,B再传给C,A每传一个packet会放入一个应答队列等待应答
- **数据被分割成一个个packet数据包在pipeline上一次传输,在pipeline反方向逐个发送ACK(命令正确应答),最终由pipeline中第一个节点A将pipeline ack发送给客户端 **
- 当一个block传输成功后,client再同理请求NameNode上传第二个block,第三个block。。。。直至上传完毕
验证package代码
@Test
public void testUploadPacket() throws IOException {
//1 准备读取本地文件的输入流
final FileInputStream in = new FileInputStream(new File("e:/wc.txt"));
//2 准备好写出数据到hdfs的输出流
final FSDataOutputStream out = fs.create(new Path("/bigdata3/wc.txt"), new Progressable() {
public void progress() {
//这个progress方法就是每传输64KB(packet)就会执行一次
System.out.println("&");
}
});
//3 实现流拷贝
IOUtils.copyBytes(in, out, configuration);
//默认关闭流选项是true,所以会自动关闭
//4 关流 可以再次关闭也可以不关了
}
HDFS块和副本机制
HDFS块
HDFS 将所有的文件全部抽象成为 block 块来进行存储,不管文件大小,全部一视同仁都是以 block 块的统一大小和形式进行存储,方便我们的分布式文件系统对文件的管理。
抽象为块的好处
- 一个文件有可能大于集群中任意一个磁盘 10T*3/128 = xxx块 2T,2T,2T 文件方式存—–>多个block块,这些block块属于一个文件
- 使用块抽象而不是文件可以简化存储子系统
- 块非常适合用于数据备份进而提供数据容错能力和可用性
块大小设置
HDFS中的文件物理上实际以块(block)的形式存储,可以通过 hdfs-site.xml 中dfs.blocksize参数设置,这个参数在hadoop2.x版本中默认是128M,老版本为64M
<property>
<name>dfs.block.size</name>
<value>块大小 以字节为单位</value> //只写数值就可以
</property>
Q:为什么是128M,为什么块的大小不能设置太小也不能设置太大
- HDFS的块设置的太小,会增加寻址时间,寻址时间可能超过读取块的时间,程序一直在找块的位置
- 如果设置的太大,从磁盘传输数据的时间会明显超过定位这个数据块开始位置的时间(寻址时间),导致程序在处理这个块时,会非常慢
HDFS副本因子
为了保证block块的安全性,也就是数据的安全性,在hadoop2当中,文件默认保存三个副本,我们可以更改副本数以提高数据的安全性
在hdfs-site.xml当中修改以下配置属性,即可更改文件的副本数
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
快缓存机制
**通常 DataNode 从磁盘中读取块,但对于访问频繁的文件,其对应的块可能被显示的缓存在 DataNode 的内存中,以堆外块缓存的形式存在。**默认情况下,一个块仅缓存在一个DataNode的内存中,当然可以针对每个文件配置DataNode的数量。作业调度器通过在缓存块的DataNode上运行任务,可以利用块缓存的优势提高读操作的性能。
例如:
连接(join)操作中使用的一个小的查询表就是块缓存的一个很好的候选。用户或应用通过在缓存池中增加一个cache directive来告诉namenode需要缓存哪些文件及存多久。缓存池(cache pool)是一个拥有管理缓存权限和资源使用的管理性分组。
例如:
一个文件 130M,会被切分成2个block块,保存在两个block块里面,实际占用磁盘130M空间,而不是占用256M的磁盘空间
HDFS中的权限
hdfs的文件权限机制与linux系统的文件权限机制类似!!
r:read w:write x:execute 权限x对于文件表示忽略,对于文件夹表示是否有权限访问其内容。
如果linux系统用户zhangsan使用hadoop命令创建一个文件,那么这个文件在HDFS当中的owner就是zhangsan
HDFS文件权限的目的,防止好人做错事,而不是阻止坏人做坏事。HDFS相信你告诉我你是谁,你就是谁!!
- 解决方案
- 指定用户信息获取FileSystem对象
- 关闭HDFS集群权限校验
vim hdfs-site.xml
#添加如下属性
<property>
<name>dfs.permissions</name>
<value>true</value>
</property>
修改完成之后要分发到其它节点,同时要重启HDFS集群
基于HDFS权限本身比较鸡肋的特点,我们可以彻底放弃HDFS的权限校验,如果生产环境中我们可以考虑借助kerberos以及sentry等安全框架来管理大数据集群安全。所以我们直接修改HDFS的根目录权限为777
hadoop fs -chmod -R 777 /
NameNode和SecondaryNameNode
NameNode如何管理和存储元数据
计算机中存储数据两种:内存或者是磁盘
元数据存储磁盘:存储磁盘无法面对客户端对元数据信息的任意的快速低延迟的响应,但是安全性高
元数据存储内存:元数据存放内存,可以高效的查询以及快速响应客户端的查询请求,数据保存在内存,如果断点,内存中的数据全部丢失。
如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage。
这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦NameNode节点断电,就会产生数据丢失。因此,引入Edits文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。
但是,如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
HDFS元数据管理:内存+磁盘;NameNode内存+FsImage的文件(磁盘)
NameNode元数据管理流程

namenode工作机制
- 第一次启动namenode格式化后,创建fsimage和edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
- 客户端对元数据进行增删改的请求。
- namenode记录操作日志,更新滚动日志。
- namenode在内存中对数据进行增删改查。
secondary namenode工作机制
- secondary namenode询问 namenode 是否需要 checkpoint。直接带回 namenode 是否检查结果。
- secondary namenode 请求执行 checkpoint。
- namenode 滚动正在写的edits日志。
- 将滚动前的编辑日志和镜像文件拷贝到 secondary namenode。
- secondary namenode 加载编辑日志和镜像文件到内存,并合并。
- 生成新的镜像文件 fsimage.chkpoint。
- 拷贝 fsimage.chkpoint 到 namenode。
- namenode将 fsimage.chkpoint 重新命名成fsimage。
NameNode与SecondaryNameNode工作机制详解
Fsimage:NameNode内存中元数据序列化后形成的文件。
Edits:记录客户端更新元数据信息的每一步操作(可通过Edits运算出元数据)。
NameNode启动时,先滚动Edits并生成一个空的edits.inprogress,然后加载Edits和Fsimage到内存中,此时NameNode内存就持有最新的元数据信息。Client开始对NameNode发送元数据的增删改的请求,这些请求的操作首先会被记录到edits.inprogress中(查询元数据的操作不会被记录在Edits中,因为查询操作不会更改元数据信息),如果此时NameNode挂掉,重启后会从Edits中读取元数据的信息。然后,NameNode会在内存中执行元数据的增删改的操作。
由于Edits中记录的操作会越来越多,Edits文件会越来越大,导致NameNode在启动加载Edits时会很慢,所以需要对Edits和Fsimage进行合并(所谓合并,就是将Edits和Fsimage加载到内存中,照着Edits中的操作一步步执行,最终形成新的Fsimage)。SecondaryNameNode的作用就是帮助NameNode进行Edits和Fsimage的合并工作。
SecondaryNameNode首先会询问NameNode是否需要CheckPoint(触发CheckPoint需要满足两个条件中的任意一个,定时时间到和Edits中数据写满了)。直接带回NameNode是否检查结果。
SecondaryNameNode执行CheckPoint操作,首先会让NameNode滚动Edits并生成一个空的edits.inprogress,滚动Edits的目的是给Edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的Edits和Fsimage会拷贝到SecondaryNameNode的本地,然后将拷贝的Edits和Fsimage加载到内存中进行合并,生成fsimage.chkpoint,然后将fsimage.chkpoint拷贝给NameNode,重命名为Fsimage后替换掉原来的Fsimage。NameNode在启动时就只需要加载之前未合并的Edits和Fsimage即可,因为合并过的Edits中的元数据信息已经被记录在Fsimage中。
Fsimage与Edits文件 解析
NameNode在执行格式化之后,会在/opt/servers/hadoop-2.9.2/data/tmp/dfs/name/current
目录下产生如下文件

- Fsimage文件:是namenode中关于元数据的镜像,一般称为检查点,这里包含了HDFS文件系统所有目录以及文件相关信息(Block数量,副本数量,权限等信息)
- Edits文件 :存储了客户端对HDFS文件系统所有的更新操作记录,Client对HDFS文件系统所有的更新操作都会被记录到Edits文件中(不包括查询操作)
- seen_txid:该文件是保存了一个数字,数字对应着最后一个Edits文件名的数字
- VERSION:该文件记录namenode的一些版本号信息,比如:CusterId,namespaceID等
Fsimage文件内容
官方地址
https://hadoop.apache.org/docs/r2.9.2/hadoop-project-dist/hadoop-hdfs/HdfsImageViewer.html
查看oiv命令
[root@node101 current]# hdfs
Usage: hdfs [--config confdir] [--loglevel loglevel] COMMAND
where COMMAND is one of:
oiv apply the offline fsimage viewer to an fsimage
基本语法
hdfs oiv -p 文件类型(xml) -i 镜像文件 -o 转换后文件输出路径
# 进入到fsimage所在文件夹
[root@node101 current]# pwd
/opt/servers/hadoop-2.9.2/data/tmp/dfs/name/current
# 将fsimage文件格式化成xml文件
[root@node101 current]# hdfs oiv -p XML -i fsimage_0000000000000005863 -o /opt/servers/fsimage.xml
22/07/27 21:08:11 INFO offlineImageViewer.FSImageHandler: Loading 4 strings
查看fsimage文件内容
<?xml version="1.0"?>
<fsimage>
<version>
<layoutVersion>-63</layoutVersion>
<onDiskVersion>1</onDiskVersion>
<oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
</version>
<NameSection>
<namespaceId>1393381414</namespaceId>
<genstampV1>1000</genstampV1>
<genstampV2>1024</genstampV2>
<genstampV1Limit>0</genstampV1Limit>
<lastAllocatedBlockId>1073741848</lastAllocatedBlockId>
<txid>265</txid>
</NameSection>
<INodeSection>
<inode>
<id>16398</id>
<type>DIRECTORY</type>
<name>history</name>
<mtime>1592376391028</mtime>
<permission>root:supergroup:0777</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16399</id>
<type>DIRECTORY</type>
<name>done_intermediate</name>
<mtime>1592375256896</mtime>
<permission>root:supergroup:1777</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16400</id>
<type>DIRECTORY</type>
<name>root</name>
<mtime>1592378079208</mtime>
<permission>root:supergroup:0777</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16413</id>
<type>FILE</type>
<name>job_1592375222804_0001-1592375231176-root-word+count-1592375281926-1-1-SUCCEEDED-default-1592375261492.jhist</name>
<replication>3</replication>
<mtime>1592375282039</mtime>
<atime>1592375281980</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>root:supergroup:0777</permission>
<blocks>
<block>
<id>1073741834</id>
<genstamp>1010</genstamp>
<numBytes>33584</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
<inode>
<id>16414</id>
<type>FILE</type>
<name>job_1592375222804_0001_conf.xml</name>
<replication>3</replication>
<mtime>1592375282121</mtime>
<atime>1592375282053</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>root:supergroup:0777</permission>
<blocks>
<block>
<id>1073741835</id>
<genstamp>1011</genstamp>
<numBytes>196027</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
<inode>
<id>16415</id>
<type>DIRECTORY</type>
<name>done</name>
<mtime>1592376776670</mtime>
<permission>root:supergroup:0777</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16427</id>
<type>DIRECTORY</type>
<name>logs</name>
<mtime>1592378009623</mtime>
<permission>root:root:0770</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16428</id>
<type>DIRECTORY</type>
<name>application_1592376944601_0001</name>
<mtime>1592378045481</mtime>
<permission>root:root:0770</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16430</id>
<type>DIRECTORY</type>
<name>wcoutput</name>
<mtime>1592378037463</mtime>
<permission>root:supergroup:0755</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16436</id>
<type>FILE</type>
<name>part-r-00000</name>
<replication>3</replication>
<mtime>1592378037264</mtime>
<atime>1592378037074</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>root:supergroup:0644</permission>
<blocks>
<block>
<id>1073741842</id>
<genstamp>1018</genstamp>
<numBytes>43</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
<inode>
<id>16445</id>
<type>FILE</type>
<name>linux123_39919</name>
<replication>3</replication>
<mtime>1592378045469</mtime>
<atime>1592378045331</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>root:root:0640</permission>
<blocks>
<block>
<id>1073741848</id>
<genstamp>1024</genstamp>
<numBytes>56910</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
<inode>
<id>16446</id>
<type>DIRECTORY</type>
<name>0617</name>
<mtime>1592387393490</mtime>
<permission>root:supergroup:0755</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16449</id>
<type>FILE</type>
<name>banzhang.txt</name>
<replication>1</replication>
<mtime>1592388309046</mtime>
<atime>1592388309026</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>root:supergroup:0644</permission>
<storagePolicyId>0</storagePolicyId>
</inode>
</INodeSection>
</fsimage>
Q:Fsimage中为什么没有记录块所对应DataNode?
 在内存元数据中是有记录块所对应的dn信息,但是fsimage中就剔除了这个信息;HDFS集群在启动的时候会加载image以及edits文件,block对应的dn信息都没有记录,集群启动时会有一个安全模式(safemode),安全模式就是为了让dn汇报自己当前所持有的block信息给nn来补全元数据。后续每隔一段时间dn都要汇报自己持有的block信息。
Edits文件内容
查看oev命令
[root@node101 current]# hdfs
Usage: hdfs [--config confdir] [--loglevel loglevel] COMMAND
where COMMAND is one of:
oev apply the offline edits viewer to an edits file
基本语法
hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
# 进入到fsimage所在文件夹
[root@node101 current]# pwd
/opt/servers/hadoop-2.9.2/data/tmp/dfs/name/current
# 将fsimage文件格式化成xml文件
[root@node101 current]# hdfs oev -p XML -i edits_0000000000000000266-
0000000000000000267 -o /opt/servers/edits.xml
查看edits文件内容
<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
<EDITS_VERSION>-63</EDITS_VERSION>
<RECORD>
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>113</TXID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>114</TXID>
<SRC>/wcoutput/_SUCCESS</SRC>
<MODE>493</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>115</TXID>
<SRC>/wcoutput/part-r-00000</SRC>
<MODE>493</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>116</TXID>
<SRC>/wcoutput</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>117</TXID>
<SRC>/wcoutput/_SUCCESS</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>118</TXID>
<SRC>/wcoutput/part-r-00000</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_DELETE</OPCODE>
<DATA>
<TXID>119</TXID>
<LENGTH>0</LENGTH>
<PATH>/wcoutput/part-r-00000</PATH>
<TIMESTAMP>1592377324171</TIMESTAMP>
<RPC_CLIENTID></RPC_CLIENTID>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>120</TXID>
<SRC>/</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>121</TXID>
<SRC>/tmp</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>122</TXID>
<SRC>/tmp/hadoop-yarn</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>123</TXID>
<SRC>/tmp/hadoop-yarn/staging</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>124</TXID>
<SRC>/tmp/hadoop-yarn/staging/history</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>125</TXID>
<SRC>/tmp/hadoop-yarn/staging/history/done</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_PERMISSIONS</OPCODE>
<DATA>
<TXID>126</TXID>
<SRC>/tmp/hadoop-yarn/staging/history/done/2020</SRC>
<MODE>511</MODE>
</DATA>
</RECORD>
</EDITS>
Q:NameNode如何确定下次启动时加载哪些Edits文件呢?
nn启动时需要加载fsimage文件以及那些没有被2nn进行合并的edits文件,nn如何判断哪些edits已经被合并了呢?
可以通过fsimage文件自身的编号来确定哪些已经被合并。

CheckPoint机制
hdfs-default.xml文件中的设置
<!-- SecondaryNameNode每隔一小时执行一次 -->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
<!-- 一分钟检查一次操作次数,当操作次数达到1百万时,SecondaryNameNode执行一次 -->
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
<description>操作动作次数</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description> 1分钟检查一次操作次数</description>
</property >
Fsimage和edits文件目录配置(hdfs.xml文件)
<!--fsimage文件存储的路径-->
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///opt/servers/hadoopDatas/namenodeDatas</value>
</property>
<!-- edits文件存储的路径 -->
<property>
<name>dfs.namenode.edits.dir</name>
<value>file:///opt/servers/hadoopDatas/dfs/nn/edits</value>
</property>
NameNode故障处理
NameNode故障后,HDFS集群就无法正常工作,因为HDFS文件系统的元数据需要由NameNode来管理维护并与Client交互,如果元数据出现损坏和丢失同样会导致NameNode无法正常工作进而HDFS文件系统无法正常对外提供服务。
如果元数据出现丢失损坏如何恢复呢?
-
将2NN的元数据拷贝到NN的节点下
kill -9 NameNode的进程id
删除NameNode存储的元数据
拷贝SecondNameNode的元数据到原NameNode的数据存储目录
重新启动NameNode
此种方式会存在元数据的丢失。 -
搭建HDFS的HA(高可用)集群,解决NN的单点故障问题!!(借助Zookeeper实现HA,一个Active的NameNode,一个是Standby的NameNode)
NameNode多目录配置
NameNode的本地目录可以配置成多个,且每个目录存放内容相同,增加了可靠性
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.tmp.dir}/name1,file://${hadoop.tmp.dir}/name2</value>
</property>
DataNode工作机制及数据存储

工作机制
- 一个数据块在datanode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
- DataNode启动后向namenode注册,通过后,周期性(1小时)的向namenode上报所有的块信息。(dfs.blockreport.intervalMsec)。
- 心跳是每3秒一次,心跳返回结果带有namenode给该datanode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个datanode的心跳,则认为该节点不可用。
- 集群运行中可以安全加入和退出一些机器。
数据完整性
DataNode节点上的数据损坏了,却没有发现,是很危险的事情,解决数据完整性的方法:
- 当DataNode读取block的时候,它会计算checksum。
- 如果计算后的checksum,与block创建时值不一样,说明block已经损坏。
- client读取其他DataNode上的block。
- 常见的校验算法crc(32),md5(128),sha1(160)
- datanode在其文件创建后周期验证checksum。
掉线时限参数设置

datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。HDFS默认的超时时长为10分钟+30秒。如果定义超时时间为timeout,则超时时长的计算公式为:
timeout = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval。
而默认的dfs.namenode.heartbeat.recheck-interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。
需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<property>
<name>dfs.heartbeat.interval </name>
<value>3</value>
</property>
DataNode的目录结构和namenode不同的是,datanode的存储目录是初始阶段自动创建的,不需要额外格式化。
datanode多目录配置
datanode也可以配置成多个目录,每个目录存储的数据不一样。即:数据不是副本。具体配置如下:- 只需要在value中使用逗号分隔出多个存储目录即可
<!-- 定义dataNode数据存储的节点位置,实际工作中,一般先确定磁盘的挂载目录,然后多个目录用,进行分割 -->
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///{hadoop_home}/hadoopDatas/datanodeDatas</value>
</property>
服役新的数据节点
需求说明:
随着公司业务的增长,数据量越来越大,原有的数据节点的容量已经不能满足存储数据的需求,需要在原有集群基础上动态添加新的数据节点。
环境准备
- 复制一台新的虚拟机出来
将我们纯净的虚拟机复制一台出来,作为我们新的节点 - 修改mac地址以及IP地址
- 关闭防火墙,关闭selinux
- 更改主机名
- 集群机器更改主机名与IP地址映射
- 新增服务器关机重启
- node104安装jdk
- 解压hadoop安装包
- 将node101关于hadoop的配置文件全部拷贝到node104
cd /export/servers/hadoop-2.9.2/etc/hadoop/
scp ./* node04:$PWD
服役新节点步骤
- 创建dfs.hosts文件
cd /opt/servers/hadoop-2.9.2/etc/hadoop/
vi dfs.hosts增加主机名称,包括新服役节点
node101
node102
node103
node104
- node101**(主节点)**编辑hdfs-site.xml添加以下配置,在namenode的hdfs-site.xml配置文件中增加dfs.hosts属性
node101执行以下命令 :
cd /opt/servers/hadoop-2.9.2/etc/hadoop
vim hdfs-site.xml
# 添加一下内容
<property>
<name>dfs.hosts</name>
<value>/opt/servers/hadoop-2.9.2/etc/hadoop/dfs.hosts</value>
</property>
<!--动态上下线配置: 如果配置文件中有, 就不需要配置-->
<property>
<name>dfs.hosts</name>
<value>/opt/servers/hadoop-2.9.2/etc/hadoop/accept_host</value>
</property>
<property>
<name>dfs.hosts.exclude</name>
<value>/opt/servers/hadoop-2.9.2/etc/hadoop/deny_host</value>
</property>
- 刷新namenode,node101执行以下命令刷新namenode
[root@node101 hadoop]# hdfs dfsadmin -refreshNodes
Refresh nodes successful
- 更新resourceManager节点,node101执行以下命令刷新resourceManager
[root@node101 hadoop]# yarn rmadmin -refreshNodes
19/03/16 11:19:47 INFO client.RMProxy: Connecting to ResourceManager at node01/192.168.145.210:8033
- namenode的slaves文件增加新服务节点主机名称,node101编辑slaves文件,并添加新增节点的主机,更改完后,slaves文件不需要分发到其他机器上面去
# node101执行以下命令编辑slaves文件 :
cd /opt/servers/hadoop-2.9.2/etc/hadoop
vim slaves
# 添加一下内容:
node101
node102
node103
node104
- 单独启动新增节点
# node104服务器执行以下命令,启动datanode和nodemanager :
cd /opt/servers/hadoop-2.9.2
sbin/hadoop-daemon.sh start datanode
sbin/yarn-daemon.sh start nodemanager
- 使用负载均衡命令,让数据均匀负载所有机器
node101执行以下命令 :
cd /opt/servers/hadoop-2.9.2
sbin/start-balancer.sh
退役节点
- 创建dfs.hosts.exclude配置文件
在namenode所在服务器的/opt/servers/hadoop-2.9.2/etc/hadoop目录下创建dfs.hosts.exclude文件,并添加需要退役的主机名称
# node01执行以下命令 :
cd /opt/servers/hadoop-2.9.2/etc/hadoop
touch dfs.hosts.exclude
vim dfs.hosts.exclude
# 添加以下内容:
node104
# 特别注意:该文件当中一定要写真正的主机名或者ip地址都行
- 编辑namenode所在机器的hdfs-site.xml配置文件,添加以下配置
cd /opt/servers/hadoop-2.9.2/etc/hadoop
vim hdfs-site.xml
#添加一下内容:
<property>
<name>dfs.hosts.exclude</name>
<value>/opt/servers/hadoop-2.9.2/etc/hadoop/dfs.hosts.exclude</value>
</property>
- 刷新namenode,刷新resourceManager
# 在namenode所在的机器执行以下命令,刷新namenode,刷新resourceManager :
hdfs dfsadmin -refreshNodes
yarn rmadmin -refreshNodes
- 节点退役完成,停止该节点进程
等待退役节点状态为decommissioned(所有块已经复制完成),停止该节点及节点资源管理器。注意:如果副本数是3,服役的节点小于等于3,是不能退役成功的,需要修改副本数后才能退役。
# node104执行以下命令,停止该节点进程 :
cd /opt/servers/hadoop-2.9.2
sbin/hadoop-daemon.sh stop datanode
sbin/yarn-daemon.sh stop nodemanager
- 从include文件中删除退役节点
# namenode所在节点也就是node01执行以下命令删除退役节点 :
cd /opt/servers/hadoop-2.9.2/etc/hadoop
vim dfs.hosts
# 删除后的内容: 删除了node104
node101
node102
node103
- node101执行一下命令刷新namenode,刷新resourceManager
hdfs dfsadmin -refreshNodes
yarn rmadmin -refreshNodes
- 从namenode的slave文件中删除退役节点
# namenode所在机器也就是node01执行以下命令从slaves文件中删除退役节点 :
cd /opt/servers/hadoop-2.9.2/etc/hadoop
vim slaves
删除后的内容: 删除了 node104
node101
node102
node103
- 如果数据负载不均衡,执行以下命令进行均衡负载
node101执行以下命令进行均衡负载
cd /opt/servers/hadoop-2.9.2/etc/hadoop
sbin/start-balancer.sh
block块手动拼接成为完整数据
所有的数据都是以一个个的block块存储的,只要我们能够将文件的所有block块全部找出来,拼接到一起,又会成为一个完整的文件,接下来我们就来通过命令将文件进行拼接:
- 上传一个大于128M的文件到hdfs上面去,我们选择一个大于128M的文件上传到hdfs上面去,只有一个大于128M的文件才会有多个block块。
这里我们选择将我们的spark安装包上传到hdfs上面去。
node101执行以下命令上传spark安装包 - web浏览器界面查看jdk的两个block块id。这里我们看到两个block块id分别为1073742279和1073742280
那么我们就可以通过blockid将我们两个block块进行手动拼接了。

- 根据我们的配置文件找到block块所在的路径
# 根据我们hdfs-site.xml的配置,找到datanode所在的路径
<!-- 定义dataNode数据存储的节点位置,实际工作中,一般先确定磁盘的挂载目录,然后多个目录用,进行分割 -->
<property>
<name>dfs.datanode.data.dir</name>
<value>file:////opt/servers/hadoop-2.9.2/hadoopDatas/datanodeDatas</value>
</property>
# 进入到以下路径 : 此基础路径为 上述配置中value的路径
cd /opt/servers/hadoop-2.9.2/hadoopDatas/datanodeDatas/current/BP-557466926-1549868683602/current/finalized/subdir0/subdir3
- 执行block块的拼接
将不同的各个block块按照顺序进行拼接起来,成为一个完整的文件
cat blk_1073742279 >> spark.tar.gz
cat blk_1073742280 >> spark.tar.gz
移动我们的jdk到/opt路径,然后进行解压
mv spark.tar.gz /opt/
cd /opt/
tar -zxf spark.tar.gz
正常解压,没有问题,说明我们的程序按照block块存储没有问题
HDFS其他重要功能
多个集群之间的数据拷贝
在我们实际工作当中,极有可能会遇到将测试集群的数据拷贝到生产环境集群,或者将生产环境集群的数据拷贝到测试集群,那么就需要我们在多个集群之间进行数据的远程拷贝,hadoop自带也有命令可以帮我们实现这个功能
本地文件拷贝scp
cd /opt/softwares/
scp -r jdk-8u141-linux-x64.tar.gz root@node102:/opt/
集群之间的数据拷贝distcp
cd /opt/servers/hadoop-2.9.2/
bin/hadoop distcp hdfs://node101:9000/jdk-8u141-linux-x64.tar.gz hdfs://cluster2:9000/
hadoop归档文件archive
主要解决HDFS集群存在大量小文件的问题!!
由于大量小文件会占用NameNode的内存,因此对于HDFS来说存储大量小文件造成NameNode内存资源的浪费!
Hadoop存档文件HAR文件,是一个更高效的文件存档工具,HAR文件是由一组文件通过archive工具创建而来,在减少了NameNode的内存使用的同时,可以对文件进行透明的访问,通俗来说就是HAR文件对NameNode来说是一个文件减少了内存的浪费,对于实际操作处理文件依然是一个一个独立的文件。

- 创建归档文件
创建归档文件注意:归档文件一定要保证yarn集群启动 - 归档文件
把/user/input目录里面的所有文件归档成一个叫input.har的归档文件,并把归档后文件存储到/user/output路径下。
[root@node101 hadoop-2.9.2]$ bin/hadoop archive -archiveName input.har –p
/user/input /user/output
- 查看归档
[root@node101 hadoop-2.9.2]$ hadoop fs -lsr /user/output/input.har
[root@node101 hadoop-2.9.2]$ hadoop fs -lsr har:///user/output/input.har
- 解归档文件
[root@node101 hadoop-2.9.2]$ hadoop fs -cp har:///user/output/input.har/* /user/har
hdfs快照snapShot管理
快照顾名思义,就是相当于对我们的hdfs文件系统做一个备份,我们可以通过快照对我们指定的文件夹设置备份,但是添加快照之后,并不会立即复制所有文件,而是指向同一个文件。当写入发生时,才会产生新文件。
- 快照使用基本语法
1、 开启指定目录的快照功能
hdfs dfsadmin -allowSnapshot 路径
2、禁用指定目录的快照功能(默认就是禁用状态)
hdfs dfsadmin -disallowSnapshot 路径
3、给某个路径创建快照snapshot
hdfs dfs -createSnapshot 路径
4、指定快照名称进行创建快照snapshot
hdfs dfs -createSanpshot 路径 名称
5、给快照重新命名
hdfs dfs -renameSnapshot 路径 旧名称 新名称
6、列出当前用户所有可快照目录
hdfs lsSnapshottableDir
7、比较两个快照的目录不同之处
hdfs snapshotDiff 路径1 路径2
8、删除快照snapshot
hdfs dfs -deleteSnapshot <path> <snapshotName>
- 快照操作实际案例
1、开启与禁用指定目录的快照
[root@node101 hadoop-2.9.2]# hdfs dfsadmin -allowSnapshot /user
Allowing snaphot on /user succeeded
[root@node101 hadoop-2.9.2]# hdfs dfsadmin -disallowSnapshot /user
Disallowing snaphot on /user succeeded
2、对指定目录创建快照
注意:创建快照之前,先要允许该目录创建快照
[root@node101 hadoop-2.9.2]# hdfs dfsadmin -allowSnapshot /user
Allowing snaphot on /user succeeded
[root@node101 hadoop-2.9.2]# hdfs dfs -createSnapshot /user
Created snapshot /user/.snapshot/s20190317-210906.549
通过web浏览器访问快照
http://node101:50070/explorer.html#/user/.snapshot/s20190317-210906.549
3、指定名称创建快照
[root@node101 hadoop-2.9.2]# hdfs dfs -createSnapshot /user mysnap1
Created snapshot /user/.snapshot/mysnap1
4、重命名快照
hdfs dfs -renameSnapshot /user mysnap1 mysnap2
5、列出当前用户所有可以快照的目录
hdfs lsSnapshottableDir
6、比较两个快照不同之处
hdfs dfs -createSnapshot /user snap1
hdfs dfs -createSnapshot /user snap2
hdfs snapshotDiff snap1 snap2
7、删除快照
hdfs dfs -deleteSnapshot /user snap1
hdfs回收站
任何一个文件系统,基本上都会有垃圾桶机制,也就是删除的文件,不会直接彻底清掉,我们一把都是将文件放置到垃圾桶当中去,过一段时间之后,自动清空垃圾桶当中的文件,这样对于文件的安全删除比较有保证,避免我们一些误操作,导致误删除文件或者数据。
回收站配置两个参数
默认值fs.trash.interval=0,0表示禁用回收站,可以设置删除文件的存活时间。
默认值fs.trash.checkpoint.interval=0,检查回收站的间隔时间。
要求fs.trash.checkpoint.interval<=fs.trash.interval。
启用回收站
修改所有服务器的core-site.xml配置文件
<!-- 开启hdfs的垃圾桶机制,删除掉的数据可以从垃圾桶中回收,单位分钟 -->
<property>
<name>fs.trash.interval</name>
<value>10080</value>
</property>
查看回收站
回收站在集群的 /user/root/.Trash/ 这个路径下
通过javaAPI删除的数据,不会进入回收站,需要调用moveToTrash()才会进入回收站
//使用回收站的方式: 删除数据
@Test
public void deleteFile() throws Exception{
//1. 获取FileSystem对象
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node101:9000"), configuration, "root");
//2. 执行删除操作
// fileSystem.delete(); 这种操作会直接将数据删除, 不会进入垃圾桶
Trash trash = new Trash(fileSystem,configuration);
boolean flag = trash.isEnabled(); // 是否已经开启了垃圾桶机制
System.out.println(flag);
trash.moveToTrash(new Path("/quota"));
//3. 释放资源
fileSystem.close();
}
恢复回收站数据
hdfs dfs -mv trashFileDir hdfsdir
trashFileDir :回收站的文件路径
hdfsdir :将文件移动到hdfs的哪个路径下
清空回收站
hdfs dfs -expunge
日志采集综合案例
需求分析

- 定时采集已滚动完毕日志文件
- 将待采集文件上传到临时目录
- 备份日志文件
代码实现
1.创建相关配置文件collector.properties
LOGS.DIR=e:/logs/
LOG.PREFIX=access.log.
LOG.TMP.FOLDER=e:/log_tmp/
HDFS.TARGET.FOLDER=/collect_log/
BAK.FOLDER=e:/log_bak/
2.创建配置类
package com.hadoop.hdfs;
public class Constant {
public static final String LOGS_DIR="LOGS.DIR";
public static final String LOG_PREFIX="LOG.PREFIX";
public static final String LOG_TMP_FOLDER="LOG.TMP.FOLDER";
public static final String HDFS_TARGET_FOLDER="HDFS.TARGET.FOLDER";
public static final String BAK_FOLDER="BAK.FOLDER";
}
3.创建配置工具类
package com.hadoop.hdfs;
import java.io.IOException;
import java.util.Properties;
public class PropTool {
//volatile关键字禁止指令重排序,保证可见性可有序性,不保证原子性
private static volatile Properties prop = null;
public static Properties getProp() throws IOException {
if(prop==null){
synchronized ("lock"){
if(prop==null){
prop = new Properties();
prop.load(LogCollectorTask.class.getClassLoader().getResourceAsStream("collector.properties"));
}
}
}
return prop;
}
}
4.编写文件上传代码
package com.hadoop.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.TimerTask;
public class LogCollectorTask extends TimerTask {
@Override
public void run() {
Properties prop = null;
try {
prop = PropTool.getProp();
} catch (IOException e) {
e.printStackTrace();
}
//采集业务逻辑
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String todayStr = dateFormat.format(new Date());
//1. 扫描指定目录,找到待上传文件,原始日志目录
File logsDir = new File(prop.getProperty(Constant.LOGS_DIR));
final String log_prefix = prop.getProperty(Constant.LOG_PREFIX);
File[] uploadFiles = logsDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return s.startsWith(log_prefix);
}
});
// 2.把待上传文件转移到临时目录
//判断文件是否存在
File tmpFile = new File(prop.getProperty(Constant.LOG_TMP_FOLDER));
if(!tmpFile.exists()){
tmpFile.mkdirs();
}
for (File uploadFile : uploadFiles) {
uploadFile.renameTo(new File(tmpFile.getPath() + "/" + uploadFile.getName()));
}
//3.使用hdfs api上传日志文件 到指定目录
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS","hdfs://node101:9000");
FileSystem fs = null;
try {
fs = FileSystem.get(configuration);
//判断hdfs目标路径是否存在,备份目录是否存在
Path path = new Path(prop.getProperty(Constant.HDFS_TARGET_FOLDER) + todayStr);
if(!fs.exists(path)){}
fs.mkdirs(path);
File bakFolder = new File(prop.getProperty(Constant.BAK_FOLDER) + todayStr);
if(!bakFolder.exists()){
bakFolder.mkdirs();
}
File[] files = tmpFile.listFiles();
for (File file : files) {
//按照日期存放
fs.copyFromLocalFile(new Path(file.getPath()), new Path(prop.getProperty(Constant.HDFS_TARGET_FOLDER)
+ todayStr + "/" + file.getName()));
//4.上传后的文件转移到备份目录
file.renameTo(new File(bakFolder.getPath()+"/"+file.getName()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.编写定时任务代码
package com.hadoop.hdfs;
import java.util.Timer;
public class LogTimerUpload {
/*
- 定时采集已滚动完毕日志文件
- 将待采集文件上传到临时目录
- 备份日志文件
*/
public static void main(String[] args) {
// LogCollectorTask logLoad = new LogCollectorTask();
// logLoad.run();
//
Timer timer = new Timer();
//定时采集任务的调度
// task:采集的业务逻辑
//延迟时间
//周期时间
timer.schedule(new LogCollectorTask(),0,3600*1000);
}
}
重点参数
参数 | 含义 | 默认值 | 推荐值 |
---|---|---|---|
dfs.blocksize | 块大小 | 2.X版本128M | 根据磁盘读取速度设置,寻址时间最好在读取时间的1% |
fs.trash.interval | 开启HDFS垃圾桶机制,删除掉的数据可以从垃圾桶中回收 单位为分钟 | ||
dfs.name.dir | NameNode 元数据存放位置 | 使用core-site.xml中的hadoop.tmp.dir/dfs/name | |
dfs.data.dir | DataNode在本地磁盘存放block的位置,可以是以逗号分隔的目录列表,DataNode循环向磁盘中写入数据,每个DataNode可单独指定与其它DataNode不一样 | ${hadoop.tmp.dir}/dfs/data | |
dfs.namenode.handler.count | NameNode用来处理来自DataNode的RPC请求的线程数量 | 10 | 建议设置为DataNode数量的10%,一般在10~200个之间 |
| dfs.datanode.handler.count | DataNode用来连接NameNode的RPC请求的线程数量,在DataNode上设定 | 3 | 取决于系统的繁忙程度
设置太小会导致性能下降甚至报错 |
| dfs.permissions | 如果是true则检查权限,否则不检查(每一个人都可以存取文件) ,于NameNode上设定 | true | true |
| dfs.replication | 在文件被写入的时候,每一块将要被复制多少份 | 3 | |
| hadoop.tmp.dir | HDFS与本地磁盘的临时文件 | /tmp/hadoop-${user.name} | |
| io.file.buffer.size | 设定在读写数据时的缓存大小,应该为硬件分页大小的2倍 | 默认是4096 | 建议为65536 ( 64K) |
常用端口号
参数 | 描述 | 默认 | 配置文件 | 例子值 |
---|---|---|---|---|
fs.default.name namenode | namenode RPC交互端口 | 8020/9000 | core-site.xml | hdfs://master:8020/ |
dfs.http.address | NameNode web管理端口 | 50070 | hdfs- site.xml | 0.0.0.0:50070 |
dfs.datanode.address | datanode 控制端口 | 50010 | hdfs -site.xml | 0.0.0.0:50010 |
dfs.datanode.ipc.address | datanode的RPC服务器地址和端口 | 50020 | hdfs-site.xml | 0.0.0.0:50020 |
dfs.datanode.http.address | datanode的HTTP服务器和端口 | 50075 | hdfs-site.xml | 0.0.0.0:50075 |