1、HDFS的体系结构
HDFS的优势:
- 存储超大文件
- 标准流式访问:“一次写入,多次读取”
- 运行在廉价的商用机器集群上
HDFS的缺点:
- 不能满足低延迟的数据访问
- 无法高效存储大量小文件
- 暂时不支持多用户写入及随意修改文件
HDFS体系结构:
(图2.1) HDFS体系结构
1.1 基本概念
1. 文件块
hdfs中的文件被分成 块 进行存储,如图2.1 中 DataNode 中的以数字编号的方块。它是文件存储处理的最小逻辑单元,默认块大小为 64MB,使用文件块的好处是:
- 文件的所有块并不需要存储在同一个磁盘上,可以利用集群上的任意一个磁盘进行存储。
- 对分布式系统来说,由于块的大小是固定的,因此计算单个磁盘能存储多少个块就相对容易,可简化存储管理。
- 在数据冗余备份时,将每个块复制到少数几台独立的机器上(默认为三台),可以确保在块、磁盘或机器发生故障后数据不会丢失。如果发现一个块不可用,系统会从其他地方读取另一个副本,这个过程对用户是透明的。
#显示块信息
hdfs fsck / -files -blocks
注意:fsck 命令将列出文件系统中根目录 “/” 下各个文件由哪些块构成。fsck 命令只是从NameNode 获取信息,并不与任何 DataNode 交互,因此并不真正获取数据。
2. NameNode和DataNode节点
《 NameNode 和 DataNode 属于 HDFS集群的两类节点 》
- NameNode 负责管理文件系统的命名空间,属于管理者角色。
- DataNode 根据需要存储、检索数据块,并定期向 NameNode 发送存储的块的列表,属于工作者。
注意:DataNode 负责管理文件系统客户端的文件读写请求,并在 NameNode 的统一调度下进行数据块的创建、删除和复制工作。如果在 NameNode 上的数据损坏,HDFS 中所有的文件都不能被访问,由此可见 NameNode 节点的重要性。为了保证 NameNode 的高可用性,Hadoop 对 NameNode 进行了补充,即 Secondary NameNode 节点。
3. Secondary NameNode节点
系统会同步一个 Secondary NameNode,也称二级 NameNode.相当于 NameNode 的快照,能够周期性地备份 NameNode,记录 NameNode 中的元数据等,也可以用来恢复 NameNode,但 Secondary NameNode 中的备份会滞后于 NameNode ,所以会带来一定的数据损失。为了防止宕机,通常是将 Secondary NameNode 和 NameNode 设置为不同的主机。使用 hdfs-site.xml 中配置的dfs.namenode.secondary.http-address 属性值可以通过浏览器查看 Secondary NameNode 的运行状态。
1.2 Master / Slave 架构
一个 HDFS 集群由一个 NameNode 和多个 DataNode 组成,属于典型的 Master / Slave 模式。
读写流程如下:
- 数据读流程:由客户端向 NameNode 请求访问某个文件,NameNode返回该文件所在位置即在哪个 DataNode 上,然后由客户端从该 DataNode 读取数据。
- 数据写流程:由客户端向 NameNode 发出文件写请求,NameNode 告诉客户该向哪个 DataNode写入该文件,然后由客户将文件写入该DataNode 节点,随后该DataNode 将该文件自动复制到其他 DataNode 节点上,默认三份备份。
1.节点添加
可扩展性是HDFS 的一个重要特性,向 HDFS 集群中添加节点很容易实现,添加一个新的 DataNode 节点的步骤如下:
- 对新节点进行系统配置,包括 hostname、hosts 文件、JDK 环境、防火墙等。
- 在新加节点上安装好 Hadoop,要和 NameNode 使用相同的配置,可以直接从 NameNode 复制。
- 在NameNode 上修改 $HADOOP_HOME/etc/hadoop/Slaves 文件,加入新加节点主机名。
- 运行启动命令:
bin/start-all.sh
2.负载均衡
HDFS 的数据在各个 DataNode 中的分布可能很不均匀,尤其是在 DataNode 节点出现故障或新增 DataNode 节点时。使用如下命令可重新平衡 DataNode 上的数据块的分布:
sbin/start-balancer.sh
3.安全机制
Master/Slave 架构通过 NameNode 来统一调度,没有NameNode,文件系统将无法使用。Hadoop采用两种机制来确保NameNode的安全。
- 第一种是将NameNode 上存储的元数据文件转移到其他文件系统中
- 第二种就是使用Secondary NameNode 同步备份
2、Hadoop FS Shell 命令
语法:hadoop fs <args>
其中 “hadoop” 命令位于 $HADOOP_HOME/bin 目录下,“fs” 为其参数,表示 FileSystem Shell , "<args>" 是 fs 的子命令,包括:
- 创建目录:mkdir
- 列表文件:ls
- 查看文件:cat
- 转移文件:put、get、mv、cp
- 删除文件:rm、rmr
- 管理命令:test、du、expunge
1、 创建目录:mkdir
语法:
hadoop fs -mkdir <paths>
示例:
# 在 HDFS 中创建 “/user”目录
hadoop fs -mkdir /user
# 在 HDFS 中创建 “/user/hadoop” 目录
hadoop fs -mkdir /user/hadoop
# 同时创建多个目录
hadoop fs -mkdir /user/hadoop/dir1 /user/hadoop/dir2
2、 列表文件:ls
语法:
hadoop fs -ls <args>
3、 查看文件:cat
语法:
hadoop fs -cat URL
示例:
# 查看 HDFS 文件file1.txt 和 file2.txt
hadoop fs -cat /input2/file1.txt /input2/file2.txt
# 查看本地文件 file3.txt
hadoop fs -cat file:///home/hduser/file3.txt
4、 转移文件:put、get、mv、cp
put命令:
语法:
hadoop fs -put <localsrc> . . . <dst>
示例:
# 将本地文件复制到 HDFS 目录 “/input2”
hadoop fs -put /home/hduser/file/file1.txt /input2
# 将本地多个文件复制到 HDFS 目录 “/input2”
hadoop fs -put /home/hduser/file/file1.txt /home/hduser/file/file2.txt /input2
get命令:
语法:
hadoop fs -get <src> <localdst>
示例:
# 将HDFS 文件 “/input2/file1” 复制到本地文件系统 “$HOME/file”中
hadoop fs -get /input2/file1 $HOME/file
mv命令:
语法:
hadoop fs -mv URL [URI...] <dest>
注意:不允许在不同的文件系统间移动文件,也就是说所有路径都必须是同一文件系统 URI 格式。
示例:
# 将HDFS上的file1.txt、file2.txt 移动到 dir1 中
hadoop fs -mv /input2/file1.txt /input2/file2.txt /user/hadoop/dir1
cp命令:
语法:
hadoop fs -cp URI [URI ...] <dest>
注意:将文件从源路径复制到目标路径,允许多个源路径,目标路径必须是一个目录。不协调子不同的文件系统间复制文件。
示例:
# HDFS 中复制多个文件到 “/user/hadoop/dir1”
hadoop fs -cp /input2/file1.txt /input2/file2.txt /user/hadoop/dir1
# 在本地文件系统中复制多个文件到目录 “file:///tmp”
hadoop fs -cp file:///file1.txt file:///file2.txt file:///tmp
5、 删除文件:rm、rmr
rm命令:
语法:
hadoop fs -rm URI [URI...]
注意:删除指定的文件,只删除非空目录和文件。
示例:
# 删除非空文件
hadoop fs -rm /input2/file1.txt
rmr命令:
语法:
hadoop fs -rmr URI [URI...]
注意:rm的递归版本,整个文件夹及子文件夹将全部删除。
示例:
# 递归删除
hadoop fs -rmr /user/hadoop/dir1
6、 管理命令:test、du、expunge
test命令:
语法:
hadoop fs -test -[选项] URI
选项:
-e:检查文件是否存在。如果存在则返回0.
-z:检查文件是否0字节。如果是则返回0.
-d:检查路径是否为目
du 命令:
语法:
hadoop fs -du URI [URI ...]
注意:显示目录中所有文件的大小
示例:
# 显示文件的大小,如果是目录则列出所有文件及其大小
hadoop fs -du /input2
# 显示文件的大小,如果是目录则统计总大小
hadoop fs -du -s /input2/file1.txt
expunge命令:
语法:
# 清空回收站
hadoop fs -expunge
3、HDFS Java API
创建一个Map/Reduce Project 项目,在src目录下,导入core-site.xml文件
core-site.xml:https://pan.baidu.com/s/11mQQiWpvm7CE3b0ObwWA-A
提取码:luwy
项目结构图:
1 创建文件
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemCreate {
public static void main(String[] args) throws IOException {
String uri=args[0];//如:hdfs://node1:9000/input2/file2.txt
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path f=new Path(uri);
FSDataOutputStream outputStream=fs.create(f);
IOUtils.write("Hello!Hadoop HDFS!", outputStream);
}
}
2 复制文件
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemCopy {
public static void main(String[] args) throws IOException {
String srcFile=args[0];//必须是本地文件,如:/home/hduser/file/file2.txt
String dstFile=args[1];//必须是HDFS文件,如:hdfs://node1:9000/input2/file2.txt
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path srcPath=new Path(srcFile);
Path dstPath=new Path(dstFile);
fs.copyFromLocalFile(srcPath, dstPath);
}
}
3 删除文件
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemDelete {
public static void main(String[] args) throws IOException {
String uri=args[0];//如:hdfs://node1:9000/input3
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path f=new Path(uri);
fs.delete(f,true);
}
}
4 重命名文件
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemRename {
public static void main(String[] args) throws IOException {
String fromFile=args[0];//如:hdfs://node1:9000/input2/file2.txt
String toFile=args[1];//如:hdfs://node1:9000/input2/file2new.txt
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path fromPath=new Path(fromFile);
Path toPath=new Path(toFile);
fs.rename(fromPath, toPath);
}
}
5 文件查询
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemGetStatus {
public static void main(String[] args) throws IOException {
String uri=args[0];//如:/input2/file1.txt
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path f=new Path(uri);
FileStatus stat=fs.getFileStatus(f);
System.out.println("文件路径:"+stat.getPath());
System.out.println("文件块大小:"+stat.getBlockSize());
System.out.println("文件大小:"+stat.getLen());
System.out.println("副本数量:"+stat.getReplication());
System.out.println("用户:"+stat.getOwner());
System.out.println("用户组:"+stat.getGroup());
System.out.println("权限:"+stat.getPermission().toString());
}
}
有时我们并不知道文件的具体名称,此时便可使用 listStatus() 方法列出目录内容。
package hdfs;
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileSystemListStatus {
public static void main(String[] args) throws IOException {
String uri=args[0];//如:/input2
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path f=new Path(uri);
FileStatus[] stats=fs.listStatus(f);
for(FileStatus stat : stats)
System.out.println(String.format("%s\t%s\t%s\t",
stat.getPermission().toString(),stat.getOwner(),stat.getPath()));
}
}