写在前面
我自己一直比较喜欢大数据这个方向的工作,虽然自己也在大数据相关的岗位上工作了小两年,但一直没有系统的学习过大数据相关的知识。
说到自身经历,可能和大部分程序员一样,一直混迹于小公司,我从第一家公司离职的理由就是拖欠工资,那个时候又是刚毕业,手里没有一点积蓄,后来实在没办法只能离职求活路。不过很感谢第一家公司,在那是我进步最快的地方,我从事大数据岗位也是那家公司花钱让我去学习的。除了是小公司,其他的都很好,技术氛围很棒!第二家公司虽然比较大,但是属于项目外包型公司(虽然我所在的部门属于自研型,但还是外包的干法,我依旧记得我入职之后才发现公司代码没有版本管理,所有代码都在主分支上,没有一份技术文档,全靠口口相传)。
今年疫情原因,各个行业裁员的裁员,降薪的降薪,我在的公司也不例外,全员降薪30%,然而周末也没了,疯狂赶项目进度,我最多的时候同时支持了五个项目组的报表开发,每天做梦都梦见自己在催着改报表。
更恐怖的是开发这个行业,尤其是有外包性质,门槛极低,低到令人发指,我在公司下面带了三个那种培训机构出来的,都是学了几个月转身包装简历就出来面试了,那的工资低还比你能加班!
后来实在不想每天这样疲于拼命的机械式生活,想着多花时间去学习,然后跳槽去好一点的公司!然而现实告诉你,我最缺的就是时间!直到拉勾推出的大数据高薪训练营。关于技术类的学习,我有点排斥找培训机构,觉得自己可以通过自学,在网上翻阅资料啊,找视频之类的方式学,因为相信自己的学习能力。但是大家也都知道,网上找的这些东西都能学会,也可以看得懂。但是当自己学多了就感觉自己学的很杂,一点都没有系统性的学。而且相对于网上找的免费的视频,内容很没有深度,TB上买的视频有些章节的确实还行,但是感觉是那种很久之前的东西拿出来不停的卖,而且关于技术方面的东西,肯定是会有不明白的地方,当然遇到这些肯定第一时间是自己去网络上找找解决方案,或者去了解为什么。但是很多东西确实在网络上找不到,久而久之,就积累了各种问题。
之前也看过很多培训机构,像什么八斗学院,马士兵教育,金讯教育等等,经常发一些二学员就业的截图,薪资都特别高,但其实在这一行的都知道,大数据这块薪资虽然高,但也没有高特别多,只是门槛高,说实话,很讨厌这种虚假营销。
选择拉勾的原因
1. 朋友推荐
刚开始知道拉勾除了Java高薪训练营,但是并不是很感兴趣,后来朋友报名了Java高薪训练营,反馈很好,恰逢拉勾推出了大数据高薪训练营,就想着说同一家公司的课程,质量应该差不多,就抱着试一试的想法找拉勾的老师聊了聊,听完介绍感觉还可以,就报名了。
2. 拉勾本身
拉勾本身就是做招聘的,对企业需求这一块更加了解企业需要什么样能力的人,那么他们的课程在这块针对性肯定比其他机构好
结果
1. 个人收获
算算从开班到现在,已经五个月了,课程已经学了一半,不是我学的很慢,而是课程质量真的很高,每一个模块单独拿出来都可以是一门课了,特别是一块内容学完就有一个实际项目等着你去做,而不是demo!现在养成了学习的习惯之后完全停不下来了!在这里,我已经经历了完整的离线数仓开发,包括服务器选型,技术选型等等,在需求这块,老师带着我们一个一个需求的讲解,对于复杂的需求和实现,老师会先给出一个与之相关但是比较基础的需求或者实现,让我们一步一步的学习。相比学之前的我,现在的我可以有底气的说具体某个组件怎么用,为什么这么用,同类产品有哪些,为什么选择这个而不是其他的,甚至部分原理源码都可以说出来,每一个模块的最后老师都会带着我们深入底层原理去了解具体某一个功能的实现,也会带着我们去剖析源码,看看源码是怎样写的!
2. 课程内容
课程说的再多也不如自己去看,我给几张知识思维导图,看过就知道干货有多少了!
教学服务
- 班主任每天催学习,提醒交作业,安排直播课,带动群里学习气氛,服务很周到
- 导师解答超级耐心,不会因为你问的问题有多简单或者稍微百度以下就能明白,还会根据你问的问题,举一反三,给你讲一些相关的知识
- 关于作业,每一个知识点结束,都会有小作业,每一个模块结束都会有一个大作业,每一份作业导师都会批改,只有批改通过了才能学习接下来的内容
内推服务
群里已经有好几波内推了,这个月还和京东合作,成立专属内推,解决就业问题。而且投简历之前,可以去找导师修改简历,让你的简历过关的机会更高,也会对你进行面试前的指导,简直贴心的不要不要!
个人的两句话
自学固然可以,但是时间成本很高,而且知识并不系统,是时间成本更高还是培训费成本更高,还有你一个人能否坚持学习,不受外界诱惑?
对于拉勾,我简而言之,丰富而完善的知识体系,行业大咖的直播解答,独家的内推资源,导师班主任全程伴读,在你偷懒的时候甩出小鞭子,定期测评,完善的作业机制,良好的学习环境
下面正文开始
一、HDFS概述
1. HDFS产出背景及定义
- HDFS产生背景
随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。
-
HDFS定义
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色,HDFS是分布式存储服务。
HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。
2. HDFS优缺点
-
优点:
-
高容错性
数据自动保存多个副本。它通过增加副本的形式,提高容错性。
某一个副本丢失以后,它可以自动恢复。
-
适合处理大数据
- 数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据;
- 文件规模:能够处理百万规模以上的文件数量,数量相当之大。
-
可构建在廉价机器上,通过多副本机制,提高可靠性。
-
-
缺点:
不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
-
无法高效的对大量小文件进行存储。
- 存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息。这样是不可取的,因为NameNode的内存总是有限的;
- 小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。
-
不支持并发写入、文件随机修改。
一个文件只能有一个写,不允许多个线程同时写;
仅支持数据append(追加),不支持文件的随机修改。
3. HDFS的重要概念
HDFS 通过统一的命名空间目录树来定位文件; 另外,它是分布式的,由很多服务器器联合起来实现其功能,集群中的服务器器有各自的角色(分布式本质是拆分,各司其职)
-
典型的 Master/Slave 架构
- HDFS 的架构是典型的 Master/Slave 结构
- HDFS 集群往往是一个 NameNode+多个DataNode 组成(HA架构会有两个NameNode,联邦机制)
- NameNode 是集群的主节点,DataNode 是集群的从节点
-
分块存储(block机制)
- HDFS 中的文件在物理上是 分块存储(block)的,块的大小可以通过配置参数来规定
- Hadoop2.x 版本中默认的block大小是 128M,老版本默认是64M
- 切分规则:如果有两个文件,分别是 200M 和 20M,那么第一个文件先分成两个切片(128M + 72M),第二个文件分成一个切片(20M),共三个切片
-
命名空间(NameSpace)
-
HDFS
支持传统的层次型文件组织结构,用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件
-
Namenode
负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被 Namenode 记录下来
-
抽象目录树
HDFS 提供给客户单一的一个抽象目录树,用户不知道这些文件是存储在哪个dataNode上
访问形式:
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 信息(<font color=red>这就是高容错的体现,很重要</font>)
-
副本机制
为了容错,文件的所有 block 都会有副本,默认是 3 个(包括本来的block,一共 3 个)。每个文件的 block大小 和 副本系数 都是可配置的
应用程序可以指定某个文件的副本数目,副本系数可以在文件创建的时候指定,也可以在之后改变
如果副本数量设置为 10,但设备数只有 3台,那么不会真正创建 10 个副本,而是创建相当于设备数量的副本,即 3个副本。等设备数增加到 10 台,创建的副本数才达到 10 台(<font color=red>很重要</font>)
-
一次写入,多次读出
- HDFS 是设计成适应 一次写入,多次读出 的场景,且不支持文件的随机修改(支持追加写入,不支持随机更新)
- 因此,HDFS 适合用来做 大数据分析的底层存储服务,并不适合用来做网盘等应用(修改不方便,延迟大,网络开销大,成本太高)
4. HDFS架构
- NameNode: HDFS集群的管理者,Master
- 维护管理Hdfs的名称空间(NameSpace)
- 维护副本策略
- 记录⽂件块(Block)的映射信息
- 负责处理客户端读写请求
-
DataNode: NameNode下达命令,DataNode执行实际操作,Slave节点。
保存实际的数据块
负责数据块的读写
-
Client: 客户端
- 上传⽂文件到HDFS的时候,Client负责将⽂件切分成Block,然后进行上传
- 请求NameNode交互,获取文件的位置信息
- 读取或写⼊文件,与DataNode交互
- Client可以使⽤一些命令来管理HDFS或者访问HDFS
二、 HDFS 客户端操作
1. Shell命令行操作HDFS
-
基本语法
- bin/hadoop fs 具体命令
- bin/hdfs dfs 具体命令(<font color=red>推荐</font>)
-
命令⼤全
[root@linux121 hadoop-2.9.2]# bin/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
-
HDFS命令演示
-
首先启动Hadoop集群(HDFS和YARN要做各自的节点上启动)
[root@linux121 hadoop-2.9.2]$ sbin/start-dfs.sh [root@linux122 hadoop-2.9.2]$ sbin/start-yarn.sh
-
-help:输出这个命令参数
[root@linux121 hadoop-2.9.2]$ hdfs dfs -help rm
-
-ls: 显示⽬录信息
[root@linux121 hadoop-2.9.2]$ hdfs dfs -ls /
-
-mkdir: 在HDFS上创建目录
[root@linux121 hadoop-2.9.2]$ hdfs dfs -mkdir -p /lagou/bigdata
-
-moveFromLocal: 从本地剪切粘贴到HDFS
[root@linux121 hadoop-2.9.2]$ touch hadoop.txt [root@linux121 hadoop-2.9.2]$ hdfs dfs -moveFromLocal ./hadoop.txt /lagou/bigdata
-
-appendToFile: 追加⼀个文件到已经存在的⽂件末尾
[root@linux121 hadoop-2.9.2]$ touch hdfs.txt [root@linux121 hadoop-2.9.2]$ vi hdfs.txt 输入 namenode datanode block replication [root@linux121 hadoop-2.9.2]$ hdfs dfs -appendToFile hdfs.txt /lagou/bigdata/hadoop.txt
-
-cat: 显示文件内容
[root@linux121 hadoop-2.9.2]$ hdfs dfs -cat /lagou/bigdata/hadoop.txt
-
-chgrp 、-chmod、-chown: Linux⽂件系统中的⽤法一样,修改文件所属权限
[root@linux121 hadoop-2.9.2]$ hdfs dfs -chmod 666 /lagou/bigdata/hadoop.txt [root@linux121 hadoop-2.9.2]$ hdfs dfs -chown root:root /lagou/bigdata/hadoop.txt
-
-copyFromLocal: 从本地⽂件系统中拷⻉文件到HDFS路径去
[root@linux121 hadoop-2.9.2]$ hdfs dfs -copyFromLocal README.txt /
-
-copyToLocal: 从HDFS拷贝到本地
[root@linux121 hadoop-2.9.2]$ hdfs dfs -copyToLocal /lagou/bigdata/hadoop.txt ./
-
-cp : 从HDFS的⼀个路径拷⻉到HDFS的另一个路径
[root@linux121 hadoop-2.9.2]$ hdfs dfs -cp /lagou/bigdata/hadoop.txt /hdfs.txt
-
-mv: 在HDFS⽬录中移动文件
[root@linux121 hadoop-2.9.2]$ hdfs dfs -mv /hdfs.txt /lagou/bigdata/
-
-get: 等同于copyToLocal,就是从HDFS下载文件到本地
[root@linux121 hadoop-2.9.2]$ hdfs dfs -get /lagou/bigdata/hadoop.txt ./
-
-put: 等同于copyFromLocal
[root@linux121 hadoop-2.9.2]$ hdfs dfs -mkdir -p /user/root/test/ #本地⽂文件系统创建yarn.txt [root@linux121 hadoop-2.9.2]$ vim yarn.txt resourcemanager nodemanager [root@linux121 hadoop-2.9.2]$ hdfs dfs -put ./yarn.txt /user/root/test/
-
-tail: 显示⼀个⽂件的末尾
[root@linux121 hadoop-2.9.2]$ hdfs dfs -tail /user/root/test/yarn.txt
-
-rm: 删除⽂件或⽂件夹
[root@linux121 hadoop-2.9.2]$ hdfs dfs -rm /user/root/test/yarn.txt
-
-rmdir: 删除空⽬录
[root@linux121 hadoop-2.9.2]$ hdfs dfs -mkdir /test [root@linux121 hadoop-2.9.2]$ hdfs dfs -rmdir /test
-
-du: 统计⽂件夹的⼤小信息
[root@linux121 hadoop-2.9.2]$ hdfs dfs -du -s -h /user/root/test [root@linux121 hadoop-2.9.2]$ hdfs dfs -du -h /user/root/test
-
-setrep: 设置HDFS中⽂件的副本数量
[root@linux121 hadoop-2.9.2]$ hdfs dfs -setrep 10 /lagou/bigdata/hadoop.txt
-
2. JAVA客户端
2.1 客户端环境准备
-
将Hadoop-2.9.2安装包解压到非中⽂路径(例如:E:\hadoop-2.9.2)。
-
配置HADOOP_HOME环境变量
-
配置Path环境变量。
创建⼀个Maven工程ClientDemo
-
导入相应的依赖坐标+日志配置文件
-
需要导入三个模块:hadoop-common、hadoopclient、hadoop-hdfs
<dependencies> <!-- 导入三个模块:hadoop-common、hadoopclient、hadoop-hdfs --> <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopclient --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</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.lagou.hdfs
-
创建HdfsClient类
public class HdfsClient{ @Test public void testMkdirs() throws IOException, InterruptedException, URISyntaxException { // 1 获取⽂件系统 Configuration configuration = new Configuration(); // 配置在集群上运行 // configuration.set("fs.defaultFS", "hdfs://linux121:9000"); // FileSystem fs = FileSystem.get(configuration); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 创建目录 fs.mkdirs(new Path("/test")); // 3 关闭资源 fs.close(); } }
会遇到的问题:
-
如果不指定操作 HDFS 集群的用户信息,默认是 获取当前操作系统的用户信息,出现权限被拒绝的问题,报错如下:
出现问题的原因:
由于 HDFS 的权限管理机制是:用户告诉 hdfs 自己是什么用户,hdfs 都会相信,认为用户不会做坏事。HDFS⽂件权限的目的,防⽌好⼈做错事,⽽不是阻⽌坏人做坏事。HDFS相信你告诉我你是谁,你就是谁。因此这种权限管理比较鸡肋
解决办法:
-
把 根目录 的权限设置为 777 ,关于文件权限管理 交给其他软件去做
hdfs dfs -R 777 /
-
在配置文件 hdfs-site.xml 中添加以下属性,以关闭 HDFS 集群权限校验,修改完成之后要分发到其它节点,同时要重启HDFS集群
vim hdfs-site.xml #添加如下属性 <property> <name>dfs.permissions.enabled</name> <value>false</value> </property>
-
指定用户信息获取 FileSystem 对象
FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root")
-
windows 解压安装 Hadoop后,在调用相关API操作 HDFS 集群时可能会报错,如下图:
出现问题的原因:
这是由于 Hadoop 安装缺少 windows 操作系统相关文件所致
解决办法:
将 winutils.exe 拷贝到 windows 系统 Hadoop 安装目录的 bin 目录下即可
-
IDEA 启动 Hadoop 的任务时,出现如下报错:
解决办法:
将 Hadoop.dll 文件 添加到 C:\Windows\System32 中
参数的优先级
当在配置文件,java 代码中,包括hdfs服务器的默认配置,都设置副本数量时,参数的优先级是:
<font color=red>代码中设置的值 -->用户自定义配置文件 -->服务器的默认配置</font>
2.2 HDFS的API操作
-
上传文件
-
可通过
configuration.set("dfs.replication", "2")
设置副本数量@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://linux121:9000"), configuration, "root"); // 2上传⽂文件 fs.copyFromLocalFile(new Path("e:/lagou.txt"), new Path("/lagou.txt")); // 3 关闭资源 fs.close(); }
-
也可通过 配置文件
hdfs-site.xml
来指定,文件放在 resources 目录下<font color=red>注:配置文件名 不能写错!!</font>
<?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>
-
-
下载文件
@Test public void testCopyToLocalFile() throws IOException, InterruptedException, URISyntaxException{ // 1 获取⽂文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 执⾏行行下载操作 // boolean delSrc 指是否将原⽂文件删除 // Path src 指要下载的⽂文件路路径 // Path dst 指将⽂文件下载到的路路径 // boolean useRawLocalFileSystem 是否开启⽂文件校验 fs.copyToLocalFile(false, new Path("/lagou.txt"), newPath("e:/lagou_copy.txt"), true); // 3 关闭资源 fs.close(); }
-
删除文件/文件夹
@Test public void testDelete() throws IOException, InterruptedException, URISyntaxException{ // 1 获取⽂文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 执⾏行行删除 fs.delete(new Path("/api_test/"), true); // 3 关闭资源 fs.close(); }
-
HDFS文件名更改
@Test public void testRename() throws IOException, InterruptedException, URISyntaxException{ // 1 获取文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 修改文件名称 fs.rename(new Path("/banzhang.txt"), new Path("/banhua.txt")); // 3 关闭资源 fs.close(); }
-
查看 文件名称、权限、长度、块信息
@Test public void testListFiles() throws IOException, InterruptedException, URISyntaxException{ // 1获取⽂文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 获取⽂文件详情 RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true); while(listFiles.hasNext()){ LocatedFileStatus status = listFiles.next(); // 输出详情 // ⽂文件名称 System.out.println(status.getPath().getName()); // ⻓长度 System.out.println(status.getLen()); // 权限 System.out.println(status.getPermission()); // 分组 System.out.println(status.getGroup()); // 获取存储的块信息 BlockLocation[] blockLocations = status.getBlockLocations(); for (BlockLocation blockLocation : blockLocations) { // 获取块存储的主机节点 String[] hosts = blockLocation.getHosts(); for (String host : hosts) { System.out.println(host); } } System.out.println("-----------华丽的分割线----------"); } // 3 关闭资源 fs.close(); }
-
区分文件和文件夹
@Test public void testListStatus() throws IOException, InterruptedException, URISyntaxException{ // 1 获取⽂文件配置信息 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 判断是⽂文件还是⽂文件夹 FileStatus[] listStatus = fs.listStatus(new Path("/")); for (FileStatus fileStatus : listStatus) { // 如果是⽂文件 if (fileStatus.isFile()){ System.out.println("f:"+fileStatus.getPath().getName()); } else{ System.out.println("d:"+fileStatus.getPath().getName()); } } // 3 关闭资源 fs.close(); }
-
文件上传 create
将本地文件上传到 hdfs 上
@Test public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException { // 1 获取文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 创建输⼊流 FileInputStream fis = new FileInputStream(new File("e:/lagou.txt")); // 3 获取输出流 FSDataOutputStream fos = fs.create(new Path("/lagou_io.txt")); // 4 流的拷贝 IOUtils.copyBytes(fis, fos, configuration); // 5 关闭资源(这里其实不需要关闭) IOUtils.closeStream(fos); IOUtils.closeStream(fis); fs.close(); }
-
文件下载 open
将 hdfs 文件下载到本地
@Test public void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException{ // 1 获取⽂文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 获取输⼊入流 FSDataInputStream fis = fs.open(new Path("/lagou_io.txt")); // 3 获取输出流 FileOutputStream fos = new FileOutputStream(new File("e:/lagou_io_copy.txt")); // 4 流的对拷 IOUtils.copyBytes(fis, fos, configuration); // 5 关闭资源 IOUtils.closeStream(fos); IOUtils.closeStream(fis); fs.close(); }
-
定位读取 seek
将 HDFS上 的 lagou.txt 的内容在控制台输出两次
@Test public void readFileSeek2() throws IOException, InterruptedException, URISyntaxException{ // 1 获取⽂文件系统 Configuration configuration = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://linux121:9000"), configuration, "root"); // 2 打开输⼊入流,读取数据输出到控制台 FSDataInputStream in = null; try{ in = fs.open(new Path("/lagou.txt")); IOUtils.copyBytes(in, System.out, 4096, false); //从头再次读取,偏移量为0 表示从头读起 in.seek(0); IOUtils.copyBytes(in, System.out, 4096, false); }finally { IOUtils.closeStream(in); fs.close(); } }
三、 HDFS读写解析
1. HDFS读数据流程
HDFS读数据流程:
- 客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
- 挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
- DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet(64KB)为单位来做校验)。
- 客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。
2.HDFS写数据流程
HDFS写数据流程:
- 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
- NameNode返回是否可以上传。
- 客户端请求第一个 Block上传到哪几个DataNode服务器上。
- NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
- 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
- dn1、dn2、dn3逐级应答客户端。
- 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet(64KB)为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
- 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。
验证Packet代码:
@Test public void testUploadPacket() throws IOException { //1 准备读取本地文件的输入流 final FileInputStream in = new FileInputStream(new File("e:/lagou.txt")); //2 准备好写出数据到hdfs的输出流 final FSDataOutputStream out = fs.create(new Path("/lagou.txt"), new Progressable() { public void progress() { //这个progress⽅法就是每传输64KB(packet)就会执行一次 System.out.println("&"); } }); //3 实现流拷贝 IOUtils.copyBytes(in, out, configuration); //默认关闭流选项是true,所以会⾃动关闭 //4 关流 可以再次关闭也可以不关了 }
四、 NameNode和SecondaryNameNode
1. NN和2NN工作机制
问题引出: NameNode如何管理和存储元数据?
计算机中存储数据的两种方式:磁盘、内存
- 元数据存储磁盘:存储磁盘⽆法⾯对客户端对元数据信息的随机访问,还有响应客户请求,必然是效率过低。但是安全性⾼
- 元数据存储内存:元数据存放内存,可以高效的查询以及快速响应客户端的查询请求,数据保存在内存,如果断点,内存中的数据全部丢失。安全性低
解决办法: 内存+磁盘;NameNode内存+FsImage的⽂件(磁盘)
新问题: 磁盘和内存中元数据如何划分?
两个数据一模⼀样,还是两个数据合并到一起才是⼀份完整的数据呢?
- 如果两份数据一模一样的话,客户端 Client 如果对元数据进行增删改操作,则需要时刻保证两份数据的一致性,导致效率变低
- 如果两份数据合并后 ==> 完整数据的情况。NameNode 引入了 edits 文件(日志文件,只能追加写入),记录了client 的增删改操作,而不再让 NameNode 把数据 dump 出来形成 fsimage文件(让 NameNode 专注于处理客户端的请求)
- edits文件:文件生成快,恢复慢;fsimage文件:文件生成慢,恢复快
新问题: 谁来负责文件合并?
如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,但是谁来合并?
- NameNode:NameNode本身任务重,再负责合并,势必效率过低,甚至会影响本身的任务
- 因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
流程分析:
-
第一阶段:NameNode启动
- 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
- 客户端对元数据进行增删改的请求。
- NameNode记录操作日志,更新滚动日志(所谓滚动日志,即 把前一阶段的日志保存成一个日志文件,再新生成一个文件,新生成文件后缀带有 inprogress 字样)。
- 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。
<font color=red>NN和2NN工作机制详解:</font>
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中。
2. Fsimage和Edits解析
-
NameNode在执行格式化之后,会在/opt/lagou/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等
NameNode启动时会将Fsimage⽂件加载到内存中,同时也把之前未合并元数据的Edits⽂件加载,集合两个文件中的元数据这样保证了NameNode中的元数据是最新最全的。通俗点说就是NameNode启动时把Fsimage和Edits⽂件进⾏了合并。
2.1 Fsimage⽂件内容
官方地址:
https://hadoop.apache.org/docs/r2.9.2/hadoop-project-dist/hadoop-hdfs/HdfsImageViewer.html
-
查看oiv和oev命令
[[root@linux121 current]$ hdfs oiv apply the offline fsimage viewer to an fsimage oev apply the offline edits viewer to an edits file
-
基本语法
hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
-
案例实操
[root@linux121 current]$ cd /opt/lagou/servers/hadoop2.9.2/data/tmp/dfs/name/current [root@linux121 current]$ hdfs oiv -p XML -i fsimage_0000000000000000265 -o /opt/lagou/servers/fsimage.xml [root@linux121 current]$ cat /opt/lagou/servers/fsimage.xml
-
查看文件
<?xml version="1.0"?> <fsimage> <version> <layoutVersion>-63</layoutVersion> <onDiskVersion>1</onDiskVersion> <oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision> </version> <NameSection> <namespaceId>722925838</namespaceId> <genstampV1>1000</genstampV1> <genstampV2>1049</genstampV2> <genstampV1Limit>0</genstampV1Limit> <lastAllocatedBlockId>1073741873</lastAllocatedBlockId> <txid>621</txid> </NameSection> <INodeSection> <lastInodeId>16487</lastInodeId> <numInodes>36</numInodes> <inode> <id>16385</id> <type>DIRECTORY</type> <name></name> <mtime>1604115316122</mtime> <permission>root:supergroup:0777</permission> <nsquota>9223372036854775807</nsquota> <dsquota>-1</dsquota> </inode> <inode> <id>16389</id> <type>DIRECTORY</type> <name>wcinput</name> <mtime>1604023034981</mtime> <permission>root:supergroup:0777</permission> <nsquota>-1</nsquota> <dsquota>-1</dsquota> </inode> </INodeSection> </fsimage>
问题: Fsimage 中为什么没有记录块所对应 DataNode ?
答案:在集群启动后,NameNode 要求 DataNode 上报数据块信息,并间隔一段时间后再次上报
2.2 Edits文件内容
-
基本语法
hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
-
案例实操
[root@linux121 current]$ hdfs oev -p XML -i edits_0000000000000000266-0000000000000000267 -o /opt/lagou/servers/hadoop-2.9.2/edits.xml [root@linux121 current]$ cat /opt/lagou/servers/hadoop-2.9.2/edits.xml
-
查看文件
<?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> </EDITS> ...
备注:Edits中只记录了更新相关的操作,查询或者下载文件并不会记录在内!!
问题: NameNode启动时如何确定加载哪些Edits⽂件呢?
答案: 需要借助fsimage⽂件最后数字编码,来确定哪些edits之前是没有合并到fsimage中,启动时只需要加载那些未合并的edits⽂件即可。
3. checkpoint周期
-
通常情况下,SecondaryNameNode每隔一小时执行一次。
官方默认配置文件:
hdfs-default.xml
<property> <name>dfs.namenode.checkpoint.period</name> <value>3600</value> </property>
-
一分钟检查一次操作次数,3当操作次数达到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 >
4. NameNode故障处理
NameNode故障后,HDFS集群就无法正常工作,因为HDFS文件系统的元数据需要由NameNode来管理维护并与Client交互,如果元数据出现损坏和丢失同样会导致NameNode⽆法正常⼯作进⽽HDFS⽂件系统⽆法正常对外提供服务。
如果元数据出现丢失损坏如何恢复呢?
搭建HDFS的HA(⾼可用)集群,解决NN的单点故障问题!!(借助Zookeeper实现HA,一个 Active的NameNode,一个是Standby的NameNode)
-
将SecondaryNameNode中数据拷贝到NameNode存储数据的目录
kill -9 NameNode进程
-
删除NameNode存储的数据(/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name)
[root@linux121 hadoop-2.9.2]rm -rf /opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name/*
-
拷贝SecondaryNameNode中数据到原NameNode存储数据目录
[root@linux121 hadoop-2.9.2]$ scp -r atguigu@hadoop104:/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/namesecondary/* ./name/
重新启动NameNode
[root@linux121 hadoop-2.9.2]$ sbin/hadoop-daemon.sh start namenode
-
使用-importCheckpoint选项启动NameNode守护进程,从而将SecondaryNameNode中数据拷贝到NameNode目录中。
-
修改hdfs-site.xml中的
<property> <name>dfs.namenode.checkpoint.period</name> <value>120</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name</value> </property>
kill -9 NameNode进程
-
删除NameNode存储的数据(/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name)
[root@linux121 hadoop-2.9.2]rm -rf /opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name/*
-
如果SecondaryNameNode不和NameNode在一个主机节点上,需要将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录,并删除in_use.lock文件
[root@linux123 dfs]$ scp -r root@linux121:/opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/namesecondary ./ [root@linux123 namesecondary]$ rm -rf in_use.lock [root@linux123 dfs]$ pwd /opt/lagou/servers/hadoop-2.9.2//data/tmp/dfs [root@linux123 dfs]$ ls data name namesecondary
-
导入检查点数据(等待一会ctrl+c结束掉)
[root@linux121 hadoop-2.9.2]$ bin/hdfs namenode -importCheckpoint
-
启动NameNode
[root@linux121 hadoop-2.9.2]$ sbin/hadoop-daemon.sh start namenode
-
五、Hadoop的限额与归档以及集群安全模式
1. HDFS⽂件限额配置
HDFS文件的限额配置允许我们以⽂件⼤小或者文件个数来限制我们在某个目录下上传的文件数量或者文件内容总量,以便达到我们类似百度网盘等限制每个⽤户允许上传的最大的⽂件的量
-
数量限额
#创建hdfs⽂件夹 hdfs dfs -mkdir -p /user/root/lagou # 给该⽂件夹下⾯设置最多上传两个文件,上传⽂件,发现只能上传一个文件 hdfs dfsadmin -setQuota 2 /user/root/lagou # 清除文件数量限制 hdfs dfsadmin -clrQuota /user/root/lagou
-
空间⼤小限额
# 限制空间⼤小4KB hdfs dfsadmin -setSpaceQuota 4k /user/root/lagou #上传超过4Kb的⽂件⼤小上去提示文件超过限额 hdfs dfs -put /export/softwares/xxx.tar.gz /user/root/lagou #清除空间限额 hdfs dfsadmin -clrSpaceQuota /user/root/lagou #查看hdfs⽂件限额数量 hdfs dfs -count -q -h /user/root/lagou
2. HDFS的安全模式
- 安全模式是HDFS所处的一种特殊状态,在这种状态下,<font color=red>文件系统只接受读数据请求,而不接受删除、修改等变更请求</font>。在NameNode主节点启动时,HDFS⾸先进入安全模式,DataNode在启动的时候会向NameNode汇报可用的block等状态,当整个系统达到安全标准时,HDFS自动离开安全模式。如果HDFS出于安全模式下,则文件block不能进行任何的副本复制操作,因此达到最⼩的副本数量要求是基于DataNode启动时的状态来判定的,启动时不会再做任何复制(从⽽达到最⼩副本数量要求),HDFS集群刚启动的时候,默认30S钟的时间是出于安全期的,只有过了30S之后,集群脱离了了安全期,然后才可以对集群进行操作。
- 相关命令:
hdfs dfsadmin -safemode
3. Hadoop归档技术
-
主要解决HDFS集群存在⼤量⼩文件的问题!!
由于⼤量⼩文件会占⽤NameNode的内存,因此对于HDFS来说存储⼤量⼩文件造成NameNode内存资源的浪费!
-
Hadoop存档⽂件HAR文件,是⼀个更高效的文件存档工具,HAR⽂件是由⼀组文件通过archive⼯具创建⽽来,在减少了NameNode的内存使⽤的同时,可以对文件进行透明的访问,通俗来说就是HAR⽂件对NameNode来说是⼀个⽂件减少了内存的浪费,对于实际操作处理文件依然是一个⼀个独立的文件。
-
案例
-
启动YARN集群
[root@linux121 hadoop-2.9.2]$ start-yarn.sh
-
归档文件
把/user/lagou/input⽬录⾥面的所有⽂件归档成⼀个叫input.har的归档⽂件,并把归档后文件存储到/user/lagou/output路径下。
[root@linux121 hadoop-2.9.2]$ bin/hadoop archive -archiveName input.har –p /user/root/input /user/root/output
-
查看归档
[root@linux121 hadoop-2.9.2]$ hadoop fs -lsr /user/root/output/input.har [root@linux121 hadoop-2.9.2]$ hadoop fs -lsr har:///user/root/output/input.har
-
解归档⽂件
[root@linux121 hadoop-2.9.2]$ hadoop fs -cp har:///user/root/output/input.har/* /user/root
-
六、DataNode
1. DataNode工作机制
DataNode工作机制:
- 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
- DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
- 心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
- 集群运行中可以安全加入和退出一些机器。
2. 数据完整性
-
思考:
如果电脑磁盘里面存储的数据是控制高铁信号灯的红灯信号(1)和绿灯信号(0),但是存储该数据的磁盘坏了,一直显示是绿灯,是否很危险?同理DataNode节点上的数据损坏了,却没有发现,是否也很危险,那么如何解决呢?
-
DataNode节点保证数据完整性的方法:
- 当DataNode读取Block的时候,它会计算CheckSum。
- 如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏。
- Client读取其他DataNode上的Block。
-
DataNode在其文件创建后周期验证CheckSum,如下图所示。
3. 掉线时限参数设置
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的单位为<font color=red>毫秒</font>,dfs.heartbeat.interval的单位为<font color=red>秒</font>。
<property> <name>dfs.namenode.heartbeat.recheck-interval</name> <value>300000</value> </property> <property> <name>dfs.heartbeat.interval</name> <value>3</value> </property>
对大数据感兴趣的小伙伴可以关注我的公众号:大数据学习宝典,在上面会定期更新学习笔记和一些心得!