常用的hdfs操作
一、实验目的
- 理解HDFS在Hadoop体系结构中的角色
- 熟练使用HDFS操作常用的shell命令
- 熟悉HDFS操作常用的Java API
二、实验平台
- 操作系统:CentOS 8
- Hadoop版本:3.3.1
- jdk版本:1.8
- Java IDE:Eclipse
三、实验内容
1. 使用Hadoop命令操作分布式文件系统。
-
新建目录
在本地和hadoop中分别创建文件夹:在本地创建目录:
Hadoop创建目录:
-
上传文件至dfs
切换到本地input目录下,创建文件并添加数据:hello hadoop。
将该文件上传至hadoop:使用hadoop fs -put <要上传的文件> <hdfs目录>
命令。
查看上传到HDFS的文件:
-
移动与删除
列出HDFS中的目录和文件:
将helloworld.txt移动到根目录:
删除helloworld.txt;
下表列出了Hadoop常用的shell命令,在之后使用的时候可以作为参考。
名称选项 | 使用格式 | 含义 |
---|---|---|
-lsr | -lsr <路径> | 递归查看指定路径的目录结构 |
-du | -du <路径> | 统计目录下个文件大小 |
-dus | -dus <路径> | 汇总统计目录下文件(夹)大小 |
-count | -count [-q] <路径> | 统计文件(夹)数量 |
-mv | -mv <源路径> <目的路径> | 移动 |
-cp | -cp <源路径> <目的路径> | 复制 |
-rm | -rm [-skipTrash] <路径> | 删除文件/空白文件夹 |
-rmr | -rmr [-skipTrash] <路径> | 递归删除 |
-put | -put <多个 linux 上的文件> <hdfs 路径> | 上传文件 |
-copyFromLocal | -copyFromLocal <多个 linux 上的文件><hdfs 路径> | 从本地复制 |
-moveFromLocal | -moveFromLocal <多个 linux 上的文件><hdfs 路径> | 从本地移动 |
-getmerge | -getmerge <源路径> <linux 路径> | 合并到本地 |
-cat | -cat <hdfs 路径> | 查看文件内容 |
-text | -text <hdfs 路径> | 查看文件内容 |
-copyToLocal | -copyToLocal [-ignoreCrc] [-crc] [hdfs 源路径] [linux 目的路径] | 从HDFS复制到本地 |
-moveToLocal | -moveToLocal [-crc] <hdfs 源路径> <linux目的路径> | 从HDFS移动到本地 |
-mkdir | -mkdir <hdfs 路径> | 创建空白文件夹 |
-setrep | -setrep [-R] [-w] <副本数> <路径> | 修改副本数量 |
-touchz | -touchz <文件路径> | 创建空白文件 |
2. HDFS-JAVA接口之读取文件
-
在eclipse中创建Hadoop项目
(1) 创建项目,新建lib目录
(2)将Hadoop项目所需要的jar包copy到lib目录下。
将之前下载的hadoop压缩包解压到本地,打开share/hadoop的common目录和hdfs目录并将其中的jar包全部拷贝至项目的lib目录下。
选中所有jar包添加到项目依赖
然后就可以开始进行编写hadoop项目了 -
FileSystem对象
要从Hadoop文件系统中读取文件,最简单的办法是使用java.net.URL
对象打开数据流,从中获取数据。不过这种方法一般要使用FsUrlStreamHandlerFactory
实例调用setURLStreamHandlerFactory()
方法。不过每个Java虚拟机只能调用一次这个方法,所以如果其他第三方程序声明了这个对象,那我们将无法使用了。
因为有时候我们不能在程序中设置URLStreamHandlerFactory
实例,这个时候咱们就可以使用FileSystem API来打开一个输入流,进而对HDFS进行操作。 -
FileSystem API示例
首先我们在本地创建一个文件,然后上传到HDFS以供测试。
接下来,在eclipse中编写代码,使用FileSystem,查看刚刚上传的文件。
运行出现错误:
因为我们没有log4j.properties文件,我们在src目录下创建一个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 #--------console----------- log4j.rootLogger=info,myconsole,myfile log4j.appender.myconsole=org.apache.log4j.ConsoleAppender log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout #log4j.appender.myconsole.layout.ConversionPattern =%d [%t] %-5p [%c] - %m%n #log4j.rootLogger=error,myfile log4j.appender.myfile=org.apache.log4j.DailyRollingFileAppender log4j.appender.myfile.File=/tmp/flume.log log4j.appender.myfile.layout=org.apache.log4j.PatternLayout log4j.appender.myfile.layout.ConversionPattern =%d [%t] %-5p [%c] - %m%n
然后再次运行
还是不行,如果我们写的直接是虚拟机的ip地址,那默认访问的应该是8020端口,我在Hadoop配置core-site.xml文件的时候设置的是8088端口,所以需要修改端口号。
还是不行,
检查了一下我的hadoop启动情况,resourceManager没有启动,而ResourceManager的作用是:负责集群中所有资源的统一管理和分配,它接收来自各个NodeManager的资源汇报信息,并把这些信息按照一定的策略分配给各个ApplicationMaster。
原来是我yarn-site.xml中resourcemanager的ip写错了,详细过程请看这篇文章resourcemanager启动失败
最后启动成功:
回到eclipse,重新运行代码,还是出现重连的情况,然后突然想起来是不是我的防火墙没有关,使用systemctl status firewalld.service
查看防火墙
果然没有关防火墙,使用systemctl stop firewalld.service
关闭防火墙,再输入systemctl disable firewalld.service
永久关闭防火墙
然后再回到eclipse运行代码,现在是出现乱码错误:
点击"Windows–>preferences"
选择"general–>workspace–>text file encoding",选择other,然后选择utf-8
最后"apply and close",最后再重新运行,这下终于可以了:
FileSystem是一个通用的文件系统API,FileSystem实例有下列几个静态工厂方法用来构造对象。public static FileSystem get(Configuration conf)throws IOException public static FileSystem get(URI uri,Configuration conf)throws IOException public static FileSystem get(URI uri,Configuration conf,String user)throws IOException
Configuration对象封装了客户端或服务器的配置,通过设置配置文件读取类路径来实现(如:/etc/hadoop/core-site.xml)。
(1) 第一个方法返回的默认文件系统是在core-site.xml中指定的,如果没有指定,就使用默认的文件系统。
(2) 第二个方法使用给定的URI方案和权限来确定要使用的文件系统,如果给定URI中没有指定方案,则返回默认文件系统,
(3) 第三个方法作为给定用户来返回文件系统,这个在安全方面来说非常重要。 -
FSDataInputStream对象
实际上,FileSystem对象中的open()方法返回的就是FSDataInputStream对象,而不是标准的java.io类对象。这个类是继承了java.io.DataInputStream的一个特殊类,并支持随机访问,由此可以从流的任意位置读取数据。在有了FileSystem实例之后,我们调用open()函数来获取文件的输入流。
public FSDataInputStream open(Path p)throws IOException public abstract FSDataInputStream open(Path f,int bufferSize)throws IOException
使用FSDataInputStream获取HDFS的/user/tmp/目录下的task.txt的文件内容,并输出
首先我们在本地创建task.txt文件,写入"怕什么真理无穷,进一寸有一寸的欢喜。",然后上传到hdfs
在eclipse中编写代码:
成功输出:
3. HDFS-JAVA接口之上传文件
FSDataOutputStream对象
FileSystem类有一系列新建文件的方法,最简单的方法是给准备新建的文件制定一个path对象,然后返回一个用于写入数据的输出流:
public FSDataOutputStream create(Path p)throws IOException
该方法有很多重载方法,允许我们指定是否需要强制覆盖现有文件,文件备份数量,写入文件时所用缓冲区大小,文件块大小以及文件权限。
注意:create()方法能够为需要写入且当前不存在的目录创建父目录,即就算传入的路径是不存在的,该方法也会为你创建一个目录,而不会报错。如果有时候我们并不希望它这么做,可以先用exists()方法先判断目录是否存在。
我们在写入数据的时候经常想要知道当前的进度,API也提供了一个Progressable用于传递回调接口,这样我们就可以很方便的将写入datanode的进度通知给应用了。
package org.apache.hadoop.util;
public interface Progressable{
public void progress();
}
在本地目录下创建test2.txt文件,并输入如下数据,注意这里是在Windows的本地目录下创建文件:
迢迢牵牛星,皎皎河汉女。
纤纤擢素手,札札弄机杼。
终日不成章,泣涕零如雨。
河汉清且浅,相去复几许?
盈盈一水间,脉脉不得语。
《迢迢牵牛星》
使用FSDataOutputStream对象将文件上传至HDFS的/user/tmp/目录下,并打印进度。
编写代码如下:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Progressable;
public class FileSystemUpload {
public static void main(String[] args) throws IOException {
//路径最好写绝对路径,要不然容易报找不到文件的错误
File localPath = new File("E:\\Users\\cl\\eclipse-workspace\\hadoop\\src\\unit2\\test2.txt");
//这里是hdfs的路径
String hdfsPath = "hdfs://192.168.*.*:8088/user/tmp/test2.txt";
//获取输入对象
InputStream in = new BufferedInputStream(new FileInputStream(localPath));
Configuration config=new Configuration();
FileSystem fs = null;
try {
//最后一个参数是指以什么身份上传文件,如果不写默认以你创建的用户的身份上传
fs = FileSystem.get(URI.create(hdfsPath), config, "root");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//待上传文件大小
long fileSize = localPath.length() > 65536 ? localPath.length() / 65536 : 1;
FSDataOutputStream out = fs.create(new Path(hdfsPath), new Progressable() {
//方法在每次上传了64KB字节大小的文件之后会自动调用一次
long fileCount = 0;
public void progress() {
System.out.println("总进度"+(fileCount / fileSize)*100+"%");
fileCount++;
}
});
//最后一个参数的意思是使用完之后是否关闭流
IOUtils.copyBytes(in, out, 2048, true);
}
}
运行结果:
4. HDFS-JAVA接口之删除文件
-
列出文件
在HDFS的API中就提供了listStatus()
方法来实现该功能。public FileStatus[] listStatus(Path f)throws IOException public FileStatus[] listStatus(Path f,PathFilter filter)throws IOException public FileStatus listStatus(Path[] files)throws IOException public FileStatus() listStatus(Path[] files,PathFilter filter)throws IOException
当传入参数是一个文件时,他会简单的转变成以数组方式返回长度为1的FileStatus对象,当传入参数是一个目录时,则返回0或多个FileStatus对象,表示此目录中包含的文件和目录。
现在我们使用
listStatus()
方法来列出hdfs根目录下的文件夹与user目录下的文件夹。
运行结果:
-
删除文件
使用FileSystem的delete()方法可以永久性删除文件或目录。public boolean delete(Path f,boolean recursive)throws IOException
如果f是一个文件或者空目录,那么recursive的值可以忽略,当recursize的值为true,并且p是一个非空目录时,非空目录及其内容才会被删除(否则将会抛出IOException异常)。
我们来创建两个目录,一个是空目录,一个是非空目录
然后编写代码将这两个目录删除
运行结果: