Hadoop学习笔记(二)HDFS
一、HDFS相关概念
1.1 数据块
在HDFS中文件的储存是以块为单位存储,默认大小为128M。在DHFS中,文件的上传后将文件切块分别存储在不同的DataNode上。
但是,如果存储一个1MB的文件在一个128MB的块中,实际占用的空间是1MB。
1.2 元数据
数据格式:FileName(文件路径,只是逻辑地址,实际不存在),replicas(副本数量),clock-ids(文件块的ID以及分布在哪些DataNode上),id2host(对应DataNode的地址)…
1.3 名称节点(NameNode)
NameNode负责管理分布式文件系统的命名空间,不参与数据的存储和读取,只记录元数据信息。是唯一存储元数据到数据块映射的地方。
在NameNode中有两个核心的数据结构:FsImage(镜像文件,用来维护文件系统树以及元数据信息)和Editlog(编辑日志文件,用来记录文件的一些操作)。
工作原理:当DHFS启动的时候,会先将FsImage的内容加载到内存中然后将EditLog的内容合并后内存中的元数据就保持最新的状态。(映射信息是放在内存中的)
注意:不适合存放大量小文件。由于元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。每个数据块、文件、目录的存储信息都会占用字节。
1.4 数据节点(DataNode)
DataNode是分布式文件系统HDFS的工作节点,负责数据的存储和读取。
1.5 第二名称节点(SecondaryNameNode)
在NameNode运行期间,HDFS会在不断的发生更新操作。这些操作会直接写入到EditLog中,因此该文件会逐渐变大,在重启名称节点的时候,合并操作就会变慢,影响启动速度。因此每隔一段时间(默认是3600秒)或者EditLog达到一定大小的时候(默认是64M),会通知NameNode进行合并操作。同时,该机制也可以当作故障恢复的”检查点“。
工作原理:首先FsImage会暂时将日志写入一个新建的EditLog.new中,然后第二名称节点将名称节点中的FsImage和EditLog文件拉到本地后载入内存中进行合并操作,然后将合并后最新的FsImage发送给名称节点,名称节点用新的FsImage替换旧的FsImage,最后将EditLog.new重命名为EditLog
单独配置第二名称节点的位置:进入hdfs-site.xml修改字段值dfs.namenode.secondary.http-address为相应主机:端口。
二、数据流
2.1 文件写入过程
2.1.1 数据的冗余
为了保证系统的容错性和可用性,HDFS采用了多副本方式来存储。
2.1.2 数据存储机制
首先namenode接受到创建文件的请求的时候,会先去检查数据是否存在,用户权限等,通过后去安排要存放数据的datanode(返回一个datanode列表按网络结构远近排序),如何选取,如下:
数据的存放(机架感知)
一个HDFS集群通常有多个机架,机架间的通信通过交换机和路由器,而机架内部间的通信不需要。HDFS默认冗余复制因子是3。文件写入操作的时候,如果在集群内部,则把第一个副本放在发起写操作请求的数据节点上(如果是在集群外部,则从集群内部随机选一个数据节点来存放)。第二个副本放在与第一个副本不同的机架上。第三个副本放在和第一个副本相同的机架上,且随机选另一个节点。如果还有更多的副本,随机选择数据节点进行存储。
一旦选定复本的存放位置,就会根据网络拓扑创建一个管线。紧接着:
数据的复制
HDFS的数据采用了流水线复制的策略。用户向HDFS中写入一个文件的时候,会先把文件写入到本地,并被分割成若干个块,每个块依次进行复制(第一个块写入的同时将数据发送给第二个节点,第二个数据节点写入的同时发给第三个节点,当第三个数据节点写入完成后,返回成功信息给第二个数据节点,第二个数据节点返回成功信息给第一个数据节点,第一个数据节点返回成功信息给NameNode,至此复制完成。!如果中途某数据节点写入失败,会另找一个数据节点进行写入),全部块完成后,文件写入完成。
2.2 文件读取过程
客户端发送请求,分布式文件系统通过RPC请求调用namenode,以确定文件起始块的位置。对于每一个块,返回该存有该块副本的datanode地址(返回目标文件的元数据)。然后去对应的datanode上读取数据。
2.3 数据错误与恢复
名称节点出错
名称节点存储的是元数据,而其中的FsImage或者EditLog文件损坏,那么整个HDFS将不可用。Hadoop采用两种有两种机制来确保名称节点安全:第一种,同步名称节点上的信息到其他文件系统;第二种,运行第二名称节点,进行补救。但是可能会有数据丢失。
数据节点出错
在运行过程中,如果某数据节点宕机了,那么该文件就不完整了。因此,每个数据节点会向名称节点发送”心跳包“来报告自己的状态。如果发生故障,名称节点会获悉并判断数据块副本数量是否小于冗余因子,来复制新的副本。
数据出错
在网络传输中,有可能发生数据错误。或者,人为误操作把某文件给删除了,这样文件就不完整了。在客户端在读取到数据后,会采用md5和shal对数据块进行校验,以确保获取到正确的信息。
三、HDFS常用命令
命令说明:hadoop fs 等价于 hdfs dfs。
- hadoop fs :查看指令列表
- hadoop fs -ls 显示当前目录结构, [-R] 递归显示目录结构
- hadoop fs -mkdir 创建目录, [-p] 创建父目录
- hadoop fs -rm 删除文件,[-R] 递归删除目录和文件
- hadoop fs -put [localsrc] [dst] 从本地加载文件到HDFS
- hadoop fs -get [dst] [localsrc] 从HDFS导出文件到本地
- hadoop fs -copyFromLocal [localsrc] [dst] 从本地加载文件到HDFS,与put一致
- hadoop fs -copyToLocal [dst] [localsrc] 从HDFS导出文件到本地,与get一致
- hadoop fs -test -e 检测目录和文件是否存在,存在返回值$?为0,不存在返回1
- hadoop fs -text 查看文件内容
- hadoop fs -du 统计目录下各文件大小,单位字节。-du -s 汇总目录下文件大小,-du -h 显示单位
- hadoop fs -tail 显示文件末尾
- hadoop fs -cp [src] [dst] 从源目录复制文件到目标目录
- hadoop fs -mv [src] [dst] 从源目录移动文件到目标目录
- hadoop fs -appendToFile [src] [dst] 将源文件追加到目标文件末尾(不支持中间插入)
四、JAVA客户端API编写
详细代码见GitHub
pom.xml
xml<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--设置hadoop版本-->
<hadoop.3.version>3.1.2</hadoop.3.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-hs</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-shuffle</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-auth</artifactId>
<version>${hadoop.3.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
HdfsUtils.java
java/**
* HdfsUtils class
* HDFS常用操作工具类
*
* @author BoWenWang
* @date 2020/2/14 22:23
*/
public class HdfsUtils {
private static final String HDFS_PATH = "hdfs://192.168.174.201:8020";
private static final String HDFS_USER = "wbw";
private static FileSystem fileSystem;
static {
Configuration conf = new Configuration();
try {
// 第三个参数是用户权限
fileSystem = FileSystem.get(URI.create(HDFS_PATH), conf, HDFS_USER);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建目录
* @param path 目录路径
* @return 是否创建成功
* @throws IOException
*/
public static boolean mkdir(String path) throws IOException {
return fileSystem.mkdirs(new Path(path));
}
/**
* 上传文件
* @param src 源路径
* @param dst 目标路径
* @throws IOException
*/
public static void copyFromLocalFile(String src, String dst) throws IOException {
fileSystem.copyFromLocalFile(new Path(src), new Path(dst));
}
/**
* 文件下载
* @param src
* @param dst
* @throws IOException
*/
public static void copyToLocalFile(String src, String dst) throws IOException {
fileSystem.copyToLocalFile(new Path(src), new Path(dst));
}
/**
* 删除文件
* @param path 文件路径
* @param b 是否递归删除
* @return 是否删除文件成功
* @throws IOException
*/
public static boolean delete(String path, boolean b) throws IOException {
return fileSystem.delete(new Path(path), b);
}
/**
* 判断文件或目录是否存在
* @param path 文件或目录路径
* @return 是否存在
* @throws IOException
*/
public static boolean exists(String path) throws IOException {
return fileSystem.exists(new Path(path));
}
/**
* 查看文件
* @param path 文件路径
* @return 文件内容
* @throws IOException
*/
public static String open(String path) throws IOException {
FSDataInputStream fis = fileSystem.open(new Path(path));
ByteArrayOutputStream bout = new ByteArrayOutputStream();
IOUtils.copy(fis, bout, 1024);
fis.close();
return new String(bout.toByteArray());
}
/**
* 文件重命名(移动)
* @param oldPath 旧路径
* @param newPath 新路径
* @return 是否操作成功
* @throws IOException
*/
public static boolean rename(String oldPath, String newPath) throws IOException {
return fileSystem.rename(new Path(oldPath), new Path(newPath));
}
/**
* 查看目录下的所有文件
* @param path 文件目录
* @param b 是否递归查看
* @throws IOException
*/
public static void listFiles(String path, Boolean b) throws IOException {
RemoteIterator<LocatedFileStatus> iterator = fileSystem.listFiles(new Path(path), b);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public static void listFiles(String path) throws IOException {
listFiles(path, false);
}
}
五、日志文件
日志文件里面记录了HDFS的各种信息,现在我们来对日志文件进行解析一下。打开Hadoop临时文件目录查看文件。其中edits就是编辑日志,fsimage就是镜像文件,而edits_inprogress则是HDFS正在运行使用的日志。
5.1 镜像文件
首先我们来看一下当前HDFS文件结构:
查看镜像文件命令:
$hdfs oiv -i fsimage_0000000000000000036 -o a.txt -p XML |
查看 a.txt 内容(这里推荐cat内容后,复制到IDEA下,方便阅览。这里仅列出部分节点内容,同时可以打开WEB-UI来进行信息对照):
目录信息节点
xml<inode>
<id>16385</id>
<!-- 类型 -->
<type>DIRECTORY</type>
<!-- 目录名(这里为根目录所以为空) -->
<name></name>
<!-- 时间戳 -->
<mtime>1581692427585</mtime>
<!-- 权限 -->
<permission>wbw:supergroup:0755</permission>
<nsquota>9223372036854775807</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16389</id>
<type>DIRECTORY</type>
<name>user</name>
<mtime>1581694636967</mtime>
<permission>wbw:supergroup:0755</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>文件信息节点
xml<inode>
<id>16391</id>
<!-- 文件类型 -->
<type>FILE</type>
<!-- 文件名 -->
<name>b.txt</name>
<!-- 副本数(文件夹没有副本数) -->
<replication>3</replication>
<!-- 时间戳 -->
<mtime>1581692458378</mtime>
<atime>1581692458090</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>wbw:supergroup:0644</permission>
<blocks>
<block>
<!-- 块ID -->
<id>1073741826</id>
<genstamp>1002</genstamp>
<!-- 字节数 -->
<numBytes>11</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>目录树(维护HDFS目录结构)
xml<INodeDirectorySection>
<directory>
<!-- 根目录ID -->
<parent>16385</parent>
<!-- 子目录/文件(这里可以对照上面的目录节点ID,发现是user子目录) -->
<child>16389</child>
</directory>
<directory>
<parent>16389</parent>
<child>16393</child>
<child>16392</child>
<child>16390</child>
</directory>
<directory>
<parent>16392</parent>
<child>16391</child>
</directory>
</INodeDirectorySection>
5.2 编辑日志
查看编辑日志命令:
$hdfs oev -i edits_0000000000000000042-0000000000000000043 -o b.txt -p XML |
操作信息节点
<RECORD> |
5.3 日志滚动
打开WEB-UI的“Startup Progress”菜单,从图上可以看出HDFS启动后,显示加载了fsimage然后再加载edits,整个操作在安全模式下进行:
安全模式(read-only)
$>hdfs dfsadmin -safemode enter //进入(进入后无法进行删除等操作) |
保存名字空间(手动滚动)
HDFS在启动的时候,会自动进行滚动。但是我们也可以手动对其进行滚动操作。
手动滚动前,编号是46:
进入安全模式(滚动操作只能在安全模式下进行): |
手动滚动后,编号是48:
对比上面2图,可以发现:
- edits文件多了一个46-47,原本是44-45
- fsimage多了一个47,少了43(fsimage一般保留2个,用于滚动)
- 同时运行时日志变成48
六、配额管理
HDFS可以对目录数量和可用空间大小进行限制。
6.1 目录配额
命令介绍
-setQuota <quota> <dirname>...<dirname>: Set the quota <quota> for each directory <dirName>. |
可以看到该命令会限制被限制目录下的文件数量(包括文件和文件夹嵌套),如果设置1,表示空目录,即自身占1个数量。
执行命名
$hdfs dfsadmin -setQuota 3 /user/wbw |
清除配额
$hdfs dfsadmin -clrQuota <dirname>...<dirname> |
6.2 空间配额
命令介绍
$>hdfs dfsadmin -setSpaceQuota <quota> <dirname> |
注意:副本为3的1GB文件将消耗3GB的配额。同时还可设置 quota 的单位(e.g. 50t is 50TB, 5m is 5MB, 3p is 3PB).,默认是字节。
清除配额
$>hdfs dfsadmin -clrSpaceQuota <dirname>...<dirname> |
七、快照管理
7.1 描述
迅速对文件(夹)进行备份。不产生新数据文件(即源文件和快照对应文件的存储块ID是相同的)。
默认是禁用快照,要想进行快照,要先启用。
7.2 命令
$>hdfs dfsadmin -allowSnapshot dir1 //在dir1启用快照 |
7.3 效果
执行如下命令
[wbw@s201 /home/wbw]$hdfs dfsadmin -allowSnapshot /user/wbw |
可以看到快照会在该目录下新建文件,如果此时修改源文件,那么快照的内容是不会变的(如图):