HDFS 的使用场景: 适合一次写入,多次读出的场景。一个文件经过创建、写入和关闭 之后就不需要改变。
HDFS优点:
- 高容错。
- 适合处理大规模数据。
- 可构建在廉价机器上。
HDFS缺点:
-
相比其它文件系统访问数据速度更慢。
-
HDFS不适合存取大量小文件,每个文件会占用NameNode150字节的内存,128G内存的NameNode能存取9亿个文件;检索时间超过了读取时间。
-
HDFS不支持并发写入,且仅支持数据追加,不支持文件随机修改。
HDFS Block
HDFS按block存储,blocksize可以另外设置,Hadoop3.x默认128MB。
HDFS读取数据时间(rt) = 数据块寻址时间(at) + 磁盘传输数据时间(tt)
若blocksize太小,一个文件会被分成多个数据块存储,读数据需要进行多次寻址,此时at较大;
若blocksize太大,没办法有效的利用并发寻找数据块的性能,此时tt较大。
合理的blocksize大小取决于磁盘传输速率,通常机械硬盘设置128MB,固态硬盘设置为256MB。
HDFS备份数
一般的分布式集群中多台服务器作为DataNode,可以自定义HDFS中文件的备份数,若有三个DataNode则最多可以备份三份。
若备份数设置大于三,则集群中增加服务器数量时可以自动备份;
若备份数设置小于三,则集群会选择最近的节点进行数据备份;
在我们之前搭建的单节点伪分布式集群中,实际只能备份一份在localhost,查找该备份的方法在之前的文章中有介绍过。
HDFS API操作
- 获取客户端对象
- 执行api操作
- 关闭资源
Demo中尝试使用了JUnit单元测试,@Test注释了待测试的代码块,@Before注释的方法为@Test之前执行的代码块,@After注释的方法为@Test之后执行的代码块。之前在学校做项目没做过类似的单元测试,都直接写完功能做整体的系统测试了,但其实开发流程规范化也是十分重要的。
public class HdfsClient {
private FileSystem fs;
@Before
public void init() throws URISyntaxException, IOException, InterruptedException {
// 连接集群的NN地址
URI uri = new URI("hdfs://localhost:9000");
// 创建一个配置文件
Configuration configuration = new Configuration();
// configuration.set("dfs.replication","2");
String user = "aaron";
// 获取客户端对象
fs = FileSystem.get(uri, configuration, user);
}
@After
public void close() throws IOException {
// 关闭资源
fs.close();
}
@Test
/**
* 待测试的代码块
*/
}
参数优先级排序:客户端代码中设置的值 > 项目资源目录下的配置文件 > 服务器的自定义配置(xxx-site.xml) > 服务器的默认配置(xxx-default.xml)
(1) 创建目录
@Test
public void testmkdir() throws IOException, URISyntaxException {
fs.mkdirs(new Path("/xiyou/huaguoshan"));
}
**弱智踩坑:**一开始URI uri = new URI(“hdfs://localhost:9000”)的hdfs后面没有打冒号,测试代码后,用Hadoop Shell和浏览NN的web页面发现都没有成功创建文件,尝试用list列出了根目录下全部文件的详细信息,发现访问的是本地文件系统并不是HDFS;错误虽然智障,但可以顺便阅读FileSystem.get方法源码。
String scheme = uri.getScheme();
String authority = uri.getAuthority();
if (scheme == null && authority == null) {
return get(conf);
}
scheme是根据uri解析的协议名,authority是主机名。如果是正确uri,scheme将为hdfs,authority为localhost:9000;由于这里传入的uri不合法所以scheme为null,进入get(conf),将返回默认的文件系统;
public static FileSystem get(Configuration conf) throws IOException {
return get(getDefaultUri(conf), conf);
}
public static URI getDefaultUri(Configuration conf) {
URI uri = URI.create(fixName(conf.get("fs.defaultFS", "file:///")));
if (uri.getScheme() == null) {
throw new IllegalArgumentException("No scheme in default FS: " + uri);
} else {
return uri;
}
}
默认的文件系统是file:///,即为本地的文件系统,这也解释了为什么之前list返回的都是本地文件系统根目录的文件。
(2) 上传:
@Test
public void testPut() throws IOException {
// 参数一:是否删除原文件 参数二:是否允许覆盖 参数三:原文件路 径 参数四:目的地路径
fs.copyFromLocalFile(false, false,
new Path("/Users/aaron/sunwukong.txt"),
new Path("/xiyou/huaguoshan"));
}
(3) 下载:
@Test
public void testGet() throws IOException {
// 参数一:是否删除原文件 参数二:原文件路径 参数三:目标地址路径 参数四:是否开启CRC校验
fs.copyToLocalFile(false, new Path("/xiyou/huaguoshan"),
new Path("/Users/aaron/"), false);
}
(4) 删除
@Test
public void testRm() throws IOException {
// 参数一:待删除路径 参数二:是否递归删除
fs.delete(new Path("/xiyou"), true);
}
注意如果参数一的路径下非空,参数二选择不递归删除会抛出非空异常。
(5) 更名和移动
@Test
public void testmv() throws IOException {
// 参数一:原文件路径 参数二:目标文件路径
// 修改文件名
fs.rename(new Path("/jingguo/weiguo.txt"), new Path("/jingguo/wg.txt"));
// 文件移动和更名
fs.rename(new Path("/jingguo/wg.txt"), new Path("/weiguo.txt"));
// 修改目录名
fs.rename(new Path("/jingguo"), new Path("/sanguo"));
// 目录的移动和更名
fs.rename(new Path("/user/sanguo"), new Path("/sanguo"));
}
(6) 获取文件详细信息
@Test
public void fileDetail() throws IOException {
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/sanguo"), true);
System.out.println(listFiles);
while (listFiles.hasNext()) {
LocatedFileStatus fileStatus = listFiles.next();
System.out.println("========" + fileStatus.getPath() + "=========");
System.out.println(fileStatus.getPermission());
System.out.println(fileStatus.getOwner());
System.out.println(fileStatus.getGroup());
System.out.println(fileStatus.getLen());
System.out.println(fileStatus.getModificationTime());
System.out.println(fileStatus.getReplication());
System.out.println(fileStatus.getBlockSize());
System.out.println(fileStatus.getPath().getName());
// 获取块信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(Arrays.toString(blockLocations));
}
}
(7) 判断是文件还是目录
@Test
public void testFile() throws IOException {
FileStatus[] listStatus = fs.listStatus(new Path("/"));
for (FileStatus fileStatus : listStatus) {
if(fileStatus.isFile()) {
System.out.println("文件:"+fileStatus.getPath().getName());
} else {
System.out.println("目录:"+fileStatus.getPath().getName());
}
}
}