Hadoop 中的 HDFS 是一个很成功的分布式文件系统,不少公司在搭建分布式原型系统时,都曾把它用作底层的文件系统。
可惜,HDFS 的操作需要使用专门的命令,例如 hdfs dfs -ls /
等。有没有什么办法,能把 HDFS 映射成硬盘上的文件夹、盘符,让读写 HDFS 就像读写本地文件一样简单呢?有的,那就是 Hadoop 中自带的 fuse_dfs 工具。使用该工具,就能把 HDFS 挂载到本机的文件系统上,通过普通的文件读写来操作 HDFS。
下面我就来介绍这个工具的编译和使用方法:
⚠️注意: Hadoop v2 和 v3 版本中,该工具的使用方法差异巨大。有的文章(比如这篇)介绍的是 v2 的操作步骤,已经不适用于 v3 了。
本文内容主要来自 sleeplessbeastie 的这篇博文,读者也可以参考这篇博文来使用 fuse_dfs。
编译 fuse_dfs
1、首先,你需要先安装 JDK 和 Hadoop。网上有很多安装 Hadoop 的教程,所以这不是本文要介绍的内容。安装完之后,你需要创建一个非 root 的用户,在该用户下,获取与已安装的 Hadoop 版本一致的源代码包,下载并解压:
# 获取对应版本的源码。你需要到 Hadoop 官网手动下载,获取最新的源码包链接;
wget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.4/hadoop-3.3.4-src.tar.gz
# 解压 Hadoop 源码
tar xvf hadoop-3.3.4-src.tar.gz
# 进入到解压好的文件夹中
cd hadoop-3.3.4-src
2、安装 Docker:
# 使用 Docker 官方提供的一键安装脚本
curl -fsSL https://get.docker.com -o - | sudo bash
3、进入编译环境。Hadoop 为我们准备了一个基于 Docker 的编译环境,只要运行以下脚本,它就会自动构建一个 Docker 镜像,安装好各类编译所需的包,然后进入到容器中:
./start-build-env.sh
⚠️ 这个步骤中,一定要保证当前用户不是 root,否则会报出莫名其妙的错误。
4、进入到容器后,我们会发现 ~/.m2 文件夹的所有者居然是 root,这会导致稍后的 Maven 运行错误,所以我们把它修正一下:
chown -R $(whoami):$(whoami) ~/.m2
5、进入 HOME 目录下的 hadoop 文件夹:
cd ~/hadoop
6、调用 Maven 编译 Hadoop 源码。Maven 已经在 Docker 镜像中安装好了,所以不需要我们手动安装。这个步骤可能会有些漫长,最好使用境外服务器来加速,在我的机子上第一次编译就花了 36 分钟:
mvn package -Pnative -Drequire.fuse=true -DskipTests -Dmaven.javadoc.skip=true
7、查看当前容器的 ID,并记录下来。在 Docker 中,默认情况下,主机名就是容器的 ID:
cat /etc/hostname
8、找到 fuse_dfs 可执行文件的地址:
sudo find / -name 'fuse_dfs'
在我的机器和 3.2.2 的 Hadoop 版本下,找到的地址是:
/home/ipid/hadoop/hadoop-hdfs-project/hadoop-hdfs-native-client/target/main/native/fuse-dfs/fuse_dfs
9、进入 fuse_dfs 可执行文件所在的目录,然后查看其依赖的二进制库:
ldd fuse_dfs
在我的机器上的输出是:
linux-vdso.so.1 (0x00007ffd357fa000)
libfuse.so.2 => /lib/x86_64-linux-gnu/libfuse.so.2 (0x00007f57ba209000)
libhdfs.so.0.0.0 => not found
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f57b9fea000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f57b9de2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f57b99f1000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f57b97ed000)
/lib64/ld-linux-x86-64.so.2 (0x00007f57ba652000)
可以看到,其中依赖了 libfuse.so.2 和 libhdfs.so.0.0.0 这两个库,所以在后续的步骤中我们得把它们从容器中拷出来,不然编译好的 fuse_dfs 是没法运行的。
10、找到上述两个文件所在的位置:
sudo find / -name 'libfuse.so.2'
sudo find / -name 'libhdfs.so.0.0.0'
11、退出容器:
exit
12、处理两个依赖库。对于 libfuse,注意到 fuse_dfs 依赖的库版本是 2,所以我们直接用系统自带的包管理器,安装 libfuse2 即可:
sudo apt install libfuse2
13、至于 fuse_dfs 的本体和 libhdfs 动态链接库,我们必须把它从容器里拷出来。还记得第 7 步、第 10 步记下来的容器 id 和文件地址吗?我们要把它们从相应的位置拷出来,然后修改文件权限和文件所有者:
# 创建一个文件夹,存放文件,免得太乱
mkdir fuse_dfs
cd fuse_dfs
# 把编译好的 fuse_dfs 从容器里拷出来
docker cp <容器ID>:<fuse_dfs 文件地址> ./fuse_dfs
# 把编译好的 libhdfs 从容器里拷出来
# 注意这里的 cp 命令有个 -L 的参数,这是为什么呢?参见下文~
docker cp -L <容器ID>:/home/<用户名>/hadoop/hadoop-hdfs-project/hadoop-hdfs-native-client/target/native/target/usr/local/lib/libhdfs.so.0.0.0 ./libhdfs.so.0.0.0
# 文件此时有可能不是可执行状态,需要加上可执行权限
chmod 755 ./libhdfs.so.0.0.0
这里的 cp 命令之所以有个 -L
的参数,是为了解析符号链接,把它指向的真实文件复制下来。
众所周知,Linux 中的惯例是:用只带大版本号的软链接,指向带完整文件名的动态链接库。例如,系统目录下会发现两个文件:libyaml-0.so.2
和 libyaml-0.so.2.0.6
,其中libyaml-0.so.2
是一个指向 libyaml-0.so.2.0.6
的软链接。所以,如果不带 -L
开关复制,那最后复制下来的就是一个快捷方式了。
14、运行 fuse_dfs 需要一些环境变量,因为比较麻烦,我们干脆写一个脚本来设置它们。编写一个叫 fuse_dfs_wrapper.sh
的 Shell 脚本,其内容如下:
#!/bin/bash
# 此脚本用于代替 fuse_dfs 命令,执行本脚本即相当于运行 fuse_dfs 本体
# 这里填写你的 Hadoop 安装位置
export HADOOP_HOME=/usr/local/hadoop
# 这里填写你的 JDK 安装位置
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
# 检测当前用户是不是 root,如果不是就退出并报错
if [ "$(id -u)" != "0" ]; then
echo "此脚本必须使用 root 用户运行" 1>&2
exit 1
fi
# 计算当前脚本所在的目录位置
CURRENT_SHELL_FILE_DIR=$(dirname $(readlink -f "$0"))
# 设置 LD_LIBRARY_PATH,加入脚本所在目录,
# 这样一来运行的时候,就能找到 libhdfs.so.0.0.0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CURRENT_SHELL_FILE_DIR
# 找到 Hadoop 安装目录中所有与 HDFS 相关的 Jar 包地址,并加入到 CLASSPATH 中
export CLASSPATH=.
while IFS= read -r -d '' file
do
export CLASSPATH=$CLASSPATH:$file
done < <(find ${HADOOP_HOME}/share/hadoop/{
common,hdfs} -name "*.jar" -print0)
# 把运行本脚本时传入的参数转发给 fuse_dfs
./fuse_dfs $@
之后,就能用上述脚本来代替 fuse_dfs 命令本身了。
fuse_dfs 的使用
1、在 /root 下创建一个文件夹,方便我们做映射:
sudo mkdir /root/hdfs
2、然后运行以下命令,就能把 HDFS 映射到文件系统上啦:
sudo ./fuse-dfs-wrapper.sh dfs://<NameNode 地址>:<端口号> /root/hdfs -oinitchecks
3、想关闭的时候,只要结束掉 fuse_dfs 进程,然后关闭 /root/hdfs 的映射就好了:
sudo killall fuse_dfs
sudo umount /root/hdfs
后记
怎么样?是不是很简单呢?我没有对这个方案做性能测试,不过国外的博客曾经做过测试,结论是这套方案基本能把传输速度打满网卡的带宽,至少可以作为一种备选方案。大家可以尝试一下,如有错漏或者想要补充的内容,欢迎在评论区告诉我!