HDFS 分布式文件系统的搭建与使用

HDFS(Hadoop Distributed File System) 是 Hadoop 的一个重要的模块,有点像磁盘阵列一样,不过它构建的是分布式网络文件系统。由于数据块从多个节上存取,也就能突破单点的网络带宽和硬件资源的限制而获得更好的性能; 能处理更大的数据,和克服单点故障的问题。许多公司正在使用 HDFS 构建自己的分布式文件系统,还比支持它的应用有 Spark, Presto, Hive, HBase, Zeppelin 等。

本文将实战自己搭建一个 HDFS 分布式文件系统,体验最基本的 HDFS 文件操作,看看它是如何分布文件块,以及如何进行冗余容错的。

本次实战环境:

  1. macOS Big Sur 11.7, VirtualBox 6.1.32 r149290, Vagrant 2.2.19
  2. Vagrant Ubuntu 22.04 LTS 虚拟机
  3. Open JDK 8
  4. Hadoop 3.3.4

我们将使用 4 个 Vagrant 虚拟机,其中一个为 NameNode, 其余为 DataNode。HDFS 沿袭了传统的 Master/Slave 系统架构,但因目前像传统的计算机名词 PC, CRT 被恶意使用的当下,Master/Slave 相应的更名为 NameNode 和 DataNode。在通常的系统中, Master 兼具协调与数据存储的功能,而 Slave 只存储数据,而 HDFS 的 NameNode 仅保管文件的元信息,数据块存储在 DataNode 中。

我们在虚拟机中的所采用的系统为 Ubuntu 22.04 LTS 版, 它们全部定义在同一个 Vagrantfile 文件中,使用固定的私有 IP 地址。所以虚拟机之间,虚拟机与宿主机之间是互通的,我们从宿主机发起的访问即模拟了远程访问 HDFS 集群的效果。

注:关于 Vagrant 的日常基本操作请参考 Vagrant 简介与常用操作及配置。另外,HDFS 还可配置更多功能的 Secondary NameNode, Checkpoint Node, Backup Node, 这些是对 HDFS 集群的增强,不在本文的研究范围。我们本着不提供傻瓜教程,不一次做对的原则,凭着直觉去蹚浑水才能积累到真正属于自己的实战经验。

所以虚拟机启动后所有的系统如下

IP Hostname
192.168.56.100 hdfs-nn01
192.168.56.101 hdfs-dn01
192.168.56.102 hdfs-dn02
192.168.56.103 hdfs-dn03

Vagrantfile 文件内容

然后启动所有的虚拟机

vagrant up

查看虚拟机状态

这时候从宿主机上也可 ping 通以上四个虚拟机的 IP 地址 192.168.56.xxx。需要 ssh  进入某一个虚拟机的话,只要运行命令 vagrant ssh <name>, 如要进入 hdfs-nn01 虚拟机,命令是

vagrant ssh hdfs-nn01

以上的 /Users/yanbin/Workspaces/vagrant 是用来在宿主机与虚拟机之间共享文件的目录,它映射到虚拟机的 /vagrant 目录,所以以上四个虚拟机的 /vagrant 都是指向宿主机的同一个目录。

Hadoop 当前(2022-10-08)版本为 3.3.4, 于 2022 年  8 月 8 日发布,源码需用 Java 8 来编译,但编译后可运行在 Java 11 下。官方的 Hadoop Java Versions 中说的是 Apache Hadoop 3.3 and upper supports Java 8 and Java 11 (runtime only), 目前有一个 Ticket 是关于支持用 Java 11 编译 Hadoop 3.3 的。所以最后还是选择用 Java 8。

从 hdfs-nn01 节点开始

先以 hdfs-nn01 为起点逐步配置,启动一个 NameNode,再尝试在 hdfs-nn01 起动 DataNode,再体验 HDFS 文件的存取。然后再渐进的使用,配置,添加真正的 DataNode,并查看文件块是如何分布的。

因此我们先 ssh 登陆 hdfs-nn01,安装 JDK  和 Hadoop

$ vagrant ssh hdfs-nn01

进到 hdfs-nn01 虚拟机的 shell, 以下命令在 hdfs-nn01 虚拟机中执行


vagrant@hdfs-nn01:~$ sudo apt update
vagrant@hdfs-nn01:~$ sudo apt install openjdk-8-jdk -y
vagrant@hdfs-nn01:~$ wget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.4/hadoop-3.3.4.tar.gz
vagrant@hdfs-nn01:~$ sudo tar xzvf hadoop-3.3.4.tar.gz -C /opt
vagrant@hdfs-nn01:~$ sudo mkdir -p /data/hadoop
vagrant@hdfs-nn01:~$ sudo chown -R vagrant:vagrant /data/hadoop /opt/hadoop-3.3.4

编辑 hdfs-nn01 中的 /etc/profile 文件,并加上以下两行

export PATH=/opt/hadoop-3.3.4/bin:$PATH

source /etc/profile 或重新用 vagrant ssh 登陆后就能使用 hdfs 命令了。

除了会用到 $HADOOP_HOME/bin 下的命令外,在 $HADOOP_HOME/sbin 目录中还有许多有用的脚本,比如启动单机的 HDFS 只要在格式化 HDFS 文件系统后执行 $HADOOP_HOME/sbin/start-dfs.sh 命令,还包括 yarn 等的操作。

如果总是用 vagrant 用户来启动, hadoop 的 PATH 也可以配置在用户目录中的 .bashrc 或 .bash_profile 当中

编辑  /opt/hadoop-3.3.4/etc/hadoop/hadoop-env.sh, 添加导出 JAVA_HOME 的代码

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

JAVA_HOME 也可以像前面 PATH  一样配置在全局或用户的 profile 文件中。还可以配置一个辅助的 HADOOP_HOME 指向 /opt/hadoop-3.3.4 目录,我们随后用 $HADOOP_HOME 指代该实际的目录。

配置 core-site.xml 文件

编辑文件 $HADOOP_HOME/etc/hadoop/core-site.xml, 内容如下

该文件默认为空的 <configuration> 内容,这里我们配置 fs.defaultFS 先用 IP 地址,后面加入 DataNode 时必须用 hostname,随后再说明。


注:以上水平线之间的操作在后面的每个 DataNode 节点中都是相同的

在 $HADOOP_HOME/etc/hadoop/hdfs-site.xml 中可配置每个文件块副本的个数(默认为 3), 和相应的 Secondary NameNode 的地址。Secondary NameNode 节点的作用并非 NameNode 的镜像,而是协助 NameNode 完成 fsimage 和 edit logs 的合并操作。

然后执行

vagrant@hdfs-nn01:~$ hdfs namenode -format hdfs-cluster
vagrant@hdfs-nn01:~$ hdfs --daemon start namenode
WARNING: /opt/hadoop-3.3.4/logs does not exist. Creating.

第一次启动 namenode 或 datanode 都会提示创建日志目录,它同时告诉了我们日志信息要去哪里查看,只要感觉到哪里不对劲就到  /opt/hadoop-3.3.4/logs 目录下去查日志。

查看启动的进程与端口号(需先用 sudo apt install net-tools 安装才能使用 netstat 命令)

9870 是 web-ui 的端口号,用 http://192.168.56.100:9870 就能在浏览器中打开管理界面, 8020 用于标识一个 hdfs 文件系统。打开 http://192.168.56.100:9870/dfshealth.html#tab-datanode

现在还没有 DataNode,现在 hdhs-nn01 节点上尝试一下 hdfs dfs 命令

vagrant@hdfs-nn01:~$ hdfs dfs -df
Filesystem Size Used Available Use%
hdfs://192.168.56.100:8020 0 0 0 NaN%
vagrant@hdfs-nn01:~$ hdfs dfs -mkdir -p /user/vagrant
vagrant@hdfs-nn01:~$ echo "Hello HDFS" > test.txt
vagrant@hdfs-nn01:~$ hdfs dfs -put test.txt /user/vagrant
2022-10-10 02:11:24,282 WARN hdfs.DataStreamer: DataStreamer Exception
org.apache.hadoop.ipc.RemoteException(java.io.IOException): File /user/vagrant/test.txt._COPYING_ could only be written to 0 of the 1 minReplication nodes. There are 0 datanode(s) running and 0 node(s) are excluded in this operation.
    at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.chooseTarget4NewBlock(BlockManager.java:2315)
    at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.chooseTargetForNewBlock(FSDirWriteFileOp.java:294)
    at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getAdditionalBlock(FSNamesystem.java:2960)
    .......

在只有 NameNode 的情况下可以创建目录,但无法存储文件。这也就证明了文件元信息是存储在 NameNode 中,数据块存在别处。我们随后把 /user/vagrant 目录删除掉

vagrant@hdfs-nn01:~$ hdfs dfs -rm -R /user
Deleted /user

在 hdfs-nn01 节点中直接启动 datanode, 在不涉到其他节点的情况下实现一个伪集群的 HDFS, 命令如下

vagrant@hdfs-nn01:~$ hdfs --daemon start datanode

再查看启动的里程与端口号

这时候浏览器中访问 http://192.168.56.100:9870/dfshealth.html#tab-datanode, 将能看到在 master 节点时同时启动的 datanode

也可以访问 DataNode 的 web-ui 界面,这里显示的是 http://hdfs-master:9864(这是第一个要使用 hostname 的地方), 但由于我们未配置 DNS 或 /etc/hosts 文件,所以需要用 IP 地址来访问,如 http://192.168.56.100:9864/, 看到的是 

现在作为一个单节点的 HDFS 系统,我们可以上传文件,可用命令或 web-ui http://192.168.56.100:9870/explorer.html#, 界面如下

目前为空。本文我们用基本的 hdfs dfs 命令来操作。hdfs dfs 后面的参数基本就是我们在 Linux shell 的常用命令前加个 - 参数化,如

  1. hdfs dfs -rm: 删除一个目录
  2. hdfs dfs -ls: 显示目录中的内容
  3. hdfs dfs -mkdir: 创建目录
  4. hdfs dfs -rmdir: 删除目录
  5. hdfs dfs -put: 上传本地文件或目录到 HDFS 文件系统中
  6. hdfs dfs -get: 从 HDFS 文件系统中下载文件或目录到本地
  7. hdfs -df: 显示磁盘空间 

等等,hdfs dfs 能显示出所有支持的命令,命令的参数也可以参考 Linux shell 相应命令的参数,如  hdfs dfs -df -h, hdfs dfs -mkdir -r /a/b/c 等

hdfs dfs 操作的起始目录是 /user/<yourName>, 不然会出现下面的错误

vagrant@hdfs-nn01:~$ hdfs dfs -ls .
ls: .': No such file or directory
vagrant@hdfs-nn01:~$ hdfs dfs -mkdir a
mkdir:
hdfs://192.168.56.100:8020/user/vagrant': No such file or directory

这就是为什么我们前面测试在没有 DataNode 节点的情况下先创建了 /user/vagrant 目录的缘故。

因此我们需先创建好 /user/<yourName> 目录,用 whoami 命令找到 Vagrant 虚拟机中当前用户名是 vagrant, 所以先执行

vagrant@hdfs-nn01:~$ hdfs dfs -mkdir -p /user/vagrant
vagrant@hdfs-nn01:~$ hdfs dfs -ls .
vagrant@hdfs-nn01:~$

这样就没问题了,现在来创建一目录,再上传一个文件

vagrant@hdfs-nn01:~$ hdfs dfs -mkdir testFolder
vagrant@hdfs-nn01:~$ echo "Hello HDFS" > test.txt
vagrant@hdfs-nn01:~$ hdfs dfs -put test.txt testFolder/
vagrant@hdfs-nn01:~$ hdfs dfs -ls testFolder
Found 1 items
-rw-r--r-- 3 vagrant supergroup 11 2022-10-09 04:02 testFolder/test.txt
vagrant@hdfs-nn01:~$ hdfs dfs -cat testFolder/test.txt
Hello HDFS

这里我们是在 hdfs-nn01 节点的 shell 操作的,能把文件上传到 HDFS 文件系统中,那么 hdfs dfs 是如何知道与哪个 HDFS 集群交互呢?

注意到我们执行 hdfs dfs 显示出来的帮助后面部分有

还记得我们之前在 $HADOOP_HOME/etc/hadoop/core-site.xml 中配置了 fs.defaultFS 吗?因而在 master 节点中默认就是对 hdfs://192.168.56.100:8020 文件系统进行的操作。

明白了如何指定远程 HDFS 集群后,我们就能够在任意安装了 Hadoop 的机器上执行 hdfs dfs 命令来存取 HDFS 文件系统中的数据了。下面我在自己的 macOS 上下载了 hadoop-3.3.4 后,执行 hdfs dfs 命令的效果

➜ ~ export HADOOP_USER_NAME=vagrant
➜ ~ hdfs --loglevel ERROR dfs -fs hdfs://192.168.56.100:8020 -ls testFolder
Found 1 items
-rw-r--r-- 3 vagrant supergroup 11 2022-10-09 21:33 testFolder/test.txt

配置 HADOOP_USER_NAME=vagrant 就能使用 hdfs dfs 命令访问到先前在 master 节点中上传到 /user/vagrant 目录中的文件,否则会试图访问 /user/<whoami> 目录, 在 macOS 下 whoami 不再是 vagrant 了。

试着在 hdfs-nn01 节点中停掉 datanode, 命令

vagrant@hdfs-master:~$ hdfs --daemon stop datanode

然后试图显示文件列表与内容

➜ ~ hdfs --loglevel ERROR dfs -fs hdfs://192.168.56.100:8020 -ls testFolder
Found 1 items
-rw-r--r-- 3 vagrant supergroup 11 2022-10-09 21:33 testFolder/test.txt
➜ ~ hdfs --loglevel ERROR dfs -fs hdfs://192.168.56.100:8020 -cat testFolder/test.txt
cat: Could not obtain block: BP-267695701-127.0.2.1-1665367434904:blk_1073741825_1001 file=/user/vagrant/testFolder/test.txt No live nodes contain current block Block locations: DatanodeInfoWithStorage[192.168.56.100:9866,DS-96a93755-07c8-4ec9-a171-ea0e77c51335,DISK] Dead nodes: DatanodeInfoWithStorage[192.168.56.100:9866,DS-96a93755-07c8-4ec9-a171-ea0e77c51335,DISK]

显示文件列表是没问题,证明文件元信息是保存在 NameNode 上的,而数据内容是在 DataNode 上的。重新启动 master 节点上的 DataNode 后又可以看到文件的内容

加入一个实际的 DataNode

在进入该步测试之前,先对当前的 HDFS 集群作一个清理,在 hdfs-nn01 节点上运行如下命令

vagrant@hdfs-nn01:~$ hdfs --daemon stop datanode
vagrant@hdfs-nn01:~$ rm -R /data/hadoop/*                                # 可选
vagrant@hdfs-nn01:~$ hdfs namenode -format hdfs-cluster
vagrant@hdfs-nn01:~$ hdfs --daemon start namenode

这样能确保有 HDFS 文件系统是干净的,而且在 HDFS 集群中也不再有死了的 DataNode

进到第一个 DataNode 节点,现在用 vagrant ssh hdfs-dn01 进到该节点的 shell,与在 hdfs-nn01 上节点的相同的基本操作包括

  1. 安装 JDK 8, 下载解压 Hadoop
  2. 配置 JAVA_HOME 和指向到 /opt/hadoop-3.3.4/bin 目录的 PATH 环境变量
  3. 创建目录 /data/hadoop 和修改 /data/hadoop 和 /opt/hadoop-3.3.4 的目录所有者
  4. 配置 core0-site.xml 文件

现在尝试在 hdfs-dn01 节点上启动 DataNode

vagrant@hdfs-dn01:~$ hdfs --daemon start datanode

这时候用 netstat 查看到的 Java 进程

但是在 http://192.168.56.100:9870/dfshealth.html#tab-datanode 没有显示出任何 DataNode 信息。

这时候就要查看日志文件了,打开

/opt/hadoop-3.3.4/logs/hadoop-vagrant-datanode-hdfs-dn01.log

看到类似下方的错误信息

2022-10-10 03:14:58,097 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: Initialization failed for Block pool BP-757515962-127.0.2.1-1665370138516 (Datanode Uuid bd84394d-4384-4d81-82b8-727baa03cb94) service to /192.168.56.100:8020 Datanode denied communication with namenode because hostname cannot be resolved (ip=192.168.56.101, hostname=192.168.56.101): DatanodeRegistration(0.0.0.0:9866, datanodeUuid=bd84394d-4384-4d81-82b8-727baa03cb94, infoPort=9864, infoSecurePort=0, ipcPort=9867, storageInfo=lv=-57;cid=CID-a1f2d5ae-b46f-4b96-b0e1-22b518f89676;nsid=643369223;c=1665370138516)
    at org.apache.hadoop.hdfs.server.blockmanagement.DatanodeManager.registerDatanode(DatanodeManager.java:1147)
    at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.registerDatanode(BlockManager.java:2566)
    at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.registerDatanode(FSNamesystem.java:4235)

原来是 NameNode hdfs-nn01 中无法解析 IP 192.168.56.101 成 hostname, 所以要让 hdfs-dn01 作为 datanode 加入到 namenode 中去的话,必须让 NameNode 能反向解析出该 DataNode 的 IP 地址,于是只要在 hdfs-nn01 节点的 /etc/hosts 中加上一条

192.168.56.101     hdfs-dn01

再回到 hdfs-dn01 上重启 DataNode, 需执行的命令是

vagrant@hdfs-dn01:~$ hdfs --daemon stop datanode
vagrant@hdfs-dn01:~$ hdfs --daemon start datanode

再查看 http://192.168.56.100:9870/dfshealth.html#tab-datanode,就有了 hdfs-dn01 这个数据节点了

观察数据在集群中的分布

目前有了一个接近实际意义上的 HDFS 集群 -- 一个 NameNode 和一个 DataNode, 接下来上传一个文件看它的数据块是如何在 HDFS 的 DataNode 中分布的,其间会往集群中逐步添加更多的数据节点。

我们在 macOS 宿主机下对 HDFS 进行远程操作,先把 hdfs dfs 命令简化一下

修改 $HADOOP/etc/hadoop/log4j.properties, 添加一行

log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR

把执行 hdfs 时的

WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

警告信息去掉,也不用加 --loglevel ERROR 参数。因为从官方下载的 Hadoop 二进制版本地库是 32 位的,而  brew install hadoop 安装的 Hadoop 不带本地库,除非下载源码自行编译。

再修改 $HADOOP/etc/hadoop/core-site.xml,改成

检查一下是否连接

➜ ~ hdfs dfs -df
Filesystem Size Used Available Use%
hdfs://192.168.56.100:8020 41555521536 32768 37984940032 0%

上传一个测试文件

➜ ~ export HADOOP_USER_NAME=vagrant
➜ ~ hdfs dfs -mkdir -p /user/vagrant
➜ ~ hdfs dfs -mkdir testFolder
➜ ~ echo "Hello HDFS" > test.txt
➜ ~ hdfs dfs -put test.txt testFolder/
➜ ~ hdfs dfs -cat testFolder/test.txt
Hello HDFS

查看文件信息 http://192.168.56.100:9870/explorer.html#/user/vagrant/testFolder

查看 hdfs-dn01 上的数据文件信息

vagrant@hdfs-dn01:~$ cat /data/hadoop/dfs/data/current/BP-757515962-127.0.2.1-1665370138516/current/finalized/subdir0/subdir0/blk_1073741825
Hello HDFS

在 hdfs-nn01 上没有  /data/hadoop/dfs/data 目录,因为它不存数据

再上一个 DataNode hdfs-dn02, 还是相同的操作与配置,还要在 hdfs-nn01 上的 /etc/hosts 中加一条

192.168.56.102  hdfs-dn02

再用命令 hdfs dfs --daemon start datanode 启动 datanode 就行

现在上来两个 DataNode 了

数据分布到了两个节点上了

在 hdfs-dn02 上查看文件

vagrant@hdfs-dn02:~$ cat /data/hadoop/dfs/data/current/BP-757515962-127.0.2.1-1665370138516/current/finalized/subdir0/subdir0/blk_1073741825
Hello HDFS

再上一个 hdfs-dn03 节点

文件信息

数据块分布到了三个节点

vagrant@hdfs-dn03:~$ cat /data/hadoop/dfs/data/current/BP-757515962-127.0.2.1-1665370138516/current/finalized/subdir0/subdir0/blk_1073741825
Hello HDFS

仍然看不出数据在冗余到什么程度,还得来一个大杀器 -- 再加一个 hdfs-dn04 数据节点

文件内容分布

终于不再扩散,默认的冗余存储方式是三份

再创建一个新文件

➜ ~ echo "Hello Test1" > test1.txt
➜ ~ hdfs dfs -put test1.txt testFolder/

数据分布

分布在不同的三个数据节点上

测试一个大文件,  也不用太大,1.2 GB 的 CSV 文件

上传完后

数据块大小为 128M, 数据分布在三个数据节点上,而实际上在所有的四他数据节点上都占用了 2-3 百M的数据大小,似乎每个节点上都有 big_file.csv 的数据文件

四个节点上都有许多的数据块文件,每个数据块大小限制为 128M。文件信息中说的在 hdfs-dn04 上没有数据,那就专门窥探一下 hdfs-dn04 的数据目录

这是一个 csv 纯文本文件,所以可以直接查看一下它在 hdfs-dn04 节点中的数据块文件的内容

cat /data/hadoop/dfs/data/current/BP-757515962-127.0.2.1-1665370138516/current/finalized/subdir0/subdir0/blk_1073741868
.........

显示的又确实是 big_file.csv 文件的内容, 这该如何作出解释呢?实际存储与上面显示的文件信息有点不相符,我们来查看每个 DataNode 的 /data/hadoop/dfs/data/current/BP-757515962-127.0.2.1-1665370138516/current/finalized/subdir0/subdir0 目录来看数据块的分布

128M 的数据块是属于大文件的,其中

数据块 hdfs-dn01 hdfs-dn02 hdfs-dn03 hdfs-dn04
blk_1073741867 ✔️ ✔️ ✔️  
blk_1073741868   ✔️ ✔️ ✔️
blk_1073741869 ✔️   ✔️ ✔️

这涉及到文件块分布的负载均衡的算法,从现在 4 个数据节点,副本数为 3 的情况,从任何两个数据节点都能拼凑出完整的 big_file.csv 文件,换句话说就是数据节点坏一,坏两个都没问题,文件还能正确读取出来,这就是 HDFS 的容错性。

Hadoop HDFS - Hadoop Distributed File System 一文中找了一张图

HDFS 的均衡策略和副本个数实现了磁盘阵列的效果,他们实际用途也差不多。

如果在 HDFS 集群中缩减了 DataNode 的数目,先前该节点上的数据应该会转移到别的节点上去。后又扩充了更多的 DataNode, 基于访问性能的考虑,HDFS 应该会优化数据块的分布,对数据块进行 Rebalance 操作移动数据。

关于 /etc/hosts  在 Vagrant 中有更好的解决办法

vagrant plugin install vagrant-hostsupdater 可以使用 host 的 /etc/hosts  中的配置,并且每个  vagrant 虚拟机都会向 /etc/hosts 中注册自己,这样就不需要在 NameNode 中编辑 /etc/hosts 文件。

另外,刚看到一个  JuiceFS 文件系统也很好玩,比如挂载一个 redis 缓存,以文件系统的方式来访问

$ juicefs format redis://your-redis-host:6379/1 myjfs
$ juicefs mount -d redis://your-redis-host:6379/1 /mnt/juicefs
$ cp -r ~/dataset /mnt/juicefs/

HDFS 文件系统也是有办法 mount 到本地目录中来,其后只要以操作本地文件的方式来操作 HDFS 中的文件,比如用 NFS gateway 的方式,NFS -> FUSE -> HDFS。

链接:

  1. Hadoop Distributed File Sytem(HDFS)
  2. 快速搭建 HDFS 系统 (超详细版)
  3. Secondary NameNode 的作用
  4. Hadoop之SecondaryNameNode
  5. Hadoop - 彻底解决 WARN util.NativeCodeLoader: Unable to load native-hadoop library...
  6. Hadoop: Setting up a Single Node Cluster.
  7. Hadoop Cluster Setup

本文链接 https://yanbin.blog/hdfs-setup-usage/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments