打怪升级之小白的大数据之旅(四十五)<认识HDFS与常用操作>
Posted GaryLea
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了打怪升级之小白的大数据之旅(四十五)<认识HDFS与常用操作>相关的知识,希望对你有一定的参考价值。
打怪升级之小白的大数据之旅(四十五)
认识HDFS与常用操作
上次回顾
- 上一章常见异常就不说了,我就大概说一下整个Hadoop的搭建吧,首先我们先对单台服务器进行配置
- 第一步:我们需要创建一台最小软件的CentOS系统,并进行一些基本配置,例如IP设置,创建用户,主机名与hosts文件设置等,然后进行服务器的克隆,我们安装Hadoop最小要求,准备三台服务器,然后重复前面的基本配置
- 需要下载一些常见的插件,如VIM以及安装一下JDK与Hadoop并设置好环境变量
- 第三步: 我们需要进行SSH无密登录的配置,然后对我们的JDKHadoop等文件进行分发
- 第四步:我们要进行集群配置前,需要进行配置文件的修改 ,并对我们的集群进行规划,例如一台服务器运行NameNode服务,一台服务器运行ResourceManager。一台服务器运行SecondaryNameNode
- 第五步:格式化一下hdfs,然后在NameNode服务器上启动HDFS,在ResourceManager上启动yarn
- 第六步:接下来就是进行测试,利用官方的案例和web连接测试一下是否正常,然后再进行一些工作中需要用到的一些服务器配置,例如历史服务器、日志的聚集以及时间同步等
- 好了,整个Hadoop集群成功搭建完,接下来就是对它的组成模块进行挨个讲解,首先是HDFS
HDFS概述
HDFS整体架构
介绍HDFS前,我先放一组图片,便于大家理解
上面的图是星爷鹿鼎记电影中的截图,我觉得它正好能说明HDFS的架构:
- 图中所说的目录对应的就是HDFS中的NameNode,它用于存储文件的元数据,元数据就是记录文件的名称、文件属性(生成时间、大小、文件权限等)、文件目录结构等信息,有了它,我们就可以找到我们需要的数据
- 每一个功法分为上中下部,在DateNode就是在本地文件系统中存储文件的块数据,它会将一个文件进行切割,分别存储在不同的服务器中
- 每过一段时间,就会有大牛对武功秘籍进行修改优化,在HDFS中就是SecondaryNameNode,它每隔一段时间就会对NameNode元数据进行备份
HDFS的定义与优缺点
定义
- 知道了HDFS的基本概念,我就要相对官方的解释一下HDFS是什么了,它是众多分布式文件管理系统中的一种
- 分布式文件管理系统就是指,将多个服务器当作一个整体;HDFS就是这个整体的管理员,当我们需要对数据进行存储时,就会将该文件进行分割成一块一块的数据,分别存储再各个服务器中
- 正因为这个特点,HDFS最大的用途就是适合一次写入,多次读取的场景,经常用于数据分析
优缺点
正因为它是分布式的文件管理系统,它的优缺点就比较明显
- 优点
- 高容错性,它在存储文件时,默认会创建三个副本,用于备份数据,因此其中一个服务器奔溃,并不会导致整个数据的丢失
- 适合处理大数据
- 在数据规模层次,它能够处理数据的规模可以达到TB甚至PB级别
- 在文件规模层级,它能够处理百万规模以上的文件数量
- 因为它是分布式存储,所以对硬件的要求比较低,我们可以构建在廉价的机器上,通过多副本机制提高可靠性
- 缺点
- 因为它有一个目录NameNode,并且数据都是分块存储在不同服务器上,所以它做不到毫秒级数据访问
- 同样的,它存储小文件时,NameNode同样会存储目录和块信息,小文件会占用NameNode的内存,是一种资源浪费
- 在HDFS中有一个规定,文件存储的寻址时间越接近读取时间,性能越好,而小文件的寻址时间会超过读取时间,它违反了HDFS的设计目标
- 不支持并发写入,文件随机修改
- 因为要对文件进行分块,所以不能使用多个线程同时写
- HDFS只支持数据的追加,不支持文件的随机修改
再议HDFS的整体架构
首先看一下几乎介绍HDFS都要发的架构图
优缺点以及整体的架构理解了,下面就要完整的将HDFS各个组件的功能进行说明
NameNode(nn)
NameNode是一个管理者,管理整个HDFS
- NameNode管理HDFS的名称空间
- 配置副本策略
- 管理数据块(Block)映射信息
- 处理客户端读写请求
DataNode(dn)
DataNode是小弟,它主要执行实际的操作
- 存储实际的数据库
- 执行数据块的读/写操作
SecondaryNameNode(2nn)
SecondaryNameNode就是秘书,主要更新元数据(例如目录等信息)
- 辅助NameNode,主要工作是定期合并Fsimage和Edits,并推送给NameNode
- 在紧急情况下,可以辅助恢复NameNode
- 当NameNode挂掉的时候,并不能马上替换NameNode提供服务(因为它是秘书)
Client
它是客户端,是用户,主要是上传下载数据
- 文件切分,文件长传HDFS时,Client会将文件切分成一个一个的Block,然后进行上传
- 与NameNode交互,获取文件的位置信息
- 与DataNode交互,读取或写入数据
- Client提供一些命令来管理HDFS,比如HDFS的增删改查操作
HDFS文件块大小
- HDFS中的文件在服务器上是分块存储数据的(block),块的大小默认是128M,如果该文件大小超过128m,就会分成两块,还是超128,就继续切割
- 块的大小可以通过配置参数来规定,一般我们默认设置就好了
设置块大小的原理
- HDFS中,如果寻址时间约为10ms,即查找到目标数据块的时间为10ms
- 寻址时间为传输时间的1%时,就是最佳状态,因此传输时间=10ms/0.01 = 1s,就是说,传输文件的时间是1s,寻址时间为10ms时,就是HDFS的最佳状态
- 目前的磁盘传输速率普遍是100MS/s
- 所以1s*100MB=100MB
- 上面的这个知识点就是为了说明HDFS块的大小设置取决于磁盘的传输速率
- 当我们的HDFS块设置的太小,那么就会增加寻址的时间,程序会一直在找块的开始位置
- 如果块设置的太大,从磁盘传输数据的时间就会明显大于这个块开始位置的所需时间,这就会导致程序在处理这块数据时变得非常慢
这个不理解没关系,后面接触多了就懂了,我们就需要记住一句话:HDFS块的大小设置主要取决于磁盘的传输速率
HDFS的Shell操作
- 其实在前面我们使用过类似的操作,例如在本地运行模式时,我们使用的
hadoop jar ....
还有我们集群搭建完测试时的 hadoop fs xxx,接下来就正式学习一下HDFS的shell操作 - 基本语法
hadoop fs 具体的命令 或者 hdfs dfs 具体命令
- 我建议使用hadoop fs,因为它里面还 包含了hadoop的一些命令,更加全面,当我们输入 hadoop fs时就会输出所有的具体的命令,我们可以进行查看,这里就不演示了
- 在进行下面常用命令前,一定记得启动了Hadoop集群
- 我所有的操作都是hadoop102服务器哈,并且下面的命令都是在
~
家目录下进行测试 - 另外下面的/input指的是我们HDFS下的/input文件夹
常用命令
上传
-moveFromLocal
从本地剪切文件并粘贴到HDFS中
# 创建一个本地文件
touch wukong.txt
# 将本地文件剪贴到HDFS中
hadoop fs -moveFromLocal ./wukong.txt /input
# 此时源文件在本地系统中就会消失
-copyFromLocal
从本地复制文件并粘贴到HDFS中
# 创建一个本地文件
touch wukong2.txt
# 将本地文件剪贴到HDFS中
hadoop fs -copyFromLocal./wukong.txt /input
# 此时源文件在本地系统中就不会消失
-appendToFile
追加一个文件到已经存在的文件末尾,
# 创建一个本地文件
touch hello.txt
# 编写一个测试数据
hello big data!
# 将本地文件剪贴到HDFS中
hadoop fs -appendToFile./hello.txt /input/wukong2.txt
# 此时我们可以在HDFS中看到wukong2.txt中有了hello.txt的数据
-put
等同于copyFromLocal
# 将本地文件剪贴到HDFS中
hadoop fs -put./hello.txt /input
# 此时/input目录下就会有hello,txt并且本地文件也存在
下载
了解了文件如何上传到HDFS,接下来就是如何从HDFS下载文件,我们先将本地的文件全部删除了
-copyToLocal
从HDFS拷贝到本地
# 将HDFS中的wukong.txt下载到当前位置
hadoop fs -copyToLocal /input/wukong.txt ./
-get
等同于copyToLocal,就是从HDFS下载文件到本地
# 将HDFS中的wukong.txt下载到当前位置
hadoop fs -get /input/wukong2.txt ./
-getmerge
合并下载多个文件,比如HDFS的目录/input下有多个文件hello.txt,wukon2.txt…
# 将HDFS中input中的所有文件中的数据合并到merge_txt.txt并下载到本地
hadoop fs -getmerge /input/* ./merge_txt.txt
HDFS直接操作
直接操作就和我们Linux的命令操作一样,只不过它操作的是HDFS中的文件
-ls
显示目录信息
# 显示HDFS的目录信息
-mkdir
在HDFS上创建目录
# 在HDFS中创建一个文件夹
hadoop fs -mkdir test
-cat
显示文件内容
# 查看HDFS中指定文件的数据
hadoop fs -cat /input/wukong2.txt
-chmod/-chown/-chgrp
Linux文件系统中的用法一样,修改文件所属权限,我就不一 一举例了
# 修改HDFS中wukong.txt的权限
hadoop fs -chmod 666 wukong.txt
-cp
从HDFS的一个路径拷贝到HDFS的另一个路径
# 复制wukong.txt 并起名叫wukon3.txt
hadoop fs -cp wukong.txt wukong3.txt
-mv
在HDFS目录中移动文件
# 移动wukong.txt到HDFS根目录下
hadoop fs -mv wukong.txt /
-tail
显示一个文件的末尾
# 查看wukong2.txt末尾的数据
hadoop fs -tail wukong2.txt
-rm
删除文件或文件夹
# 删除wukong3.txt文件
hadoop fs -rm wukong3.txt
-rmdir
删除空目录
# 删除我们前面创建的HDFS的test目录
hadoop fs -rmdir test
-du
统计文件夹的大小信息
# 查看input文件夹的大小信息
hadoop fs -du -s -h /input
# 不加-s -h指的是显示hdfs对应路径下每个文件夹和文件的大小
# -h指的是是否换算成最大的单位,例如一个文件是100mb,它就会显示100mb
# -s指的是显示hdfs对应路径下所有文件和的大小
-setrep
设置HDFS中文件的副本数量
# 设置wukong.txt的副本数量为10
hadoop fs -setrep 10 /input/wukong.txt
注意:-setrep设置副本数量如果大于当前我们集群中节点数据,默认的副本数量还是我们当前集群数量,当我们增加新的节点后,就会自动创建副本
HDFS的客户端操作
- 前面我们学习了如何在shell中对HDFS进行操作,接下来就是如何通过java代码对HDFS进行操作
- HDFS在客户端的操作就和我们学习JDBC的时候很像,它的流程也是:
- 建立连接
- 具体操作
- 关闭资源
- 通过Java来操作HDFS,我们当然是通过windows的IDEA来操作了,所以,我们的Windows也需要安装Hadoop
操作HDFS前的准备工作
安装Hadoop的方式和Maven一样,我们直接下载windows版本的依赖包,然后解压,设置环境变量即可
第一步:下载windows版本的Hadoop包
大家可以自行度娘下载哈,Hadoop官方没有提供windows版本,需要自己下载源码然后编译,官网里有介绍编译的方法,或者后台私信我哈
第二步:设置环境变量, HADOOP_HOME 然后设置path %HADOOP_HOME%\\bin
第三步:在windows命令行测试
注意:如果上述操作后还有问题可以将bin目录下hadoop.dll
和winutils.exe
放到 C:/windows/system32
目录下
创建项目并初始化HDFS所需配置
第一步: 创建一个Maven工程HDFSDemo,并导入相应的依赖坐标+日志添加,创建Maven工程我就不演示了,创建好之后,直接在pom.xml中添加所需要的依赖
- pom.xml配置
<dependencies> <!-- 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- log4j :日志管理jar包--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.12.0</version> </dependency> <!-- hadoop依赖的jar包--> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.1.3</version> </dependency> </dependencies>
第二步:日志信息配置,在main文件夹下的resources中创建一个log4j2.xml的配置文件,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
<Appenders>
<!-- 类型名为Console,名称为必须属性 -->
<Appender type="Console" name="STDOUT">
<!-- 布局为PatternLayout的方式,
输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
<Layout type="PatternLayout"
pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
</Appender>
</Appenders>
<Loggers>
<!-- 可加性为false -->
<Logger name="test" level="info" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
<!-- root loggerConfig设置 -->
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
第三步: 创建包名: com.company.hdfs
第四步: 创建HDFSDemo类,最终结构如下
第五步:开始正式编写代码操作HDFS前,我们先要了解一下操作步骤:
- 1.获取文件系统
- 2.具体操作
- 3.释放资源
- 记得在下面操作之前,虚拟机中的Hadoop集群要正常运行
HDFS的常用API操作
我在这里直接使用单元测试的方法来对API进行操作,这样方便大家查看,另外,里面的异常我都主动抛出了,大家在编写的时候需要自己捕获一下异常
HDFSDemo的通用编写
public class HDFSDemo {
private FileSystem fs;
/*
在执行单元测试方法前先执行
*/
@Before
public void before() throws Exception {
/*
get(final URI uri, final Configuration conf,
final String user)
uri : NameNode的地址
conf : 配置文件的内容
user : 操作HDFS的用户名
*/
Configuration conf = new Configuration();
//1.获取文件系统对象
fs = FileSystem.get(new URI("hdfs://hadoop102:9820"),
conf, "hadoopuser");
}
/*
在执行单元测试方法后再执行
*/
@After
public void after(){
//3.关闭资源
try {
if (fs != null) {
fs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
... 后面的代码就是我们的API
}
copyFromLocalFile 文件上传
/*
文件上传
*/
@Test
public void test() throws URISyntaxException, IOException, InterruptedException {
/*
copyFromLocalFile(boolean delSrc, boolean overwrite,
Path src, Path dst)
delSrc : 是否删除源文件
overwrite : 如果目标文件已经存在,是否覆盖
如果不覆盖,那么目标文件如果已经存在则报错。
src :源文件路径(本地)
dst :目标文件路径(HDFS)
*/
fs.copyFromLocalFile(true,true,
new Path("D:\\\\io\\\\hdfs\\\\aa.txt"),
new Path("/input2"));
}
copyToLocalFile 文件下载
/*
文件下载
*/
@Test
public void test2() throws IOException {
/*
copyToLocalFile(boolean delSrc, Path src, Path dst,
boolean useRawLocalFileSystem)
delSrc : 是否删除源文件(HDFS)
src : 源文件路径(HDFS)
dst : 目标文件路径(本地)
useRawLocalFileSystem : 是否使用RawLocalFileSystem
*/
fs.copyToLocalFile(false,new Path("/input2/aa.txt"),
new Path("D:\\\\io\\\\hdfs"),false);
}
delete 删除文件或目录
/*
删除文件或目录
*/
@Test
public void test3() throws IOException {
/*
delete(Path f, boolean recursive)
f : 文件或目录的路径
recursive :如果删除的是文件true和false都可以。如果删除的是目录必须是true否则抛异常
注意:如果是空目录true和false也都可以
*/
fs.delete(new Path("/txt/sanguo.txt"),true);
}
rename 修改文件名或移动文件
/*
修改文件名或移动文件
*/
@Test
public void test4() throws IOException {
/*
rename(Path src, Path dst)
src : 源文件路径
dst : 目标文件路径
*/
//改名
// fs.rename(new Path("/txt/xiyouji.txt"),
// new Path("/txt/xiyou.txt"));
//移动文件
fs.rename(new Path("/txt/xiyou.txt"),
new Path("/input"));
}
RemoteIterator 文件详情查看
/*
文件详情查看
*/
@Test
public void test5() throws Exception {
/*
listFiles(final Path f, final boolean recursive)
f : 目标文件路径
recursive : 是否递归
*/
RemoteIterator<LocatedFileStatus> iterator
= fs.listFiles(new Path("/"), true);
//遍历
while(iterator.hasNext()){
LocatedFileStatus fileStatus = iterator.next();
//获取文件的名字
System.out.println("==============文件的名字:"
+ fileStatus.getPath().getName() + "===================");
System.out.println("副本数量:" + fileStatus.getReplication());
//获取块信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
System.out.println(Arrays.toString(blockLocations));
}
}
FileStatus 判断文件或目录
/*
判断文件或目录
*/
@Test
public void test6() throws IOException {
FileStatus[] status = fs.listStatus(new Path("/input"));
for (FileStatus fileStatus : status) {
if (fileStatus.isDirectory()){
System.out.println(fileStatus.getPath().getName() + "是一个目录");
}else if(fileStatus.isFile()){
System.out.println(fileStatus.getPath().getName() + "是一个文件");
}
}
}
FSDataOutputStream/FSDataInputStream 通过流的形式实现上传和下载
/*
通过流的形式实现上传和下载
上传
*/
@Test
public void test7() throws Exception {
//读取 --- 本地读(文件输入流)
FileInputStream fis = new FileInputStream("D:\\\\io\\\\hdfs\\\\long.txt");
//写 --- 向HDFS写数据的流
FSDataOutputStream fos = fs.create(new Path("/input2/long.txt"));
//一边读一边写
/*
copyBytes(InputStream in, OutputStream out,
int buffSize, boolean close)
in : 输入流
out : 输出流
buffSize : 缓冲区大小
close : 是否关闭流
*/
IOUtils.copyBytes(fis,fos,1024,false);
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
}
/*
下载
*/
@Test
public void test8() throws IOException {
//读 --- 从HDFS上读
FSDataInputStream fis = fs.open(new Path("/input2/long.txt"));
//写 --- 向本地写(文件输出流)
FileOutputStream fos = new FileOutputStream("D:\\\\io\\\\hdfs\\\\long.txt");
//文件对拷(一边读一边写)
IOUtils.copyBytes(fis,fos,1024,true);
}
在IDEA中设置HDFS的用户名
- 在前面我们的通用配置时,我们自己提供了user这个值,当我们的Maven工程很多的时候,每创建一次工程都需要提供一次user,我们可以通过IDEA的设置来传递,这样可以防止因用户名问题导致一些错误
- 前面我们是通过测试方法进行测试,这次我们使用Main方法进行测试
package com.company.hdfs; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import java.io.IOException; /* 创建FileSystem对象 */ public class HDFSDemo2 { /* 在 EditConfigurations中 VM Options : -DHADOOP_USER_NAME=atguigu 注意:如果找不到当前类请先运行一次。 */ public static void main(String[] args) throws IOException { System.out.println("hello"); //用来设置配置内容 Configuration conf = new Configuration(); //配置NameNode的地址 conf.set("fs.defaultFS","hdfs://hadoop102:9820"); //创建文件系统对象 FileSystem fs = FileSystem.get(conf); //上传操作 fs.copyFromLocalFile(false,true, new Path("F:\\\\IO\\\\hdfs\\\\aa.txt"), new Path("/input")); //关闭资源 fs.close(); } }
- 在我们创建文件对象时,没有传递user用户,它就会报下面这个错误
Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: user=m, access=WRITE, inode="/input":atguigu:supergroup:drwxr-xr-x
- 解决方法:
在 EditConfigurations中 VM Options : -DHADOOP_USER_NAME=atguigu 注意:如果找不到当前类请先运行一次。
在客户端中,配置文件的优先级
建立一个HadooopDemo3,然后在hadoop102中复制一份hdfs-site.xml配置文件到Main的resources中,整体结构如下
package com.company.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
/*
配置参数:
方式1 : xxx-default.xml (服务器 --- 默认配置文件)
方式2 : xxx-site.xml(服务器)
方式3 : 在代码中配置(客户端)
方式4 : 在工程中的配置文件(客户端)
优先级 : 在代码中配置(客户端) > 在工程中的配置文件(客户端) > xxx-site.xml(服务器)
> xxx-default.xml (服务器 --- 默认配置文件)
*/
public class HDFSDemo3 {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://hadoop102:9820");
//配置副本的数量
conf.set以上是关于打怪升级之小白的大数据之旅(四十五)<认识HDFS与常用操作>的主要内容,如果未能解决你的问题,请参考以下文章
打怪升级之小白的大数据之旅(四十八)<初识MapReduce>
打怪升级之小白的大数据之旅(四十九)<MapReduce框架原理一:MapReduce工作流程&InputFormat>