问题一:如果一个文件中有 10 个数值,一行一个,并且都可以用 int 来度量。现在求 10 个数值的和
思路:
int
类型问题二:10000 个文件,每个文件 2T,文件里的内容依然是每行一个数值,求这一堆文件的所有数值的和
思路与方案:
问题三:问题二中的 10000 个 2T 的文件应该怎么分布才能让这 10000 个任务的执行效率达到最高?
思路:
问题四:数据的处理(存储和计算)是这么设计的?
答:存储和计算相互依赖。在涉及存储时必须考虑计算,反之相同
存储:HDFS;计算:MapReduce
HDFS 设计思想:把存入到 HDFS 集群的数据均匀分散的存储到整个集群中
说明:集群的配置是去全局的
案例1: 100G 数据分多少集群节点存储的比较
序号 | 集群节点数 | 切分存储块的大小 | 存储方式 | 运算所需时间(秒) |
---|---|---|---|---|
1 | 100 | 1G | 每个节点 1G 数据量 | 1 |
2 | 90 | 1G | 10 台存 2G,80 台存 1G | 2 |
3 | 90 | 512M | 20 台存 1.5G,70 台存 1G | 1.5 |
案例2: 大文件 access.log 100G 的切分方法
对于用户来说,一个文件是完整的存储到 HDFS 进来的,所以用户再去下载该文件时要的是完整的文件整体,要把所有的块合并起来且顺序不能错。块越少拼接越容易
上述案例得出:切分的块是不是越大越好?
总结:不大不小最好。不大不小:HDFS 在设计时考虑到不同的应用场景,在每个不同的应用场景中可能需要的块的大小不一样,可以自己配置。
HDFS 块的默认大小为:
让大数据能够存储到 HDFS 集群,并考虑计算的效率问题,让文件切分存储,并让这些块均匀分散的存储到整个集群中
HDFS 集群存储的使用场景:
HDFS 集群理论上可无限制的增加节点,但有上限:
问题五:HDFS 如何保障数据安全?
解决:配置多份
多份数据分布的原则:
小问题: 若集群有 3 个存储节点,但用户指定存储 4 份,则 HDFS 上最终有几份数据?3 份
结论:HDFS 集群中的任何一个节点,肯定没有完全相同的两份数据
问题六:HDFS 核心思想:分而治之,冗余备份
冗余备份的默认值:3 份。备份数量的配置文件路径:
/software/hadoop/etc/hadoop/hdfs-site.xml 更改后重启服务生效
<property>
<name>dfs.replicationname>
<value>1value>
property>
知识点1:如果节点机器性能有差异怎么均匀分散?
数据节点机器性能差异不是特别多,若某一台机器的性能比较差,可设置该机器少存一些数据。设置:
hadoop fs -setrep [-R] [-w] <numPeplicas> <path>
知识点2:block 块的大小设置多少?
默认 128M,实际生产最多 256M。若不懂就按照默认的,大部分都是按默认的
知识点3:HDFS 集群节点很多会导致什么情况
元数据信息 fsimage 很多,加载到内存中的时间越来越长
DataNode 节点多,节点保存的数据块的个数也多
知识点4:跨网络肯定有数据延迟和丢失问题
知识点5:HDFS 不适合存储小文件
原因:存储一亿个小文件,大小仅仅 1T,但要消耗 20G 左右的内存
解决方案:对小文件进行合并,或对小文件提前做处理
知识点:文件存储在硬盘上,硬盘的最小存储单位叫做 “扇区”(sector)。每个扇区存储512字节(相当于0.5KB)。操作系统读取硬盘时,不会一个个扇区的读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个 “块”(block)。这种由多个扇区组成的 “块”,是文件存储的最小单位。“块” 的大小,最常见的是 4KB,即连续 8 个 sector 组成一个 block。文件数据都存储在 “块” 中,那么很显然,我们还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种存储文件元信息的区域就叫做 iNode。
HDFS 是大数据存储的基础,几乎所有的大数据分布式存储需求都会使用到。
HDFS 被设计成使用低廉的服务器进行海量数据的存储,如何做到?分散存储
主从架构。下边三个节点的架构是最基础的,高可用会有 StandbyNameNode,用于防止 NameNode 宕机。
优点:
缺点:(不适合以下操作)
命令 | 功能 | 举例 |
---|---|---|
hadoop fs hdfs dfs |
两种方式操作 hdfs 文件的命令前缀 | |
-help | 输出这个命令参数手册 | hadoop fs -help |
-ls | 显示目录信息 | hadoop fs -ls hdfs://ip:9000/ hadoop fs -ls / |
-put | 本地文件上传至 hdfs | 把当前目录下的 a.txt 上传到 hdfs:hadoop fs -put a.txt /hdfsPath |
-get | 从 hdfs 下载文件到本地 | hadoop fs -get /a.txt localPath |
-cp | 从 hdfs 的一个路径拷贝到另一个路径 | 把 /a.txt 拷贝到 /aa 下,并更名为 a2.txthadoop fs -cp /a.txt /aa/a2.txt |
-mv | 在 hdfs 目录中移动文件 | hadoop fs -mv /a.txt /aa |
-mkdir | 创建文件夹 | hadoop fs -mkdir /b |
-rm | 删除文件或文件夹 | hadoop fs -rm -r /aa/bb |
-rmdir | 删除空目录 | hadoop fs -rmdir /aa/bb |
-moveFromLocal | 从本地剪切到 hdfs | hadoop fs -moveFromLocal /home/a.txt /aa/bb |
-moveToLocal | 从 hdfs 剪切到本地 | hadoop fs -moveToLocal /aa/bb/a.txt /home |
-copyFromLocal | 从本地文件系统中拷贝文件到 hdfs | hadoop fs -copyFromLocal ./a.txt /aa |
-copyToLocal | 从 hdfs 拷贝到本地 | hadoop fs -copyToLocal /a.txt . |
-appendToFile | 追加一个文件到已经存在的文件末尾 | hadoop fs -appendToFile ./a.txt /a.txt |
-cat | 显示文件内容 | hadoop fs -cat /aa/a.txt |
-tail | 显示一个文件的末尾 | hadoop fs -tail /aa/a.txt |
-text | 以字符形式打印一个文件的内容 | hadoop fs -text /aa/a.txt |
-chmod | 与 Linux 文件系统的用法一样,对文件设置权限 | hadoop fs -chmod 666 /aa/a.txt |
-df | 统计文件夹的大小信息 | hadoop fs -df -sh /aa/* |
-count | 统计一个指定目录下的文件节点数量 | hadoop fs -count /aa |
-setrep | 设置 hdfs 中文本的副本数量 | hadoop fs -setrep 3 /aa/a.txt |
hdfs dfsadmin -report | 查看 hdfs 集群工作状态 | Live datanodes (2) 说明有两台是正常运行的数据节点 |
HDFS 的 API 操作所需的 maven
依赖导入 pom.xml
文件的
和 之间,并等待下载完成
<dependencies>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-commonartifactId>
<version>2.7.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-clientartifactId>
<version>2.7.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-hdfsartifactId>
<version>2.7.4version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-mapreduce-client-coreartifactId>
<version>2.7.4version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>2.4.3version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<minimizeJar>trueminimizeJar>
configuration>
execution>
executions>
plugin>
plugins>
build>
<properties>
<maven.compiler.source>16maven.compiler.source>
<maven.compiler.target>16maven.compiler.target>
properties>
// FileSystem.get()
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import java.io.IOException;
public class hdfs01GetFileSystem {
public static void main(String[] args) throws IOException {
// 1. 创建Configuration对象
Configuration conf = new Configuration();
// 2. 设置文件系统类型
// 第二个参数是访问域名,做过域名解析可设置成 hdfs://hadoop0:8020
conf.set("fs.defaultFS", "hdfs://hadoop0:8020");
// 3. 获取指定文件系统
FileSystem fileSystem = FileSystem.get(conf);
// 4. 打印输出
System.out.println(fileSystem);
}}
// FileSystem.listFiles() + for循环
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class traverseFile {
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
// 1. 获取FileSystem,默认端口8020
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 调用 listFile()方法 获取根目录下所有的文件信息
RemoteIterator<LocatedFileStatus> iterator = fileSystem.listFiles(new Path("/"), true);
// 3. 遍历迭代器
while (iterator.hasNext()) {
LocatedFileStatus fileStatus = iterator.next();
// 获取文件的绝对路径:hdfs://172.16.15.100/xxx
System.out.println(fileStatus.getPath() + "===" + fileStatus.getPath().getName());
// 文件的block信息
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
System.out.println("主机为:" + host);
}}
System.out.println("block数量为:" + blockLocations.length);
}}}
hdfs://hadoop0:8020/0320/data.txt===data.txt
主机为hadoop1
主机为hadoop2
block数量为:1
hdfs://hadoop0:8020/0320/merge.txt===merge.txt
主机为hadoop2
主机为hadoop1
block数量为:1
...
// FileSystem.mkdirs()
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class hdfs03CreateFolder {
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
// 1. 获取FileSystem
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 创建文件夹
fileSystem.mkdirs(new Path("/0320"));
// 3. 关闭FileSystem
fileSystem.close();
}}
// FileSystem.copyFromLocalFile()
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class hdfs04FileUpload {
public static void main(String[] args) throws InterruptedException, IOException, URISyntaxException {
hdfs04FileUpload fileUpload = new hdfs04FileUpload();
fileUpload.FileUpload();
}
/* 定义上传文件的方法 */
public void FileUpload() throws URISyntaxException, IOException, InterruptedException {
// 1. 获取文件系统
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 上传文件
fileSystem.copyFromLocalFile(new Path("/Users/jason93/Desktop/BigData/file/data.txt"), new Path("/0320"));
// 3. 关闭FileSystem
fileSystem.close();
}}
// IOUtils.copy()
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class hdfs05FileDownload {
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
// 1. 获取FileSystem
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 获取hdfs的输入流
FSDataInputStream inputStream = fileSystem.open(new Path("/0320/data.txt"));
// 3. 获取本地文件的输出流
FileOutputStream outputStream = new FileOutputStream("/Users/jason93/Desktop/BigData/file/hdfs/dataDown.txt");
// 4. 文件的拷贝
IOUtils.copy(inputStream, outputStream);
// 5. 关闭流
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
fileSystem.close();
}}
# /Users/jason93/Desktop/BigData/file/hdfs/merge/
# data1.txt
hello,world
# data2.txt
hello,hadoop
# data3.txt
hello,hdfs
// IOUtils.copy()
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class hdfs06MergeFileUpload {
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
// 1. 获取FileSystem
FileSystem fileSystem =
FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 获取hdfs大文件的输出流
FSDataOutputStream fsDataOutputStream =
fileSystem.create(new Path("/0320/hdfs/merge.txt"));
// 3. 获取一个本地文件系统
LocalFileSystem localFileSystem = FileSystem.getLocal(new Configuration());
// 4. 获取本地文件夹下所有文件的详情
FileStatus[] fileStatuses = localFileSystem.listStatus(new Path("/Users/jason93/Desktop/BigData/file/hdfs/merge"));
// 5. 遍历每个文件,获取每个文件的输入流
for (FileStatus fileStatus : fileStatuses) {
FSDataInputStream fsDataInputStream = localFileSystem.open(fileStatus.getPath());
// 6. 将小文件的内容复制到大文件
IOUtils.copy(fsDataInputStream, fsDataOutputStream);
IOUtils.closeQuietly(fsDataInputStream);
}
// 7. 关闭流
IOUtils.closeQuietly(fsDataOutputStream);
localFileSystem.close();
fileSystem.close();
}}
方式一:通过命令行方式
# 合并指定目录下的所有文件
hadoop fs -getmerge /0320/hdfs/* /home/data/hdfs/mergeDown.txt
# 合并目录下的指定文件也可以(相对路径)
hadoop fs -getmerge /0320/hdfs/data1.txt /0320/hdfs/data3.txt mergeDown13.txt
# 查看结果:
[root@hadoop0 hdfs]# ls
mergeDown13.txt mergeDown.txt merge.txt
[root@hadoop0 hdfs]# cat mergeDown.txt
hello,world
hello,hadoop
hello,hdfs
[root@hadoop0 hdfs]# cat mergeDown13.txt
hello,world
hello,hdfs
方式二:通过 Java API方式
// IOUtils.copy()
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class hdfs07MergeFileDownload {
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
// 1. 获取FileSystem
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop0:8020"), new Configuration(), "root");
// 2. 获取一个本地文件系统
LocalFileSystem localFileSystem = FileSystem.getLocal(new Configuration());
// 3. 获取本地大文件的输出流
FSDataOutputStream outputStream = localFileSystem.create(new Path("/Users/jason93/Desktop/BigData/file/hdfs/mergeDown.txt"), true);
// 4. 获取hdfs下的所有小文件
RemoteIterator<LocatedFileStatus> listFiles = fileSystem.listFiles(new Path("/0320/hdfs"), true);
// 5. 遍历
while (listFiles.hasNext()) {
LocatedFileStatus locatedFileStatus = listFiles.next();
FSDataInputStream inputStream = fileSystem.open(locatedFileStatus.getPath());
// 6. 将小文件复制到大文件中
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
}
// 7. 关闭流
IOUtils.closeQuietly(outputStream);
localFileSystem.close();
fileSystem.close();
}}
信息说明:
异常情况: HDFS 在读取文件时,如果其中一个块突然坏掉了怎么办?
具体步骤:
Client 发送写数据请求
NameNode 响应请求,然后做一系列校验,如果能上传该数据则返回该文件的所有切块应该被存放在哪些 DataNode 上的 DataNode 列表
block-001: hadoop2 hadoop3
block-002: hadoop3 hadoop4
Client 拿到 DataNode 列表后,开始传数据
首先传第一个block-001,DataNode 列表就是 hadoop2 和 hadoop3,Client 就把 block-001 传到 hadoop2 和 hadoop3 上
以此类推,用传第一个数据块的方式传其他的数据
当所有的数据块都传完后,Client 会给 NameNode 返回一个状态信息,表示数据已全部写入成功,或者失败
NameNode 接收到 Client 返回的状态信息来判断当次写入数据的请求是否成功,若成功则更新元数据信息
异常情况:
场景一:HDFS 在上传文件时,若其中一个 DataNode 突然挂掉了怎么办?
场景二:HDFS 向 DataNode 写入数据失败怎么办?(上传 100MB 的文件,上传到 50MB,突然断了,或 block 由于网络等原因异常了,HDFS 会怎么处理?)
HDFS 三大核心机制:心跳机制、安全模式、副本存放策略
Hadoop 是 Master/Slave 架构,Master 中有 NameNode 和 ResourceManager,Slave 中有 DataNode 和 NodeManager。
【心跳机制】:DataNode 每隔一段时间(默认 3 秒)就会跟 NameNode 取得一次联系,从而证明自己还活着,让 NameNode 能够识别到当前集群中有多少存活的节点。
NameNode 判断 DataNode 是否宕机需要一个标准:超时
timeout(超时时长) = 10 * 心跳时长(3秒) + 2 * 检测心跳是否正常工作的间隔(5分钟)
超时时间可在 hdfs-site.xml 文件中配置 dfs.heartbeat.interval
参数,或使用 Zookeeper 做一个监控,有节点宕机可迅速感知。
心跳机制分两个方面:
心跳机制作用:
心跳数据包:
在正常的启动范围内,HDFS 集群会进入安全模式,无法对外提供服务。安全模式下,客户端不能对任何数据进行操作,只能查看元数据信息。
进入安全模式的场景:
0.1%
时会进入安全模式
dfs.safemode.threshold.pct=0.999f
dfs.namenode.safemode.threshold-pct=0.999f
若要强制对外提供服务,可使用HDFS命令操作:
hdfs dfsadmin -safemode leave # 退出安全模式
hdfs dfsadmin -safemode enter # 进入安全模式
hdfs dfsadmin -safemode get # 获取安全模式状态
hdfs dfsadmin -safemode wait # 等待
hdfs dfsadmin -safemode leave
说明:
决定一个数据块的那几个副本(默认是 3)到底该存储到哪些服务器上
原则:
副本存放策略:
策略:
策略是一个参考,不是硬性标准。所以实际选取存储空间大、不忙的节点
**方法:**将每个文件的数据分块存储,每一个数据块又保存多个副本,这些数据块副本分布在不同的机器节点上
作用:数据分块存储和副本存放,是保证可靠性和高性能的关键
重点:组件的职责、元数据
如何管理元数据? 使用 WAL(Write-Ahead Logging)预写日志系统
WAL:数据库中一种高效的日志算法,对于非内存数据库而言,磁盘 I/O 操作是数据库效率的一大瓶颈。在相同的数据量下,采用 WAL 日志的数据库系统在事务提交时,磁盘写操作只有传统的回滚日志的一半左右,大大提高了数据库磁盘 I/O 操作的效率,从而提高了数据库的性能。
说明:MySQL 实现了 WAL,所有的事务操作都会记录日志,若某张表的数据丢失后,可根据该日志拿到对应数据,对表进行恢复
元数据信息的位置:${HADOOP_HOME}/data/namenode/current/
。示例如下:
相关说明:
(1)edits_inprogress_000… 文件:它是时刻操作的文件,按一定时间或一定大小(不同版本有差异)分割为若干 edits_000… 文件
# edits 文件:
hdfs oev -i edits_0000000000000013664-0000000000000013665 -o edits.xml
cat edits.xml
# fsimage 文件:
hdfs oiv -i fsimage_0000000000000013725 -p XML -o fsimage.xml
cat fsimage.xml
(2)seen_txid:存放 edits_inprogress_00… 日志最新的 id(存放 transactionId 的文件),比如 edits_inprogress_00xxx0013728,则 (3)seen_txid 为13728。format 之后是 0
(4)VERSION:存放 HDFS 集群的版本信息
(5)fsimage_000xxx.md5:校验性文件
NameNode 元数据存储机制:
元数据合并的好处【面试点】
元数据的 Checkpoint: 每隔一段时间,会有 SecondaryNameNode 将 NameNode 上积累的所有 edits 和一个最新的 fsimage 下载到本地,并加载到内存中进行 merge(合并),该过程称为 Checkpoint。
数据块的两个参数: 块的大小、副本的个数
data 数据的存放目录:
${HADOOP_HOME}/data/datanode/current/BP-1365453085-172.16.15.103-1646548673937/current/finalized/subdir0/
例1:一个集群有 500 个节点,现增加 10 个节点。HDFS 如何表现?
解决数据倾斜的方法:负载均衡
例2:一个集群 500 个节点,现减少 10 个节点,这 10 个节点上的数据块信息丢失。HDFS 如何表现?
知识点:
异地灾备
,可以从异地机房做数据恢复**职责:**分担 NameNode 合并元数据信息(镜像文件和操作日志)的压力
**注意:**SecondaryNameNode 不要和 NameNode 配置在一个节点上
说明:
工作机制:
集群要对外提供服务,首先要保证 NameNode 正常,不能宕机。因为企业一般都 7*24 小时不间断提供服务。保证 NameNode 实时提供服务而不宕机的机制:HA(High Available)高可用
SPOF(Single Point Of Failure)单点故障,是主从架构存在的通性问题
单点故障具体解决方案:做备份
为防止 Active 的 NameNode 宕机,在旁边准备一台 Standby 节点。假设 Active 的 NameNode 节点是 Hadoop0,Standby 的 NameNode 节点是 Hadoop4,若 Hadoop0 宕机了,Hadoop4 会代替它运行。
HDFS 高可用功能,用配置过 Active/Standby 两个 NameNode 实现在集群中对 NameNode 的热备份来解决 NameNode 机器宕机或软硬件升级导致集群无法使用的问题。
元数据信息在 NameNode 节点 Hadoop0 中,当它宕机后,Hadoop4 要迅速取代 Hadoop0,也就意味着 Hadoop4 和 Hadoop0 要存储一模一样的元数据信息,即 Hadoop4 是 Hadoop0 的一个热备份。【重要】
不管 Active 节点做了什么操作,Standby 节点都要 时刻保持同步。
保持同步的方法:创建 JournalNode 集群,NameNode(Active)写入该集群,NameNode(Standby)从该集群中读取。JournalNode 集群的各个节点跟 Zoopeeper 集群类似,每个节点都有可能成为主节点,因此不存在单点故障。至于区分 Active 和 Standby,由 Zookeeper 集群的文件目录树决定。该目录树是一个 LOCK
,两个 NameNode 谁先抢到谁就是 Active。
为了保险起见,设置多个 Standby 是否可以?可以,但有条件,也不建议特别多,个位数。
多主多从:主节点是一个小集群,从节点也是一个集群(比如 Kudu)
元数据信息加载到内存中,有可能内存放不下,导致 内存受限。解决内存受限问题:联邦
HDFS Federation:指 HDFS 集群可同时存在多个 NameNode,包含多组 HA,每组 HA 中各 NameNode 存储相同元数据,元数据分多份,均分到各组 HA 的 NameNode中。
这种设计可解决单 NameNode 存在的以下问题:
HDFS Federation方案: