JuiceFS Python SDK 与 fsspec 集成实战:用 Ray 直接读取 JuiceFS 上的数据文件
【免费下载链接】juicefs JuiceFS is a distributed POSIX file system built on top of Redis and S3. 项目地址: https://gitcode.com/GitHub_Trending/ju/juicefs
导读
本文围绕 sdk/python/examples/fsspec/readme.md 中的官方示例展开,讲解如何让 JuiceFS 通过 Python 生态的标准文件系统抽象层 fsspec(filesystem-spec)对外提供服务:只需一行 fsspec.filesystem("jfs", …) 即可获得一个可被 Pandas、Dask、Ray 等数据框架直接消费的 POSIX 风格文件系统,并配合 Ray Datasets 读取远程 CSV 数据。读完本文,你将掌握 fsspec 驱动的 JuiceFS 挂载流程、Python SDK 的底层实现原理、核心初始化参数的含义,以及一个可复现的端到端数据读取示例。
一、示例概览:fsspec 读取 CSV 的完整链路
官方示例的入口脚本为 sdk/python/examples/fsspec/main.py,其完整运行流程记录在 sdk/python/examples/fsspec/readme.md 中:
# This example demonstrates how to use the fsspec library to read a CSV file.
juicefs mount redis://localhost /tmp/jfs -d
# Download the data file
wget https://gender-pay-gap.service.gov.uk/viewing/download-data/2021 -O /tmp/jfs/ray_demo_data.csv
# run the example
python3 sdk/python/examples/fsspec/main.py
整个链路分为三层:
二、示例代码逐段拆解
2.1 通过 fsspec 读取 HTTPS 远程 CSV
import fsspec
import ray
import sys
sys.path.append('.')
import sdk.python.juicefs.juicefs.spec
# from sdk.python.juicefs.juicefs.spec import JuiceFS
fs = fsspec.filesystem('https')
ds = ray.data.read_csv(
"https://gender-pay-gap.service.gov.uk/viewing/download-data/2021",
filesystem=fs,
partition_filter=None # Since the file doesn't end in .csv
)
ds.count()
- import sdk.python.juicefs.juicefs.spec 是关键一步:导入该模块会产生副作用——在 spec.py 末尾通过 register_implementation("jfs", JuiceFS, True) 与 register_implementation("juicefs", JuiceFS, True) 将 jfs / juicefs 两个协议名注册进 fsspec 的全局实现注册表。只有完成这一步,后续 fsspec.filesystem("jfs", …) 才能被正确解析。
- fsspec.filesystem('https') 返回 fsspec 内置的 HTTP/HTTPS 文件系统实现,Ray 数据集直接流式读取远程 CSV,无需先下载到本地。
- partition_filter=None 告诉 Ray 不要按扩展名过滤分区,因为该数据文件 URL 不以 .csv 结尾。
- ds.count() 触发真正的读取与解析,输出 CSV 的总行数。
2.2 通过 jfs 协议访问 JuiceFS 上的同一份数据
jfs = fsspec.filesystem("jfs", auto_mkdir=True, name="myjfs", meta="redis://localhost")
dsjfs = ray.data.read_csv('/ray_demo_data.csv', filesystem=jfs)
dsjfs.count()
- fsspec.filesystem("jfs", …) 依据注册表找到 JuiceFS 类并实例化,等价于直接用 JuiceFS(name="myjfs", meta="redis://localhost", auto_mkdir=True)。
- name="myjfs" 是卷名(volume name);meta="redis://localhost" 指向元数据引擎地址,即示例第一步中 juicefs mount redis://localhost 所使用的同一个 Redis。二者必须与挂载时使用的卷配置保持一致,否则无法定位到相同的数据。
- auto_mkdir=True 表示在写文件时若父目录不存在则自动创建。
- 读取路径为 /ray_demo_data.csv(卷内绝对路径),即第一步通过 wget 写入 /tmp/jfs/ray_demo_data.csv 的同一个文件——因为 /tmp/jfs 挂载点对应的正是名为 myjfs 的卷根目录。
这段脚本展示了 fsspec 的典型价值:对上层(Ray)而言,https://… 与 jfs://… 只是两种不同的 URL scheme,读取与解析逻辑完全一致,数据源切换成本极低。
三、JuiceFS 的 fsspec 实现:JuiceFS 类源码解析
JuiceFS 类定义于 sdk/python/juicefs/juicefs/spec.py,继承自 fsspec 的 AbstractFileSystem:
class JuiceFS(AbstractFileSystem):
protocol = "jfs", "juicefs"
def __init__(self, name, auto_mkdir=False, **kwargs):
if self._cached:
return
super().__init__(**kwargs)
self.auto_mkdir = auto_mkdir
self.temppath = kwargs.pop("temppath", "/tmp")
self.fs = Client(name, **kwargs)
几个值得注意的设计点:
- 协议双注册:protocol = "jfs", "juicefs" 使得 jfs 与 juicefs 两种 scheme 均可访问同一实现,方便与既有代码风格对齐。
- 底层委托:self.fs = Client(name, **kwargs) 将真正的 POSIX 语义操作委托给 juicefs.py 中基于 ctypes 的 Client 类。
- POSIX 语义覆盖:类中完整实现了 mkdir/makedirs、ls/info/du、cp_file(内部走 fs.clone)、rm/rmr、mv(内部走 rename)、link/symlink、touch、chmod 等文件系统操作,并借助 stat 模块的 S_ISDIR/S_ISLNK/S_ISREG 正确区分目录、软链接与普通文件,返回的 info 字典包含 size、mode、ino、nlink、uid、gid、created、mtime 等元数据。其语义与 pkg/vfs 提供的 VFS 层保持一致。
- 文件读写:_open() 返回 JuiceFile(继承 AbstractBufferedFile),当 autocommit=False 时写入先落临时文件、commit() 时再原子 rename 到目标路径(见 spec.py),保证写入的原子性。
四、底层实现:ctypes 与 libjfs 动态库
JuiceFS 类之所以能直接操作 JuiceFS 卷,是因为 Python SDK 通过 ctypes 加载了由 Go 编译的共享库 libjfs.so(macOS 为 libjfs.dylib、Windows 为 libjfs.dll)。从 juicefs.py 可见:
class JuiceFSLib(object):
def __init__(self):
if sys.platform == "win32":
_ext = "dll"
elif sys.platform == "darwin":
_ext = "dylib"
else:
_ext = "so"
self.lib = cdll.LoadLibrary(os.path.join(os.path.dirname(__file__), f"libjfs.{_ext}"))
- 加载方式:libjfs 与 spec.py、juicefs.py 同目录(sdk/python/juicefs/juicefs/),setup.py 中 package_data={'juicefs': ['*.so', '*.dylib', '*.dll']} 保证打包时动态库会被一并打入 wheel。
- 构建方式:libjfs 由 Go 代码编译而来。在 sdk/python/Makefile 中通过 go build -buildmode c-shared -o juicefs/juicefs/libjfs.$(LIBJFS_EXT) ../java/libjfs 生成,即复用 Java SDK 的 libjfs Go 包(sdk/java/libjfs),其版本号取自 pkg/version/version.go。
- 调用约定:Client 构造时将 name、meta 以及全部配置项序列化为 JSON 字符串,调用 jfs_init 完成文件系统初始化;此后每次 stat、open、read、write 等操作都映射为一次 jfs_* 系列 C 函数调用(例如 jfs_pread、jfs_pwrite、jfs_close)。check_error(juicefs.py)会把负返回码转换为带 errno 的 OSError,从而与 Python 标准 IO 的错误语义对齐。
因此,fsspec → JuiceFS(Python 封装)→ Client(ctypes)→ libjfs(Go,实现 POSIX 语义)→ 元数据引擎(Redis 等)与对象存储,构成了完整的调用链。
五、Client 关键初始化参数说明
Client.__init__ 的完整签名见 juicefs.py,以下参数会通过 jfs_init 传给底层文件系统(括号内为默认值,与 pkg/meta/config.go、pkg/chunk 等模块的配置体系对应):
| name | 必填 | 卷名(volume name),须与 juicefs format 创建时一致 |
| meta | 必填 | 元数据引擎地址,如 redis://localhost |
| bucket | "" | 对象存储 bucket 地址;留空时使用本地磁盘目录 |
| storage_class | "" | 对象存储类别 |
| read_only | False | 只读挂载 |
| no_session | False | 不注册客户端会话 |
| no_bgjob | True | 关闭后台任务(如自动备份、GC 相关任务) |
| open_cache | "0" | 打开文件缓存策略 |
| backup_meta | "3600" | 元数据自动备份间隔(秒) |
| heartbeat | "12" | 会话心跳间隔(秒) |
| cache_dir | "memory" | 本地缓存目录,memory 表示内存缓存 |
| cache_size | "100M" | 本地缓存大小上限 |
| free_space_ratio | "0.1" | 本地磁盘最小可用空间比例 |
| cache_partial_only | False | 仅缓存部分块 |
| verify_cache_checksum | "extend" | 缓存校验和验证模式 |
| cache_eviction | "2-random" | 缓存淘汰策略 |
| cache_scan_interval | "3600" | 缓存目录扫描间隔(秒) |
| cache_expire | "0" | 缓存过期时间 |
| writeback | False | 写缓存(异步回写)模式 |
| buffer_size | "300M" | 读写缓冲内存上限 |
| prefetch | 1 | 预读并发数 |
| max_readahead | "0" | 最大预读量 |
| upload_limit / download_limit | "0" | 上传/下载限速 |
| max_uploads / max_deletes | 20 / 10 | 最大上传/删除并发 |
| io_retries | 10 | IO 重试次数 |
| get_timeout / put_timeout | "5" / "60" | 读/写超时(秒) |
| attr_cache / entry_cache / dir_entry_cache | "1s" / "0s" / "1s" | 属性、目录项、目录项列缓存时长 |
| debug | False | 调试日志 |
| access_log | "" | 访问日志路径 |
| push_gateway 等 | "" | Prometheus PushGateway / Graphite / RemoteWrite 监控上报配置 |
Client 构造时会把上述参数映射为底层配置键(如 cacheDir、cacheSize、memorySize、attrTimeout、entryTimeout 等,见 juicefs.py),通过 json.dumps 序列化后传入 jfs_init。这意味着 fsspec 使用方式与命令行挂载的参数体系一一对应,例如 fsspec.filesystem("jfs", name="myjfs", meta="redis://localhost", cache_size="1G") 等价于在挂载时指定 –cache-size 1G。
六、环境准备与运行验证
6.1 构建并安装 Python SDK
Python SDK 位于 sdk/python,构建共享库后打包为 wheel:
# 构建 libjfs 动态库(需 Go 工具链)
make -C sdk/python libjfs
# 打包并安装 Python 包(juicefs,内含 libjfs.so)
make -C sdk/python juicefs
pip install sdk/python/juicefs/dist/*.whl
# 示例还需要
pip install fsspec ray
从 setup.py 可以看到,除了运行时依赖 six 之外,setup.py 还通过 entry_points 注册了 fsspec.specs: jfs = juicefs.JuiceFS,因此在正确安装后 fsspec.filesystem("jfs", …) 无需显式 import 也能被 fsspec 发现。
6.2 挂载卷并准备数据
# 后台挂载,元数据引擎使用本地 Redis
juicefs mount redis://localhost /tmp/jfs -d
# 下载公开数据集到 JuiceFS 挂载点
wget https://gender-pay-gap.service.gov.uk/viewing/download-data/2021 -O /tmp/jfs/ray_demo_data.csv
若尚未创建卷,需先执行 juicefs format redis://localhost myjfs 完成格式化,确保卷名与 Python 侧 name 参数一致。
6.3 运行示例并观察结果
python3 sdk/python/examples/fsspec/main.py
脚本先统计 HTTPS 源 CSV 的行数,再统计 JuiceFS 上同一份 CSV 的行数,两个 ds.count() 的结果应一致(均等于该数据集实际行数),打印分隔符 —-++++—-++++—- 便于区分两次输出。
6.4 用官方测试套件验证 SDK 正确性
仓库为 fsspec 实现提供了基于 fsspec 官方抽象测试集的用例,见 sdk/python/juicefs/tests/test.py:
class JuiceFSFixtures(abstract.AbstractFixtures):
@pytest.fixture(scope="class")
def fs(self):
meta = os.getenv("JUICEFS_META", "redis://localhost")
m = filesystem("jfs", auto_mkdir=True, name="test", meta=meta)
return m
它继承 fsspec 的 AbstractGetTests、AbstractPutTests、AbstractCopyTests,用 JUICEFS_META 环境变量指定元数据引擎(默认 redis://localhost),对 jfs 协议执行 fsspec 官方抽象测试,验证 get/put/copy 等接口行为符合 fsspec 规范。这为将 JuiceFS 接入数据科学流水线提供了回归保障。
七、总结
通过本文可以看到,JuiceFS 的 fsspec 集成是一条"薄封装 + 强底层"的路径:
- 面向数据科学生态:jfs / juicefs 协议让 Ray、Dask、Pandas 等 fsspec 生态工具可以像使用本地文件系统或 S3 一样使用 JuiceFS,数据源切换几乎零成本;
- 底层是完整 POSIX 文件系统:fsspec 层只做接口适配,真正的读写、元数据、缓存、配额等能力全部由 Go 实现的 libjfs 与 JuiceFS 核心模块(pkg/vfs、pkg/meta、pkg/chunk、pkg/object)提供;
- 示例可复现:按照 sdk/python/examples/fsspec/readme.md 的三步操作(挂载 → 下载数据 → 运行脚本),即可在本机验证从 HTTPS 与 JuiceFS 两个数据源读取同一份 CSV 的全过程。
相关代码与文档索引:
- 示例脚本:sdk/python/examples/fsspec/main.py
- 示例说明:sdk/python/examples/fsspec/readme.md
- fsspec 文件系统实现:sdk/python/juicefs/juicefs/spec.py
- ctypes 客户端与参数体系:sdk/python/juicefs/juicefs/juicefs.py
- 打包与构建:sdk/python/juicefs/setup.py、sdk/python/Makefile
- 官方抽象测试:sdk/python/juicefs/tests/test.py
【免费下载链接】juicefs JuiceFS is a distributed POSIX file system built on top of Redis and S3. 项目地址: https://gitcode.com/GitHub_Trending/ju/juicefs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





