在 Hadoop 生态使用 JuiceFS 存储

JuiceFS 提供与 HDFS 接口高度兼容的 Java 客户端,Hadoop 生态中的各种应用都可以在不改变代码的情况下,平滑地使用 JuiceFS 存储数据。

环境要求

1. Hadoop 及相关组件

JuiceFS Hadoop Java SDK 同时兼容 Hadoop 2.x、Hadoop 3.x,以及 Hadoop 生态中的各种主流组件。

2. 用户权限

JuiceFS 默认使用本地的 用户UID 映射,在分布式环境下使用时,为了避免权限问题,请参考文档将需要使用的 用户UID 同步到所有 Hadoop 节点。也可以通过定义一个全局的用户和用户组文件给集群共享读取,查看详情

3. 文件系统

通过 JuiceFS Java 客户端为 Hadoop 生态提供存储,需要提前创建 JuiceFS 文件系统。部署 Java 客户端时,在配置文件中指定已创建文件系统的元数据引擎地址。

创建文件系统可以参考 JuiceFS 快速上手指南

注意

如果要在分布式环境中使用 JuiceFS,创建文件系统时,请合理规划要使用的对象存储和数据库,确保它们可以被每个集群节点正常访问。

4. 内存资源

JuiceFS Hadoop Java SDK 最多需要额外使用 4 * juicefs.memory-size 的 off-heap 内存用来加速读写性能,默认情况下,最多需要额外 1.2GB 内存(取决于写入负载)。

客户端编译

注意

不论为哪个系统环境编译客户端,编译后的 JAR 文件都为相同的名称,且只能部署在匹配的系统环境中,例如在 Linux 中编译则只能用于 Linux 环境。另外,由于编译的包依赖 glibc,建议尽量使用低版本的系统进行编译,这样可以获得更好的兼容性。

编译依赖以下工具:

Linux 和 macOS

克隆仓库:

  1. $ git clone https://github.com/juicedata/juicefs.git

进入目录,执行编译:

注意

如果使用 Ceph 的 RADOS 作为 JuiceFS 的存储引擎,需要先安装 librados-dev 包并且在编译 libjfs.so 时加上 -tags ceph

  1. $ cd juicefs/sdk/java
  2. $ make

编译完成后,可以在 sdk/java/target 目录中找到编译好的 JAR 文件,包括两个版本:

  • 包含第三方依赖的包:juicefs-hadoop-X.Y.Z.jar
  • 不包含第三方依赖的包:original-juicefs-hadoop-X.Y.Z.jar

建议使用包含第三方依赖的版本。

Windows

用于 Windows 环境的客户端需要在 Linux 或 macOS 系统上通过交叉编译的方式获得,编译依赖 mingw-w64,需要提前安装。

与编译面向 Linux 和 macOS 客户端的步骤相同,比如在 Ubuntu 系统上,先安装 mingw-w64 包,解决依赖问题:

  1. $ sudo apt install mingw-w64

克隆并进入 JuiceFS 源代码目录,执行以下代码进行编译:

  1. $ cd juicefs/sdk/java
  2. $ make win

部署客户端

让 Hadoop 生态各组件能够正确识别 JuiceFS,需要进行以下配置:

  1. 将编译好的 JAR 文件和 $JAVA_HOME/lib/tools.jar 放置到组件的 classpath 内,常见大数据平台和组件的安装路径见下表。
  2. 将 JuiceFS 相关配置写入配置文件(通常是 core-site.xml),详见客户端配置参数

建议将 JAR 文件放置在一个统一的位置,其他位置通过符号链接进行调用。

大数据平台

名称安装路径
CDH/opt/cloudera/parcels/CDH/lib/hadoop/lib
/opt/cloudera/parcels/CDH/spark/jars
/var/lib/impala
HDP/usr/hdp/current/hadoop-client/lib
/usr/hdp/current/hive-client/auxlib
/usr/hdp/current/spark2-client/jars
Amazon EMR/usr/lib/hadoop/lib
/usr/lib/spark/jars
/usr/lib/hive/auxlib
阿里云 EMR/opt/apps/ecm/service/hadoop//package/hadoop/share/hadoop/common/lib
/opt/apps/ecm/service/spark//package/spark/jars
/opt/apps/ecm/service/presto//package/presto/plugin/hive-hadoop2
/opt/apps/ecm/service/hive//package/apache-hive/lib
/opt/apps/ecm/service/impala//package/impala/lib
腾讯云 EMR/usr/local/service/hadoop/share/hadoop/common/lib
/usr/local/service/presto/plugin/hive-hadoop2
/usr/local/service/spark/jars
/usr/local/service/hive/auxlib
UCloud UHadoop/home/hadoop/share/hadoop/common/lib
/home/hadoop/hive/auxlib
/home/hadoop/spark/jars
/home/hadoop/presto/plugin/hive-hadoop2
百度云 EMR/opt/bmr/hadoop/share/hadoop/common/lib
/opt/bmr/hive/auxlib
/opt/bmr/spark2/jars

社区开源组件

名称安装路径
Spark${SPARK_HOME}/jars
Presto${PRESTO_HOME}/plugin/hive-hadoop2
Flink${FLINK_HOME}/lib

客户端配置参数

请参考以下表格设置 JuiceFS 文件系统相关参数,并写入配置文件,一般是 core-site.xml

核心配置

配置项默认值描述
fs.jfs.implio.juicefs.JuiceFileSystem指定要使用的存储实现,默认使用 jfs:// 作为 scheme。如想要使用其它 scheme(例如 cfs://),则修改为 fs.cfs.impl 即可。无论使用的 scheme 是什么,访问的都是 JuiceFS 中的数据。
fs.AbstractFileSystem.jfs.implio.juicefs.JuiceFS指定要使用的存储实现,默认使用 jfs:// 作为 scheme。如想要使用其它 scheme(例如 cfs://),则修改为 fs.AbstractFileSystem.cfs.impl 即可。无论使用的 scheme 是什么,访问的都是 JuiceFS 中的数据。
juicefs.meta指定预先创建好的 JuiceFS 文件系统的元数据引擎地址。可以通过 juicefs.{vol_name}.meta 格式为客户端同时配置多个文件系统。具体请参考「多文件系统配置」

缓存配置

配置项默认值描述
juicefs.cache-dir设置本地缓存目录,可以指定多个文件夹,用冒号 : 分隔,也可以使用通配符(比如 * )。请预先创建好这些目录,并给予 0777 权限,便于多个应用共享缓存数据。
juicefs.cache-size0设置本地缓存目录的容量,单位 MiB,默认为 0,即不开启缓存。如果配置了多个缓存目录,该值代表所有缓存目录容量的总和。
juicefs.cache-full-blocktrue是否缓存所有读取的数据块,false 表示只缓存随机读的数据块。
juicefs.free-space0.1本地缓存目录的最小可用空间比例,默认保留 10% 剩余空间。
juicefs.attr-cache0目录和文件属性缓存的过期时间(单位:秒)
juicefs.entry-cache0文件项缓存的过期时间(单位:秒)
juicefs.dir-entry-cache0目录项缓存的过期时间(单位:秒)
juicefs.discover-nodes-url指定发现集群节点列表的方式,每 10 分钟刷新一次。

YARN:yarn
Spark Standalone:http://spark-master:web-ui-port/json/
Spark ThriftServer:http://thrift-server:4040/api/v1/applications/
Presto:http://coordinator:discovery-uri-port/v1/service/presto/

I/O 配置

配置项默认值描述
juicefs.max-uploads20上传数据的最大连接数
juicefs.max-deletes2删除数据的最大连接数
juicefs.get-timeout5下载一个对象的超时时间,单位为秒。
juicefs.put-timeout60上传一个对象的超时时间,单位为秒。
juicefs.memory-size300读写数据的缓冲区最大空间,单位为 MiB。
juicefs.prefetch1预读数据块的线程数
juicefs.upload-limit0上传带宽限制,单位为 Mbps,默认不限制。
juicefs.download-limit0下载带宽限制,单位为 Mbps,默认不限制。

其他配置

配置项默认值描述
juicefs.bucket为对象存储指定跟格式化时不同的访问地址
juicefs.debugfalse是否开启 debug 日志
juicefs.access-log访问日志的路径。需要所有应用都有写权限,可以配置为 /tmp/juicefs.access.log。该文件会自动轮转,保留最近 7 个文件。
juicefs.superuserhdfs超级用户
juicefs.usersnull用户名以及 UID 列表文件的地址,比如 jfs://name/etc/users。文件格式为 <username>:<UID>,一行一个用户。
juicefs.groupsnull用户组、GID 以及组成员列表文件的地址,比如 jfs://name/etc/groups。文件格式为 <group-name>:<GID>:<username1>,<username2>,一行一个用户组。
juicefs.umasknull创建文件和目录的 umask 值(如 0022),如果没有此配置,默认值是 fs.permissions.umask-mode
juicefs.push-gatewayPrometheus Pushgateway 地址,格式为 <host>:<port>
juicefs.push-interval10推送数据到 Prometheus 的时间间隔,单位为秒。
juicefs.push-authPrometheus 基本认证信息,格式为 <username>:<password>
juicefs.fast-resolvetrue是否开启快速元数据查找(通过 Redis Lua 脚本实现)
juicefs.no-usage-reportfalse是否上报数据。仅上版本号等使用量数据,不包含任何用户信息。

多文件系统配置

当需要同时使用多个 JuiceFS 文件系统时,上述所有配置项均可对特定文件系统进行指定,只需要将文件系统名字放在配置项的中间,比如下面示例中的 jfs1jfs2

  1. <property>
  2. <name>juicefs.jfs1.meta</name>
  3. <value>redis://jfs1.host:port/1</value>
  4. </property>
  5. <property>
  6. <name>juicefs.jfs2.meta</name>
  7. <value>redis://jfs2.host:port/1</value>
  8. </property>

配置示例

以下是一个常用的配置示例,请替换 juicefs.meta 配置中的 {HOST}{PORT}{DB} 变量为实际的值。

  1. <property>
  2. <name>fs.jfs.impl</name>
  3. <value>io.juicefs.JuiceFileSystem</value>
  4. </property>
  5. <property>
  6. <name>fs.AbstractFileSystem.jfs.impl</name>
  7. <value>io.juicefs.JuiceFS</value>
  8. </property>
  9. <property>
  10. <name>juicefs.meta</name>
  11. <value>redis://{HOST}:{PORT}/{DB}</value>
  12. </property>
  13. <property>
  14. <name>juicefs.cache-dir</name>
  15. <value>/data*/jfs</value>
  16. </property>
  17. <property>
  18. <name>juicefs.cache-size</name>
  19. <value>1024</value>
  20. </property>
  21. <property>
  22. <name>juicefs.access-log</name>
  23. <value>/tmp/juicefs.access.log</value>
  24. </property>

Hadoop 环境配置

请参照前述各项配置表,将配置参数加入到 Hadoop 配置文件 core-site.xml 中。

CDH6

如果使用的是 CDH 6 版本,除了修改 core-site 外,还需要通过 YARN 服务界面修改 mapreduce.application.classpath,增加:

  1. $HADOOP_COMMON_HOME/lib/juicefs-hadoop.jar

HDP

除了修改 core-site 外,还需要通过 MapReduce2 服务界面修改配置 mapreduce.application.classpath,在末尾增加(变量无需替换):

  1. /usr/hdp/${hdp.version}/hadoop/lib/juicefs-hadoop.jar

将配置参数加入 conf/flink-conf.yaml。如果只是在 Flink 中使用 JuiceFS, 可以不在 Hadoop 环境配置 JuiceFS,只需要配置 Flink 客户端即可。

Hudi

注意

目前最新版 Hudi(v0.10.0)还不支持 JuiceFS,你需要自行编译最新 master 分支。

请参考「Hudi 官方文档」了解如何配置 JuiceFS。

重启服务

当需要使用以下组件访问 JuiceFS 数据时,需要重启相关服务。

注意

在重启之前需要保证 JuiceFS 配置已经写入配置文件,通常可以查看机器上各组件配置的 core-site.xml 里面是否有 JuiceFS 相关配置。

组件名服务名
HiveHiveServer
Metastore
SparkThriftServer
PrestoCoordinator
Worker
ImpalaCatalog Server
Daemon
HBaseMaster
RegionServer

HDFS、Hue、ZooKeeper 等服务无需重启。

若访问 JuiceFS 出现 Class io.juicefs.JuiceFileSystem not foundNo FilesSystem for scheme: jfs 错误,请参考 FAQ

环境验证

JuiceFS Java 客户端部署完成以后,可以采用以下方式验证部署是否成功。

Hadoop

  1. $ hadoop fs -ls jfs://{JFS_NAME}/
说明

这里的 JFS_NAME 是创建 JuiceFS 文件系统时指定的名称。

Hive

  1. CREATE TABLE IF NOT EXISTS person
  2. (
  3. name STRING,
  4. age INT
  5. ) LOCATION 'jfs://{JFS_NAME}/tmp/person';

监控指标收集

请查看「监控」文档了解如何收集及展示 JuiceFS 监控指标

基准测试

以下提供了一系列方法,使用 JuiceFS 客户端内置的压测工具,对已经成功部署了客户端环境进行性能测试。

1. 本地测试

元数据性能

  • create

    1. hadoop jar juicefs-hadoop.jar nnbench create -files 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench -local

    此命令会 create 10000 个空文件

  • open

    1. hadoop jar juicefs-hadoop.jar nnbench open -files 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench -local

    此命令会 open 10000 个文件,并不读取数据

  • rename

    1. hadoop jar juicefs-hadoop.jar nnbench rename -files 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench -local
  • delete

    1. hadoop jar juicefs-hadoop.jar nnbench delete -files 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench -local
  • 参考值

操作TPS时延(ms)
create6441.55
open34670.29
rename4832.07
delete5061.97

I/O 性能

  • 顺序写

    1. hadoop jar juicefs-hadoop.jar dfsio -write -size 20000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/DFSIO -local
  • 顺序读

    1. hadoop jar juicefs-hadoop.jar dfsio -read -size 20000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/DFSIO -local

    如果多次运行此命令,可能会出现数据被缓存到了系统缓存而导致读取速度非常快,只需清除 JuiceFS 的本地磁盘缓存即可

  • 参考值

操作吞吐(MB/s)
write647
read111

如果机器的网络带宽比较低,则一般能达到网络带宽瓶颈

2. 分布式测试

以下命令会启动 MapReduce 分布式任务程序对元数据和 IO 性能进行测试,测试时需要保证集群有足够的资源能够同时启动所需的 map 任务。

本项测试使用的计算资源:

  • 服务器:3 台 4 核 32 GB 内存的云服务器,突发带宽 5Gbit/s。
  • 数据库:阿里云 Redis 5.0 社区 4G 主从版

元数据性能

  • create

    1. hadoop jar juicefs-hadoop.jar nnbench create -maps 10 -threads 10 -files 1000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench

    此命令会启动 10 个 map task,每个 task 有 10 个线程,每个线程会创建 1000 个空文件,总共 100000 个空文件

  • open

    1. hadoop jar juicefs-hadoop.jar nnbench open -maps 10 -threads 10 -files 1000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench

    此命令会启动 10 个 map task,每个 task 有 10 个线程,每个线程会 open 1000 个文件,总共 open 100000 个文件

  • rename

    1. hadoop jar juicefs-hadoop.jar nnbench rename -maps 10 -threads 10 -files 1000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench

    此命令会启动 10 个 map task,每个 task 有 10 个线程,每个线程会 rename 1000 个文件,总共 rename 100000 个文件

  • delete

    1. hadoop jar juicefs-hadoop.jar nnbench delete -maps 10 -threads 10 -files 1000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/NNBench

    此命令会启动 10 个 map task,每个 task 有 10 个线程,每个线程会 delete 1000 个文件,总共 delete 100000 个文件

  • 参考值

    • 10 并发
    操作IOPS时延(ms)
    create41782.2
    open94070.8
    rename31972.9
    delete30603.0
    • 100 并发
    操作IOPS时延(ms)
    create117737.9
    open340832.4
    rename899510.8
    delete719113.6

I/O 性能

  • 连续写

    1. hadoop jar juicefs-hadoop.jar dfsio -write -maps 10 -size 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/DFSIO

    此命令会启动 10 个 map task,每个 task 写入 10000MB 的数据

  • 连续读

    1. hadoop jar juicefs-hadoop.jar dfsio -read -maps 10 -size 10000 -baseDir jfs://{JFS_NAME}/tmp/benchmarks/DFSIO

    此命令会启动 10 个 map task,每个 task 读取 10000MB 的数据

  • 参考值

操作平均吞吐(MB/s)总吞吐(MB/s)
write1981835
read1241234

3. TPC-DS

测试数据集 100GB 规模,测试 Parquet 和 ORC 两种文件格式。

本次测试仅测试前 10 个查询。

使用 Spark Thrift JDBC/ODBC Server 开启 Spark 常驻进程,然后通过 Beeline 连接提交任务。

测试硬件

机器型号CPUMemoryDisk数量
Master阿里云 ecs.r6.xlarge432GiB系统盘:100GiB1
Core阿里云 ecs.r6.xlarge432GiB系统盘:100GiB
数据盘:500GiB 高效云盘 x 2
3

软件配置

Spark Thrift JDBC/ODBC Server
  1. ${SPARK_HOME}/sbin/start-thriftserver.sh \
  2. --master yarn \
  3. --driver-memory 8g \
  4. --executor-memory 10g \
  5. --executor-cores 3 \
  6. --num-executors 3 \
  7. --conf spark.locality.wait=100 \
  8. --conf spark.sql.crossJoin.enabled=true \
  9. --hiveconf hive.server2.thrift.port=10001
JuiceFS 缓存配置

Core 节点 2 块数据盘挂载在 /data01/data02 目录下,core-site.xml 配置如下:

  1. <property>
  2. <name>juicefs.cache-size</name>
  3. <value>200000</value>
  4. </property>
  5. <property>
  6. <name>juicefs.cache-dir</name>
  7. <value>/data*/jfscache</value>
  8. </property>
  9. <property>
  10. <name>juicefs.cache-full-block</name>
  11. <value>false</value>
  12. </property>
  13. <property>
  14. <name>juicefs.discover-nodes-url</name>
  15. <value>yarn</value>
  16. </property>
  17. <property>
  18. <name>juicefs.attr-cache</name>
  19. <value>3</value>
  20. </property>
  21. <property>
  22. <name>juicefs.entry-cache</name>
  23. <value>3</value>
  24. </property>
  25. <property>
  26. <name>juicefs.dir-entry-cache</name>
  27. <value>3</value>
  28. </property>

测试

任务提交的命令如下:

  1. ${SPARK_HOME}/bin/beeline -u jdbc:hive2://localhost:10001/${DATABASE} \
  2. -n hadoop \
  3. -f query{i}.sql

结果

JuiceFS 可以使用本地磁盘作为缓存加速,以下数据是跑 4 次后的结果(单位秒)。

ORC
QueriesRedisTiKVHDFS
q1202020
q2283326
q3242728
q4300309290
q511611791
q6374241
q7242823
q8131516
q98711289
q10232422

orc

Parquet
QueriesRedisTiKVHDFS
q1333539
q2283231
q3232524
q4273284266
q59610794
q6363542
q7283024
q8111214
q9859777
q10242838

parquet

FAQ

1. 出现 Class io.juicefs.JuiceFileSystem not found 异常

出现这个异常的原因是 juicefs-hadoop.jar 没有被加载,可以用 lsof -p {pid} | grep juicefs 查看 JAR 文件是否被加载。需要检查 JAR 文件是否被正确地放置在各个组件的 classpath 里面,并且保证 JAR 文件有可读权限。

另外,在某些发行版 Hadoop 环境中,需要修改 mapred-site.xml 中的 mapreduce.application.classpath 参数,添加 juicefs-hadoop.jar 的路径。

2. 出现 No FilesSystem for scheme: jfs 异常

出现这个异常的原因是 core-site.xml 配置文件中的 JuiceFS 配置没有被读取到,需要检查组件配置的 core-site.xml 中是否有 JuiceFS 相关配置。