欢迎光临
我们一直在努力

JuiceFS Python SDK 与 fsspec 集成实战:用 Ray 直接读取 JuiceFS 上的数据文件

JuiceFS Python SDK 与 fsspec 集成实战:用 Ray 直接读取 JuiceFS 上的数据文件

【免费下载链接】juicefs JuiceFS is a distributed POSIX file system built on top of Redis and S3. 【免费下载链接】juicefs 项目地址: 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

整个链路分为三层:

  • 挂载 JuiceFS:通过 juicefs mount redis://localhost /tmp/jfs -d 将元数据引擎(Redis)与对象存储(默认使用本地磁盘目录作为存储后端)组合成的 JuiceFS 文件系统挂载到本机 /tmp/jfs 目录(-d 表示守护进程后台运行)。这一步让 JuiceFS 卷在本地表现为一个普通 POSIX 目录。
  • 下载数据文件:使用 wget 从英国政府性别薪酬差距公开数据集下载 CSV,并直接写入挂载点 /tmp/jfs/ray_demo_data.csv。写入的数据会经过 JuiceFS 切块后落入底层对象存储。
  • 运行 fsspec 示例:执行 python3 sdk/python/examples/fsspec/main.py,脚本内部通过 fsspec 抽象层同时读取 HTTPS 远程 CSV 与 JuiceFS 上的 CSV,并用 Ray Datasets 完成行数统计。
  • 二、示例代码逐段拆解

    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. 【免费下载链接】juicefs 项目地址: https://gitcode.com/GitHub_Trending/ju/juicefs

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:171主机测评 » JuiceFS Python SDK 与 fsspec 集成实战:用 Ray 直接读取 JuiceFS 上的数据文件
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址