Hadoop-5-HDFS
1、概述
Hadoop Distributed File System (HDFS™),是一种分布式文件管理系统,用于管理多个机器上的文件
它主要用于存储文件,通过目录树结构来定位文件的位置。适合一次写入,多次读出的场景,不支持文件的修改,所以用它做数据的分析,而不是当作网盘
2、优缺点
【1】优点
- 高容错性。容错性体现在数据保存到多个副本上(DataNode),并且可以通过增加副本的数量(replication),来提高这种特性。并且当一个副本的数据丢失后,它还可以自动恢复
- 大数据量。一是处理数据的量级,可以达到TB、PB的级别。二是文件的数量,能够处理百万以上的规模
- 流式数据。一次写入,多次写出。只能追加,不能修改。保证数据的一致性
- 高可用性。可以将数据节点部署到多个廉价机器上
【2】缺点
- 不能实现低延迟的数据访问。无法做到毫秒级的海量数据存储
- 无法高效地存储多个小文件。处理大量的小文件的存储、目录结构以及块信息,会占用NameNode的大量内存资源。此外小文件存储时的寻址时间会超过其读取时间,这违反了HDFS设计的初衷
- 不能并发写入和随机修改。对于一个文件,在同一时间内只能有一个写入,不允许多个线程同时写入。仅支持文件的追加,不支持文件的修改
3、架构设计
主要由NameNode,DataNode和SecondaryNameNode组成
需要注意的是SecondaryNameNode不是NameNode的热备,所以当NameNode节点挂掉时,其并不能立即替换NameNode并提供服务。SecondaryNameNode是一个用于监控HDFS状态的后台辅助程序,它每隔一段时间就会获取NameNode(HDFS元数据)的快照
4、块
HDFS中的文件在物理上是分块存储(block),块的大小可以通过etc/hadoop/hdfs-site.xml
中的dfs.blocksize
来设置。apache hadoop 2.9.2默认大小为134217728(128MB)
HDFS的块的大小比物理磁盘的块的大小要大,这是为了减少寻址的开销。如果块设置得足够大,那么数据传输的时间要明显地久于块寻址的时间
寻址时间为传输时间的1%时,是最佳状态。计算公式:文件传输时间 = 磁盘寻址时间 / 0.01
块的大小 = 文件传输时间 * 磁盘传输速率
根据公式:假如寻址时间是10s,传输速率是100MB/s,那么块的大小 = 10s / 0.01 * 100MB/s = 100MB
5、命令操作【常用命令】
所有命令都是通过bin/hadoop fs 命令
的方式执行的。注意命令前面都有一个横杠
【1】创建目录
-mkdir -p
,选项p表示递归,和Linux命令一样,不带参数仅创建一级目录
【2】查看目录
-ls -R
,选项R表示递归,它会一直到目录的最里面
【3】删除目录
-rmdir
【4】上传文件
-copyFromLocal /本地文件 /HDFS目录
,复制本地文件到HDFS
-put /本地文件 /HDFS目录
,和copyFromLocal命令一样
-moveFromLocal /本地文件 /HDFS目录
,移动本地文件到HDFS
【5】下载文件
-copyToLocal /HDFS文件 /本地目录
,复制HDFS文件到本地
-get /HDFS文件 /本地目录
,和copyToLocal命令一样
-moveToLocal /HDFS文件 /本地目录
,移动HDFS文件到本地。
Hadoop 2.9.2,提示:moveToLocal: Option ‘-moveToLocal’ is not implemented yet.
【6】追加文件
-appendToFile /本地文件 /HDFS文件
,将本地文件的内容追加到HDFS文件的末端
【7】查看文件
-cat /HDFS文件或目录
,查看文件,可以使用*通配符,和Linux命令一样
-tail -f /HDFS文件
,查看文件末尾,后41行,可以追加f选项,和Linux命令一样
【8】删除文件
-rm -r /HDFS目录或文件
,删除文件或目录,和Linux命令一样
6、Java客户端操作
【1】准备环境
- Java
- Maven
- 和服务端一致的Hadoop,并解压缩到无中文目录
- 下载winutils,并将hadoop-2.8.3/bin目录里的
winutils.exe
复制到解压缩后的hadoop的bin目录当中 - 配置环境变量:
HADOOP_HOME
到解压缩目录 - 打开IDE。一定要先配置好环境变量再打开IDE
【2】MAVEN依赖
<properties>
<!-- 和服务端版本一致 -->
<hadoop.version>2.9.2</hadoop.version>
</properties>
<dependency>
<!-- hadoop-client间接依赖hadoop-common -->
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
【3】获取文件系统
通过org.apache.hadoop.fs.FileSystem
对象的静态方法来获取
-
FileSystem get(Configuration conf) throws IOException
,需要传递一个org.apache.hadoop.conf.Configuration
对象,该配置对象的属性,可以从etc/hadoop/core-site.xml
中获取Configuration conf = new Configuration(); // NameNode服务器的IP,9000是默认端口 conf.set("fs.defaultFS", "hdfs://???.???.???.???:9000"); FileSystem fileSystem = null; try { fileSystem = FileSystem.get(conf); System.out.println(fileSystem); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
FileSystem get(final URI uri, final Configuration conf, String user) throws IOException, InterruptedException
URI uri = null; try { // NameNode服务器的IP,9000是默认端口 uri = new URI("hdfs://???.???.???.???:9000"); } catch (URISyntaxException e) { e.printStackTrace(); return; } Configuration conf = new Configuration(); FileSystem fileSystem = null; try { // 操作文件系统的用户名 fileSystem = FileSystem.get(uri, conf, "???"); System.out.println(fileSystem); } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
【推荐】
FileSystem get(URI uri, Configuration conf) throws IOException
URI uri = null; try { // NameNode服务器的IP,9000是默认端口 uri = new URI("hdfs://???.???.???.???:9000"); } catch (URISyntaxException e) { e.printStackTrace(); return; } // 操作文件系统的用户名。这句话也可以配置到vm参数当中-DHADOOP_USER_NAME=??? System.setProperty("HADOOP_USER_NAME", "???"); Configuration conf = new Configuration(); FileSystem fileSystem = null; try { fileSystem = FileSystem.get(uri, conf); System.out.println(fileSystem); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
【4】参数配置的优先级
通过org.apache.hadoop.conf.Configuration
对象配置参数的优先级是最高的。接着是classpath路径下的配置文件,如core-site.xml
等。最后才是默认配置的参数
【5】创建目录
try {
boolean mkdirs = fileSystem.mkdirs(new Path("/?/?/?"));
System.out.println(mkdirs ? "成功" : "失败");
} catch (IOException e) {
e.printStackTrace();
}
【6】查看目录
try {
RemoteIterator<LocatedFileStatus> remoteIterator = fileSystem.listFiles(new Path("/"), true); // 第二个参数表示是否递归
System.out.println("Permission\tOwner\tGroup\t\tSize\tLast Modified\tPath\t\tReplication");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd HH:mm");
while (remoteIterator.hasNext()) {
LocatedFileStatus locatedFileStatus = remoteIterator.next();
String permission = locatedFileStatus.getPermission().toString();
StringBuilder line = new StringBuilder(permission).append("\t");
String owner = locatedFileStatus.getOwner();
line.append(owner).append("\t");
String group = locatedFileStatus.getGroup();
line.append(group).append("\t");
long size = locatedFileStatus.getLen();
line.append(size).append("\t");
long lastModified = locatedFileStatus.getModificationTime();
String time = simpleDateFormat.format(new Date(lastModified));
line.append(time).append("\t\t");
Path path = locatedFileStatus.getPath();
String pathStr = path.toString();
line.append(pathStr).append("\t\t");
short replication = locatedFileStatus.getReplication();
line.append(replication);
BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations();
line.append(Arrays.asList(blockLocations)).append("\t");
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
【7】删除目录或文件
try {
boolean delete = fileSystem.delete(new Path("/?"), true); // 第二个参数表示是否递归
System.out.println(delete ? "成功" : "失败");
} catch (IOException e) {
e.printStackTrace();
}
【8】上传文件
// 复制文件
try {
fileSystem.copyFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
e.printStackTrace();
}
// 剪切文件
try {
fileSystem.moveFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
e.printStackTrace();
}
// 复制文件,流式方式
File file = new File("?:\\???");
try {
FileInputStream fis = new FileInputStream(file);
FSDataOutputStream fsos = fileSystem.create(new Path("/???/???"));
/*
* 第三个参数,缓冲容器大小
* 第四个参数,是否关闭输入流和输入流
*/
IOUtils.copyBytes(fis, fsos, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}
【9】下载文件
try {
/*
* 第一个参数,是否删除源文件。true: 剪切,false: 复制
* 最后一个参数,是否将RawLocalFileSystem作为本地文件系统。推荐使用
*/
fileSystem.copyToLocalFile(false, new Path("?:\\???"), new Path("/???/???"), true);
} catch (IOException e) {
e.printStackTrace();
}
// 下载文件,流式方式
try {
FSDataInputStream fsis = fileSystem.open(new Path("/???/???");
File file = new File("?:\\???");
FileOutputStream fos = new FileOutputStream(file);
/*
* 第三个参数,缓冲容器大小
* 第四个参数,是否关闭输入流和输入流
*/
IOUtils.copyBytes(fsis, fos, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}
// 下载2块文件
try {
FSDataInputStream fsis = fileSystem.open(new Path("/???/jdk-8u212-linux-x64.tar.gz"));
File part1 = new File("?:\\part1");
FileOutputStream fos1 = new FileOutputStream(part1);
int bufferSize = 4096; // 缓冲大小
byte[] buffer = new byte[bufferSize];
long blockSize = 128 * 1024 * 1024; // 128MB -- 块大小
for (int offset = 0; offset < blockSize; offset += bufferSize) {
int read = fsis.read(buffer, 0, bufferSize);
fos1.write(buffer, 0, read);
}
IOUtils.closeStream(fos1); // 关闭输出流
File part2 = new File("?:\\part2");
FileOutputStream fos2 = new FileOutputStream(part2);
fsis.seek(blockSize);
IOUtils.copyBytes(fsis, fos2, bufferSize, true); // 复制块1,并关闭输入流和输出流
} catch (IOException e) {
e.printStackTrace();
}
【10】重命名目录或文件(移动操作)
try {
boolean rename = fileSystem.rename(new Path("/?/?"), new Path("/?/?"));
System.out.println(rename ? "成功" : "失败");
} catch (IOException e) {
e.printStackTrace();
}
【11】判断是否是文件还是目录
try {
Path path = new Path("/?/?");
boolean file = fileSystem.isFile(path);
boolean directory = fileSystem.isDirectory(path);
System.out.println(file ? "是文件" : "不是文件!");
System.out.println(directory ? "是目录" : "不是目录!");
} catch (IOException e) {
e.printStackTrace();
}
注意:如果目录或文件不存在时,这2种判断都会返回false
7、机架感知(Rack Awareness)
Hadoop具有机架感知的功能。 将一个块的分片复制品放在不同的机架上,通过这种块放置的方式,外加使用机架感知来实现容错。 这可以在群集发生网络切换故障或分区时,提供数据的可用性
就默认分片数为3而言:如果本机DataNode需要存储数据,则Hadoop会将DataNode的副本放到本机上,否则就随机放到其他DataNode上。同时会在另外一机架上的节点放置副本。最后一副本放到同一机架上的不同节点
这样做可以减少机架间的写入流量,从而提高写入效率。因为机架故障的可能性远小于节点故障的可能性,所以这种策略不会影响数据的可靠性和可用性。此外也减少了读取数据时使用的聚合网络带宽,因为块放到了2个唯一的机架上,而不是3个。三分之一的副本存放到一个节点上,三分之二的副本存放到一个机架上。这种策略可以提高写入性能,而不会影响数据的可靠性或可读性
8、文件上传和文件下载的工作流程
【1】文件上传
- 客户端请求NameNode,是否可以进行文件的上传
- 当客户端收到可以上传的响应后,依次顺序上传block0(0 ~ 128MB),block1(128MB ~ 256MB)到DataNode
- 获取所有需要上传的DataNode的节点信息,依次请求建立传输通道
- 收到DataNode响应,传输通道建立完毕,开始传输数据
【2】文件下载
- 客户端请求NameNode,获取block信息
- 收到NameNode返回的元数据信息,请求对应的DataNode,获取所需的block数据
- 合并所有的block数据为一个完整的数据,最终得到完整数据
9、NameNode和SecondaryNameNode工作机制
【1】NameNode启动时的工作机制
- NameNode将镜像文件(FsImage)加载到内存中
- NameNode将编辑日志(EditLog)加载到内存中
- 保存检查点位置(checkpoint)
- 进入安全模式(Safe mode)
这一过程,可以通过NameNode的管理页面来查看,在Startup Progress
选项卡页面当中
Elapsed Time: ??? sec, Percent Complete: 100%
Phase | Completion | Elapsed Time |
---|---|---|
Loading fsimage /???/dfs/name/current/fsimage_??? ??? KB | 100% | ??? sec |
inodes (???/???) | 100% | |
delegation tokens (???/???) | 100% | |
cache pools (???/???) | 100% | |
Loading edits | 100% | ??? sec |
/???/dfs/name/current/edits_???-??? ??? MB (???/???) | 100% | |
Saving checkpoint | 100% | ??? sec |
inodes /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
delegation tokens /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
cache pools /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
Safe mode | 100% | ??? sec |
awaiting reported blocks (???/???) | 100% |
每一个block占元数据的大小为150字节,由于NameNode在启动时,会将这些都加载到内存当中,而内存是有上限的。所以这就是Hadoop不宜存储过多小文件的原因,它会占用大量的内存空间
【2】SecondaryNameNode运行时的工作机制
- 请求NameNode是否需要检查点(Checkpoint)
- 如果需要Checkpoint,则执行
- 复制NameNode的编辑日志(edits),包括已经执行完毕的(例如:edits_0000000000000000001-0000000000000000002)以及正在执行的(例如:edits_inprogress_0000000000000010453)
- 加载到内存中,合并生成新的镜像文件(fsimage.checkpoint)
- 复制镜像文件(fsimage.checkpoint)到NameNode
将编辑日志(EditLog)中的所有事务保存到镜像文件(FsImage)里,并加载到内存中,然后结转旧的编辑日志(EditLog),这个过程称为检查点(checkpoint)
触发SecondaryNameNode请求NameNode查看是否需要执行检查点(checkpoint),是由2个参数配置来决定的,在etc/hadoop/hdfs-site.xml
中
<!-- 执行检查点的周期,单位:秒。默认为1小时 -->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
<!-- 执行编辑日志的事务次数。默认为一百万次 -->
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
</property>
可以通过前端管理页面来查看SecondaryNameNode的概览,链接是:http://SecondaryNameNode服务主机的IP:50090
包括了:Hadoop的版本(Version),NameNode的服务地址(NameNode Address),启动时间(Started),上次检查点时间(Last Checkpoint)以及检查点周期(Checkpoint Period)和HDFS事务执行次数上限(Checkpoint Transactions)
注意:hadoop-2.9.2
版本的SecondaryNameNode管理页面JS有一个名为moment
的function未定义,所以在加载页面时不会展示数据。解决办法是打开浏览器的开发者工具里的Sources
选项卡,修改moment
函数
'date_tostring' : function (v) {
// return moment(Number(v)).format('ddd MMM DD HH:mm:ss ZZ YYYY');
return new Date(v); // 直接新建时间对象并返回即可
},
在dfs-dust.js:61
处打断点,修改并保存JS,放开断点即可
【3】NameNode运行时的工作机制
接收SecondaryNameNode发来的镜像文件(fsimage.checkpoint),并重命名为fsimage
10、编辑日志和镜像文件
【1】概念
EditLog文件位于??/dfs/name/current/
目录里,以edits_
开头。它保存了HDFS的所有更新操作的记录的序列化信息
seen_txid文件保存了正在执行过程中的EditLog文件(edits_inprogress_???
)的事务ID
FsImage文件位于??/dfs/name/current/
目录里,以fsimage_
开头。它保存了HDFS的所有目录和文件的idnode的序列化信息,是HDFS元数据的检查点(checkpoint)的持久化形态
VERSION文件,保存了NameNode的唯一标识(namespaceID),全局唯一的集群ID(clusterID),以及存储形态(storageType=NAME_NODE)
SecondaryNameNode和NameNode相比,其???/dfs/namesecondary/current/
目录里,仅仅少了正在执行过程中的EditLog文件(edits_inprogress_???)和seen_txid文件。这样设计是为了方便当NameNode节点故障时,并且没有及时备份数据,可以通过SecondaryNameNode来恢复NameNode
【2】反序列化EditLog和FsImage
可以反序列化编辑日志,使用命令:bin/hdfs oev -p 反序列化的文件类型 -i 编辑日志路径 -o 反序列化后的文件路径
可选的反序列化的文件类型有:binary (native binary format that Hadoop uses), xml (default, XML format), stats (prints statistics about edits file)。可以看到默认是XML类型
反序列化镜像文件,使用命令:bin/hdfs oiv -p 反序列化的文件类型 -i 镜像文件路径 -o 反序列化后的文件路径
可选的反序列化的文件类型有:(XML|FileDistribution|ReverseXML|Web|Delimited) The default is Web。可以看到默认是Web类型。推荐使用XML
【3】反序列化后的EditLog
<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
<!-- EditLog版本号 -->
<EDITS_VERSION>-63</EDITS_VERSION>
<RECORD>
<!-- 操作类型:开始记录EditLog -->
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<!-- 事务ID -->
<TXID>1</TXID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作类型:结束记录EditLog -->
<OPCODE>OP_END_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>2</TXID>
</DATA>
</RECORD>
<!-- 另外一个EditLog -->
<RECORD>
<!-- 操作类型:创建目录 -->
<OPCODE>OP_MKDIR</OPCODE>
<DATA>
<TXID>4</TXID>
<!-- 文件大小:0,目录 -->
<LENGTH>0</LENGTH>
<!-- inodeID -->
<INODEID>16386</INODEID>
<!-- 路径 -->
<PATH>/tmp</PATH>
<!-- 创建时间戳 -->
<TIMESTAMP>1565635741266</TIMESTAMP>
<!-- 权限 -->
<PERMISSION_STATUS>
<!-- 所有者 -->
<USERNAME>hadoop</USERNAME>
<!-- 所属组 -->
<GROUPNAME>supergroup</GROUPNAME>
<MODE>504</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>
<RECORD>
<!-- 操作类型:新建文件 -->
<OPCODE>OP_ADD</OPCODE>
<DATA>
<TXID>10315</TXID>
<LENGTH>0</LENGTH>
<INODEID>18322</INODEID>
<PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
<!-- 分片数 -->
<REPLICATION>4</REPLICATION>
<MTIME>1565637361625</MTIME>
<ATIME>1565637361625</ATIME>
<!-- 块大小:128MB -->
<BLOCKSIZE>134217728</BLOCKSIZE>
<!-- 客户端名称 -->
<CLIENT_NAME>DFSClient_attempt_1565637203418_0001_r_000000_0_950599497_1</CLIENT_NAME>
<!-- 客户端IP -->
<CLIENT_MACHINE>???.???.???.???</CLIENT_MACHINE>
<OVERWRITE>false</OVERWRITE>
<PERMISSION_STATUS>
<USERNAME>hadoop</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
<RPC_CLIENTID>fb6e1e9c-f8a4-4428-a6f0-12304607e6c1</RPC_CLIENTID>
<RPC_CALLID>4</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作类型:分配块ID -->
<OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
<DATA>
<TXID>10316</TXID>
<BLOCK_ID>1073743480</BLOCK_ID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作类型:添加块 -->
<OPCODE>OP_ADD_BLOCK</OPCODE>
<DATA>
<TXID>10318</TXID>
<PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
<BLOCK>
<BLOCK_ID>1073743480</BLOCK_ID>
<NUM_BYTES>0</NUM_BYTES>
<GENSTAMP>2656</GENSTAMP>
</BLOCK>
<RPC_CLIENTID></RPC_CLIENTID>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作类型:删除临时目录 -->
<OPCODE>OP_DELETE</OPCODE>
<DATA>
<TXID>10324</TXID>
<LENGTH>0</LENGTH>
<PATH>/???/_temporary</PATH>
<TIMESTAMP>1565637361871</TIMESTAMP>
<RPC_CLIENTID>4e4b34de-f566-4b67-a350-e014ebc27e39</RPC_CLIENTID>
<RPC_CALLID>39</RPC_CALLID>
</DATA>
</RECORD>
</RECORD>
<RECORD>
<!-- 操作类型:重命名(执行MapReduce任务) -->
<OPCODE>OP_RENAME</OPCODE>
<DATA>
<TXID>10354</TXID>
<LENGTH>0</LENGTH>
<SRC>/tmp/hadoop-yarn/staging/history/done_intermediate/hadoop/job_1565637203418_0001_conf.xml</SRC>
<DST>/tmp/hadoop-yarn/staging/history/done/2019/08/13/000000/job_1565637203418_0001_conf.xml</DST>
<TIMESTAMP>1565637363513</TIMESTAMP>
<OPTIONS>TO_TRASH</OPTIONS>
<RPC_CLIENTID>696288b7-d3ff-4bf2-a17b-5dc108925fc9</RPC_CLIENTID>
<RPC_CALLID>20</RPC_CALLID>
</DATA>
</RECORD>
<!-- 另外一个EditLog -->
<RECORD>
<!-- 操作类型:重命名(上传文件) -->
<OPCODE>OP_RENAME_OLD</OPCODE>
<DATA>
<TXID>25</TXID>
<LENGTH>0</LENGTH>
<SRC>/???/???._COPYING_</SRC>
<DST>/???/???</DST>
<TIMESTAMP>1566211667239</TIMESTAMP>
<RPC_CLIENTID>a3bffd43-b49b-4c0b-b4d1-394feb9fd9a2</RPC_CLIENTID>
<RPC_CALLID>8</RPC_CALLID>
</DATA>
</RECORD>
</EDITS>
【4】反序列化后的FsImage
<?xml version="1.0"?>
<fsimage>
<!-- 版本信息 -->
<version>
<layoutVersion>-63</layoutVersion>
<onDiskVersion>1</onDiskVersion>
<oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
</version>
<!-- NameNode信息 -->
<NameSection>
<!-- 对应VERSION文件里的namespaceID -->
<namespaceId>598335362</namespaceId>
<genstampV1>1000</genstampV1>
<genstampV2>2670</genstampV2>
<genstampV1Limit>0</genstampV1Limit>
<lastAllocatedBlockId>1073743492</lastAllocatedBlockId>
<txid>10455</txid>
</NameSection>
<!-- 目录和文件信息 -->
<INodeSection>
<!-- 最后一个InodeId -->
<lastInodeId>18346</lastInodeId>
<numInodes>42</numInodes>
<inode>
<id>16385</id>
<!-- 这是个目录 -->
<type>DIRECTORY</type>
<!-- /:根路径 -->
<name/>
<mtime>1566131716332</mtime>
<!-- 权限,所有者:所属组:0权限信息 -->
<permission>hadoop:supergroup:0755</permission>
<nsquota>9223372036854775807</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16386</id>
<type>DIRECTORY</type>
<!-- 目录名称 -->
<name>tmp</name>
<mtime>1565636758639</mtime>
<permission>hadoop:supergroup:0770</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>18346</id>
<!-- 这是个文件 -->
<type>FILE</type>
<!-- 文件名称 -->
<name>jdk-8u212-linux-x64.tar.gz</name>
<!-- 分片数量 -->
<replication>4</replication>
<mtime>1566132732553</mtime>
<atime>1566132726115</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>hadoop:supergroup:0644</permission>
<blocks>
<block>
<id>1073743491</id>
<genstamp>2669</genstamp>
<numBytes>134217728</numBytes>
</block>
<block>
<id>1073743492</id>
<genstamp>2670</genstamp>
<numBytes>60795424</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
</INodeSection>
<INodeReferenceSection/>
<!-- 快照信息 -->
<SnapshotSection>
<snapshotCounter>0</snapshotCounter>
<numSnapshots>0</numSnapshots>
</SnapshotSection>
<!-- 目录的父子关系 -->
<INodeDirectorySection>
<directory>
<parent>16385</parent>
<child>18340</child>
<child>16392</child>
<child>16386</child>
</directory>
<directory>
<parent>16386</parent>
<child>16387</child>
<child>18243</child>
</directory>
</INodeDirectorySection>
<FileUnderConstructionSection/>
<SecretManagerSection>
<currentId>0</currentId>
<tokenSequenceNumber>0</tokenSequenceNumber>
<numDelegationKeys>0</numDelegationKeys>
<numTokens>0</numTokens>
</SecretManagerSection>
<CacheManagerSection>
<nextDirectiveId>1</nextDirectiveId>
<numDirectives>0</numDirectives>
<numPools>0</numPools>
</CacheManagerSection>
</fsimage>
11、滚动编辑日志
使用命令bin/hdfs dfsadmin -rollEdits
,这样会结转当前正在执行中的EditLog(edits_???-???
),并生成一个新的正在执行过程中的EditLog(edits_inprogress_???
)
Successfully rolled edit logs.
New segment starts at txid ???
12、通过SecondaryNameNode来恢复NameNode
【1】手动方式
- 模拟NameNode故障,
jps
查看NameNode的进程号,通过命令kill -9 NameNode的进程ID
强制停止NameNode - 首先删除
???/dfs/name
目录里的所有文件 - 使用
scp
复制SecondaryNameNode的???/dfs/namesecondary
目录里的所有文件到NameNode的???/dfs/name
目录里,并删除复制后的name目录里的in_use.lock
文件 - 启动NameNode,
sbin/hadoop-daemon.sh start namenode
- 查看是否可以访问NameNode的前端管理页面,并测试HDFS的文件上传和文件下载
【2】命令方式
-
模拟NameNode故障,
jps
查看NameNode的进程号,通过命令kill -9 NameNode的进程ID
强制停止NameNode -
修改NameNode节点的
etc/hadoop/hdfs-site.xml
文件<!-- 修改执行检查点的周期为2分钟,这是为了保证能够及时同步SecondaryNameNode的数据。默认为1小时 --> <property> <name>dfs.namenode.checkpoint.period</name> <value>120</value> </property> <!-- 指定NameNode的name目录的路径。默认为file://${hadoop.tmp.dir}/dfs/name --> <property> <name>dfs.namenode.name.dir</name> <value>/???/dfs/name</value> </property>
-
使用
scp
复制SecondaryNameNode的???/dfs/namesecondary
目录到NameNode的???/dfs
目录下,和???/dfs/name
平级即可。并删除复制后的namesecondary目录里的in_use.lock
文件 -
删除
???/dfs/name
目录里的所有文件 -
执行导入检查点数据命令,
bin/hdfs namenode -importCheckpoint
执行命令后,会发现NameNode被启动起来。命令执行过程较长,如果此时访问NameNode的前端管理页面,会发现提示信息:
Upgrade in progress. Not yet finalized.
当看到process的信息后,就可以Ctrl + C停止命令了
-
启动NameNode,
sbin/hadoop-daemon.sh start namenode
-
查看是否可以访问NameNode的前端管理页面,并测试HDFS的文件上传和文件下载
13、安全模式(Safemode)
【1】概念
当NameNode在刚启动时,会进入安全模式,此时HDFS对于客户端来说是只读的
在系统正常运行期间,NameNode会在内存中保存所有的块位置的映射信息。而在安全模式下,每个DataNode会向NameNode发送最新的块列表信息。NameNode了解的块位置信息越多,它可越高效地运行文件系统
一旦满足最小副本条件,NameNode会在30秒后退出安全模式。最小副本条件是指,在整个文件系统当中,99.9%的块满足最小副本级别,最小副本级别可以通过etc/hadoop/hdfs-site.xml
的dfs.replication.min
参数来配置,默认值为1
启动一个刚格式化的HDFS集群时,不会进入安全模式,是因为此时还没有任何块信息
【2】相关命令
-
查看当前是否在安全模式当中
bin/hdfs dfsadmin -safemode get
-
进入安全模式
bin/hdfs dfsadmin -safemode enter
-
退出安全模式
bin/hdfs dfsadmin -safemode leave
-
等待安全模式退出
bin/hdfs dfsadmin -safemode wait
#!/bin/bash
bin/hdfs dfsadmin -safemode wait
echo "退出安全模式!"
# 执行HDFS文件操作
# bin/hadoop fs -???
14、NameNode多目录配置
在etc/hadoop/hdfs-site.xml
中配置
<!-- 指定NameNode的name目录的路径,多个目录之间用逗号隔开。默认为file://${hadoop.tmp.dir}/dfs/name -->
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/name1,file://${hadoop.tmp.dir}/dfs/name2</value>
</property>
注意:配置此项后,再次启动集群时需要格式化NameNode
15、DataNode工作机制
- DataNode启动后,向NameNode发起注册
- 注册成功后,每隔1个小时上传一次所有块的位置信息
- NameNode每隔3秒钟(可配置),执行一次心跳检测,DataNode接收到心跳检测并返回给NameNode发来的命令信息
- 如果超过10分钟(可配置)[外加30秒],NameNode没有收到DataNode传来的心跳检测信息,则认为该节点不可用
DataNode不同于NameNode,其无需进行格式化,在初始化过程中,创建好数据目录
DataNode保存了数据,数据长度(文件大小),校验和(CRC),以及时间戳等信息。这些位于???/dfs/data/current
目录当中
DataNode校验和使用CRC算法(循环冗余校验)。对原始数据进行CRC的校验计算,然后和传输过来的CRC校验位进行对比,查看结果是否一致
心跳检测周期和心跳超时时间,可以在etc/hadoop/hdfs-site.xml
中来配置
<!-- 心跳检测DataNode的超时时间,单位:毫秒。默认为300秒(5分钟) -->
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<!-- DataNode的心跳检测间隔,单位:秒。默认为3秒 -->
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
心跳超时时间 = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval
所以默认心跳超时时间是10分钟
一旦NameNode将最近没有发送heartbeat消息的DataNode标记为已死亡,就不会再将新的IO请求转发给它,并且对于HDFS来说,它是不可用的。这就有可能降低某些block的分片数量(replication factor)
16、DataNode多目录配置
在etc/hadoop/hdfs-site.xml
中配置
<!-- 指定NameNode的name目录的路径,多个目录之间用逗号隔开。默认为file://${hadoop.tmp.dir}/dfs/data -->
<property>
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/data1,file://${hadoop.tmp.dir}/dfs/data2</value>
</property>
注意:配置此项后,再次启动集群时需要格式化NameNode
17、集群节点的动态增加和动态移除
【1】动态新增节点
-
准备好新的机器,配置好静态IP,host和防火墙,以及Java运行环境和环境变量,并新建执行Hadoop的用户
-
使用
scp
命令复制NameNode服务节点的Hadoop目录到新的机器上 -
配置好其他节点的host
-
使用命令
ssh-copy-id 用户名@新机器host
,为NameNode节点和ResourceManager节点,创建新的机器的免密登录 -
① 在所有节点的
etc/hadoop
目录下,创建名为dfs.hosts
的文件,并添加所有节点的IP到文件中。每个IP占一行,不允许有空格② 在所有节点的
etc/hadoop/hdfs-site.xml
中,修改配置<!-- 修改 --> <property> <!-- 配置副本数 --> <name>dfs.replication</name> <value>增加机器后的DataNode的数量</value> </property> <!-- 新增 --> <property> <!-- 指定可以连接到NameNode的主机列表。如果该值为空,则允许所有主机。默认为空 --> <name>dfs.hosts</name> <value>/???/可以连接到NameNode的主机列表文件</value> </property>
③ 在所有节点的
etc/hadoop/slaves
中,将新机器的IP添加到里面 -
在NameNode节点机器上,执行命令
bin/hdfs dfsadmin -refreshNodes
,刷新DataNode -
在ResourceManager节点机器上,执行命令
bin/yarn rmadmin -refreshNodes
,刷新NodeManager -
在NameNode节点机器上,执行命令
sbin/start-balancer.sh
,实现集群数据的平衡(Blocks) -
此时查看NameNode的前端管理页面,在Datanodes选项页面里,应该新增了一个DataNode节点,其Admin State为Dead
-
到新机器上,分别执行命令
sbin/hadoop-daemon.sh start datanode
启动DataNode,和命令sbin/yarn-daemon.sh start nodemanager
启动NodeManager -
再回到NameNode的前端管理页面,刷新可以看到,新节点的Admin State为In Service
-
上传文件和下载文件,进行测试
【2】动态移除节点
-
① 在所有节点的
etc/hadoop
目录下,创建名为dfs.hosts.exclude
的文件,并添加要移除的节点的IP到文件中。每个IP占一行,不允许有空格② 在所有节点的
etc/hadoop/hdfs-site.xml
中,修改配置<!-- 修改 --> <property> <!-- 配置副本数 --> <name>dfs.replication</name> <value>移除机器后的DataNode的数量</value> </property> <!-- 新增 --> <property> <!-- 指定不允许连接到NameNode的主机列表。如果该值为空,则不排除任何主机。默认为空 --> <name>dfs.hosts.exclude</name> <value>/???/不允许连接到NameNode的主机列表文件</value> </property>
③ 在所有节点的
etc/hadoop/slaves
中,将准备移除的机器的IP删除 -
在NameNode节点机器上,执行命令
bin/hdfs dfsadmin -refreshNodes
,刷新DataNode -
在ResourceManager节点机器上,执行命令
bin/yarn rmadmin -refreshNodes
,刷新NodeManager -
在NameNode节点机器上,执行命令
sbin/start-balancer.sh
,实现集群数据的平衡(Blocks) -
此时查看NameNode的前端管理页面,在Datanodes选项页面里,那些准备移除的节点机器的Admin State应该为Decommission In Progress。等待数据传输给其他的DataNode完毕,状态变为Decommission
-
在准备退役的机器上,分别执行命令
sbin/yarn-daemon.sh stop nodemanager
停止NodeManager,和命令sbin/hadoop-daemon.sh stop datanode
停止DataNode -
如果在
etc/hadoop/hdfs-site.xml
中配置了dfs.hosts
,则还应删掉对应的退役机器的IP,并执行命令重新刷新DataNode和NodeManager
18、集群间的数据拷贝
使用命令,bin/hadoop distcp hdfs://源服务机器IP:9000/?/? hdfs://目标服务机器IP:9000/?/?
注意:数据拷贝的时候,会执行一个MapReduce任务,所以必须确保2个集群的相关资源(YARN等)都在正常运行中
19、数据的归档
由于NameNode启动时会加载所有的元数据到内存当中,所以过多的小文件,会占用大量的内存资源。我们可以把这些小文件归档(压缩)起来,这样就能节省很多的内存空间
数据归档本质上是一个MapReduce程序(确保YARN相关资源已启动)
相关命令:
-
创建归档文件,
bin/hadoop archive -archiveName 归档文件名.har -p 要归档的目录 归档文件输出目录
-
查看归档文件,
bin/hadoop fs -ls -R har:///???/???.har
-
提取归档文件,其实就是复制归档文件里的内容,
bin/hadoop fs -cp har:///???/归档文件名.har/归档文件 /目标路径
注意:只能复制归档文件的内容,不能移动(mv),否则会报错:
mv: `har:///???/???.har/???': Does not match target filesystem
20、快照
快照相当于是对目录进行一个备份,它并不会立即复制所有的文件,而是先创建一个索引文件指向目标文件。当有新的文件写入时,它才会产生新的文件
相关命令:
-
开启指定目录的快照功能,
bin/hdfs dfsadmin -allowSnapshot /指定目录
-
创建指定目录的快照,
bin/hdfs dfs -createSnapshot /指定目录 快照文件名称
快照文件名称可以省略,默认的名称为
/指定目录/.snapshot/s日期-时间.毫秒
-
重命名快照,
bin/hdfs dfs -renameSnapshot /快照目录 旧名称 新名称
注意:系统默认创建的快照名称是无法重命名的,会报错:
renameSnapshot: Modification on a read-only snapshot is disallowed
-
展示当前用户下的所有的已开启快照功能的目录,
bin/hdfs lsSnapshottableDir
-
比较两个快照目录的不同,
bin/hdfs snapshotDiff /开启快照功能的目录 快照名称1 快照名称2
快照名称可以使用
.
来代替,表示当前开启了快照功能的目录注意:比较结果是用右边的快照和左边的快照相比较
-
删除快照,
bin/hdfs dfs -deleteSnapshot /开启快照功能的快照目录 快照名称
注意快照目录和快照名称之间的空格
-
禁用指定目录的快照功能,
bin/hdfs dfsadmin -disallowSnapshot /指定目录
注意:禁用快照功能前,需要先删除当前目录的所有快照,否则报错:
disallowSnapshot: The directory /???/??? has snapshot(s). Please redo the operation after removing all the snapshots.
使用快照进行恢复,则使用bin/hadoop fs -cp /快照目录里的文件 /指定目录
。注意不能使用移动(mv)命令,因为快照文件是只读的
21、回收站
回收站功能默认是关闭的,需要在etc/hadoop/core-site.xml
配置文件中,设置2个参数来开启它
<!-- 回收站内的文件的有效期,单位:分钟。默认为0,表示禁用回收站功能 -->
<property>
<name>fs.trash.interval</name>
<value>0</value>
</property>
<!--
检查回收站的周期,单位:分钟。该值要小于等于fs.trash.interval。如果为0,则设置为fs.trash.interval的值。
每当回收站运行的时候,它都会创建一个新的检查点,用于删除超过fs.trash.interval时间(文件有效期)之后的文件
-->
<property>
<name>fs.trash.checkpoint.interval</name>
<value>0</value>
</property>
同时还要注意:默认拥有查看回收站的权限的用户名是dr.who
,需要修改它为操作Hadoop集群的用户。否则访问NameNode的前端管理页面时会报错:Permission denied: user=dr.who, access=READ_EXECUTE, inode="/user":???:supergroup:drwx------
在etc/hadoop/core-site.xml
中
<!-- 拥有查看HDFS Web页面(NameNode前端管理页面)的权限的用户名 -->
<property>
<name>hadoop.http.staticuser.user</name>
<value>dr.who</value>
</property>
删除的文件目录,位于回收站的目录/user/执行Hadoop的用户名/.Trash/Current/???
里
恢复回收站里的文件的方法是,将回收站里的文件移动出来,使用命令bin/hadoop fs -mv /回收站目录 /恢复目录
。注意,如果移动文件夹,可能因为权限问题而无法查看
删除回收站目录,可以使用命令bin/hadoop fs -rm -r -skipTrash /user/???
。加上-skipTrash
选项