在近期发布的 JuiceFS 社区版1.3 和企业版 5.2 中,我们对 Windows 客户端进行了大量的优化工作。在早期版本中,JuiceFS 也尝试支持 Windows 平台,但是在使用体验和稳定性方面存在诸多问题,无法达到一个稳定的可用状态。
去年,我们决定重新对 Windows 客户端 进行全面改进,力求在提高性能的同时,提供更加稳定和高质量的用户体验。在近期的一次云端渲染场景测试中,我们对 3000 台 Windows 客户端进行了小文件读写性能测试,结果表明性能已能满足大规模使用需求。我们将在下一篇博客中分享此次测试的详细情况。
在此次历时一年的优化过程中,由于 Windows 客户端相关文档和资料相对有限,我们面临了诸多挑战。本文将详细阐述过程中遇到的关键问题、采取的方案以及目前已取得的进展。需要注意的是,目前的版本处于 Beta 阶段,即主要功能已完成,但仍需通过持续的测试和用户反馈进行优化。希望有需求的用户积极使用,并在社区中提出建议,我们也将积极支持并解决大家的问题。
01 目标是什么?
对于 Windows 版本 的开发,核心任务是将现有文件系统逻辑迁移到 Windows 平台。接入方式主要有两种:
-
一是通过 Linux 挂载并启动 Samba 服务,将文件共享给 Windows 使用;这种方式可能带来额外的协议开销和配置复杂性,进而影响性能和稳定性。
-
二是直接在 Windows 上运行客户端,这种方式更接近 Windows 原生的使用体验,但历史版本存在较多 bug。在此次 Beta 版本 中,我们主要针对第二种方式对 Windows 客户端进行优化。
我们希望这个版本能够达到真正可用的状态,包括确保大多数 Windows 应用程序能够在 JuiceFS 的文件系统上正常运行,支持数据读写操作以及文件管理功能;确保系统具有合格的性能。为此,Beta 版本需要完成的工作可以分为几个方面:
1. 修复工具集
工具集是 JuiceFS 使用过程中的重要组成部分,但许多常用命令在 Windows 客户端上无法正常使用。我们将在下文详细描述各个主要命令,所进行的优化和改进工作。
2. 优化使用体验
在过去的版本中,用户体验存在许多问题,尤其在性能方面和操作简便性方面。因此,我们需要着重优化这些方面,使得使用过程更加流畅、简便。
3. 修复 Bug
- 与身份验证和权限管理相关的问题,过去的版本在这些方面比较混乱。
- 与 Windows API 相关的 Bug,特别是错误码返回值和某些逻辑问题。
- 一些零散的 Bug,需要在 Beta 版本中一并修复。
02 怎么做 ?
在这个优化过程中,我们依旧借助了一个非常重要的组件——WinFsp(Windows File System Proxy),它是一个开源库,提供了两个至关重要的特性:
- 提供了一个高效的文件系统驱动接口,实现了 Windows 文件系统驱动(FSD, File System Driver)。
- 还提供了 FUSE(Filesystem in Userspace)接口中的 high-level 接口。FUSE 本身提供了两套接口——high-level 接口和 low-level 接口。在JuiceFS 中,我们使用的是 low-level 的接口,但通过做一些路径转换,我们已经可以成功对接 FUSE 层。
在引入 WinFsp 后,Windows 上的读取流程大致是这样的。下图中上半部分代表用户空间,下半部分代表内核空间。
图中展示的流程是数据从应用程序到驱动程序之间的流转路径。在用户空间中,应用程序执行 I/O 操作(如文件读写)会传递到内核空间,并流转到 Windows 内核中的一个核心组件 —— Windows I/O Manager。该组件负责管理所有的 I/O 操作。

接下来,Windows I/O Manager 会将这些 I/O 操作转换为 Windows I/O Request Package(IRP),然后将其转发到文件系统驱动层。此处简化了部分层级,最终这些请求会传递到文件系统驱动层,具体来说就是 WinFsp 文件系统驱动。
当文件系统驱动接收到请求时,它会将请求转发给 WinFsp 的 DLL。这个 DLL 承担了与 Windows 系统内核数据进行接口转换的责任。
在我们 JuiceFS 的实现中,应用程序通过链接 WinFsp DLL 来处理 I/O 请求。当 Windows I/O 请求被传递到 DLL 时,WinFsp 会将其转换为 FUSE 接口,并通过 Windows FUSE 兼容层 进行回调处理。绝大部分的工作集中在这一层,包括 bug 修复和性能优化。
处理完这些操作后,最终的请求会交给 JuiceFS 核心 VFS(虚拟文件系统)层,核心 VFS 负责执行具体的文件系统逻辑和请求处理。
还有 4 个困难
1. Unix vs Windows
尽管 WinFsp 已经帮助我们将 I/O 请求转换为 FUSE 接口,但我们依然需要面对 Unix 和 Windows 之间的本质性差异。
权限管理差异
例如,Linux 中的文件权限(可读、可写、可执行等)和扩展权限(如 ACL)与 Windows 中的权限模型完全不同。Windows 使用 DACL(Discretionary Access Control List)进行权限定义,同时支持权限继承,这与 Unix 的权限模型有很大差异。因此,虽然 WinFsp 处理了一部分权限的转换,但我们仍需解决如何将这两种 权限体系进行有效映射,以便更好地支持 Windows 应用。
系统特性的差异
Unix 和 Windows 在文件系统特性方面也存在较多的本质差异。诸如硬链接、软链接、挂载方式、目录长度、字符限制等、文件属性等,而我们的 JuiceFS 核心文件系统是基于 Unix 的文件系统设计,因此我们需要解决或者绕开这些差异,以确保在 Windows 平台上也能正常运行。
API 差异
Unix 和 Windows 在系统 API 上也有许多差异,尽管我们使用了 Go 语言来简化部分 API 差异的处理,但依然存在一些需要手动解决的情况。例如,获取 UID 和 GID 等信息,在 Windows 系统中并没有直接的对应方式。虽然 Go 语言在跨平台处理上做了很多优化,但一些特定的系统 API 仍然需要我们自行处理这些差异,确保系统的兼容性和稳定性。
2. Linux FUSE vs WinFsp FUSE
之前,JuiceFS 一直与 low-level FUSE 接口进行对接,而 WinFsp FUSE 则是一个 high-level FUSE 接口,这两者存在本质的区别。
首先,low-level FUSE 通常使用 inode 作为回调参数,而高级 FUSE 则主要使用路径作为回调参数,这是两者的一个显著差异。然而,差异不仅限于此。
尽管 WinFsp 提供了一个 high-level的 FUSE 回调接口,但其实现逻辑并未完全遵循 Linux 中 libfuse 的实现方式。因此,在使用 WinFsp FUSE 时,我们常常会遇到一种情况:在 Linux 中,某些行为表现得很自然和一致,完全如FUSE文档所述的一致,但在 WinFsp 中,这些行为的回调逻辑却有所不同。这使得我们需要特别注意并解决这些平台之间的行为差异。
3. 黑盒的 Windows 内核
在开发过程中,我们还需要面对一个 黑盒的 Windows 内核。由于 Windows 内核没有开源,且相关文档资料非常有限,调试和观察 Windows 内核的行为变得较为困难。这使得整个开发过程充满了不确定性,对于开发人员来说,Windows 内核的工作机制就像一个封闭的黑盒,需要通过不断的实验和反向工程来理解和适应。
4. 有限的 FSD 文档资源
除了 Windows 内核本身的黑盒特性外,关于文件系统驱动(FSD)的文档资源也非常有限。微软并未公开完整的 Windows 内核开发文档,很多 API 仅由第三方整理,官方文档稀缺。事实上,唯一一本专注于 Windows 文件系统驱动的书籍是 1997 年出版的 《Windows NT File System Internals: A Developer's Guide》。尽管该书中包含大量错误(多数是由于不适用于当前版本),直到今天,Windows 文件系统驱动的开发者仍然只能依赖这本书作为参考。这些有限的文档资源使得我们在排查问题和理解 Windows 文件系统行为时,必须付出更多的时间和精力来收集资料并进行学习。
03 做到了哪些?
接下来,我们将介绍我们在 Beta 版本中进行的具体工作。主要可以分为两个部分:JuiceFS 端的工作以及 WinFsp 端的工作。
1. JuiceFS 部分
工具集
首先,工具集在我们日常使用 JuiceFS 时至关重要。使用者在部署之后,都需要借助工具集来进行性能测试、监控等操作。例如,最基本的性能测试通常会运行 bench 命令。然而,在早期版本中,这个命令在 Windows 平台上无法运行,系统会提示命令不支持 Windows。
此外,挂载文件系统后,我们通常需要观察一些关键的性能指标,例如原始数据的延迟、吞吐量、对象存储的表现,以及分布式缓存的吞吐量等。然而,在之前的版本中,这些监控功能也无法正常使用,系统会提示无法打开 .control
文件。
JuiceFS 1.3 和 企业版 5.2 中,这些问题都得到了修复。目前,工具集已经从几乎完全不可用,发展到了基本可用的状态。
API 兼容性
在早期版本中,一些用户可能会遇到问题,特别是当将 JuiceFS 用作 Git 目录时。有些用户发现直接执行 Git 命令时,操作无法正常执行。问题的主要原因在于错误码的处理不兼容 Windows 系统。我们没有像 Windows 本身那样处理特定的错误码,导致应用程序依赖于某些特定错误码时无法正常工作。这一部分主要涉及到 API 兼容性 的改进。
此外,早期版本中还可能出现挂载状态异常的情况。例如,挂载过程完成后,系统提示挂载失败,实际上文件系统已经挂载;或是挂载成功的提示显示不正确。在当前的 Beta 版本中,我们已经对这些行为进行了修复和优化,包括 Git 命令相关的错误。
本质上,Linux 和 Windows 在错误码方面的差异是难以完全匹配的,因此实现 100% 的一致性非常困难。尽管如此,我们依然通过持续的反馈和改进,不断优化这些兼容性问题,力求提供更稳定的 Windows 版本。
User/permission
在早期版本中,我们的权限管理还存在一些模糊之处。比如,当用户 A 设置了某些权限或在其他区域设置了权限时,我们并没有清晰的预期和统一的处理方式。这导致了一些问题,例如,用户 A 挂载了文件系统后,他希望同事能够读取文件,但当时我们没有明确的方案来处理这种情况。为了实现文件共享,我们曾使用一个名为 as-root 的参数,将所有用户权限提升为 root 权限,这种做法虽然能暂时解决问题,但并不理想。
而在 Beta 版本中,我们对权限管理进行了优化。重新设计了权限机制,明确了权限管理的目标,确保在 Windows API 的正常工作下,尽可能地与 POSIX 行为保持一致。具体来说,我们在没有开启 UID 映射的情况下,确保 Linux 用户的 UID(如 1001)能够在 Windows 系统中正确映射和共享,保证相同 UID 的用户被视为相同的用户。
此外,如果启用了 UID 映射功能,这是一个更为复杂的话题,但我们也将提供支持。总之,我们的目标是确保 Windows 系统的权限管理尽可能与 POSIX 行为一致,为用户提供更清晰、可靠的权限控制。
挂载相关
在早期版本中,挂载操作只能在前台进行,无法支持后台挂载。这导致了一个问题:如果用户希望后台挂载文件系统以便共享给其他用户,他们不得不依赖第三方工具或手动配置。例如,他们可能需要使用微软自带的命令行工具来创建系统服务,或者手写 YAML 配置文件,这样的过程既繁琐又容易出错。
在 JuiceFS 1.3 和 企业版 5.2 中,我们解决了这一问题,直接支持了后台挂载。与 Linux 版本一致,用户可以通过 -d
参数在社区版中实现后台挂载,而企业版默认支持后台挂载。如果用户需要前台挂载,则可以使用 -f
参数。
此外,过去的社区版也不支持将同一个文件系统挂载到多个盘符上。现在,在 企业版 中,我们已实现了这一功能,允许用户将同一个文件系统挂载到多个盘符上。目前,这项功能仅在企业版中可用。
性能提升
在早期版本中,存在一个广泛且严重的问题,那就是小文件的读写性能非常差,文件属性的读取效率也很低。
在 JuiceFS 1.3 和 企业版 5.2 版本中,我们进行了大幅优化,显著提升了小文件的读写性能。具体的优化进展可以通过 bench 工具来测试和反馈,该工具能够提供关于大文件和小文件的读写性能指标。
以阿里云上一个 88 核 16 GB 的机器为例,使用阿里云的 Redis 和自建的 MinIO 进行性能测试。在默认命令行参数下,我们发现小文件的写入性能接近 1.2 版本,而小文件的读取性能则有了显著提升:从 1.2 版本 的每秒 200 个小文件提升至 1.3 版本 的每秒接近 1000 个小文件。
此外,文件属性的读取性能也得到了优化。过去每秒大约只能获取 200 个文件属性,而现在可以达到 4500 次每秒。当然,这一结果受缓存的影响,实际性能可能会有所波动,但总体上优化效果非常显著。

另一项重要的优化涉及路径深度的影响。在 1.2 版本 中,当文件路径深度增加时,性能急剧下降。例如,在三层目录的情况下,读取小文件的性能大幅下降,可能每秒只能读取几十个小文件。而在 1.3 版本 中,即使路径深度达到三层,性能几乎没有显著变化,保持了稳定的表现。这一改进确保了用户在使用过程中,不会因为创建更多目录而导致性能下降。

其他优化与修复
子目录挂载和文件重命名操作在之前的版本中可能无法正常进行,尤其是在重命名时,常常会遇到一些难以解释的错误,导致操作无法完成。现在,这些问题已经得到了优化和修复。此外,可能还有一些其他因素未在此列出,仍在持续改进中。
2. FSD 相关问题修复与定位
POSIX 文件权限映射
在之前的版本中,我们遇到一个与文件权限映射相关的问题,尤其是 0666 权限设置。根据 POSIX 标准,0666 权限意味着文件对于所有用户都是可读可写的。然而,在 Windows 上,即使我们为某个文件设置了 0666 权限,Everyone 组(即所有用户)仍然无法进行覆盖写操作。
这个问题的根本原因在于 WinFsp 目前没有完全处理这些权限映射。即使在 Windows 上设置了类似的权限,实际上它并不会如 POSIX 系统那样让所有用户都能够读取和写入文件。
目前,WinFsp 的版本仍然存在这个问题,我们已经向上游提交 PR 来修复这个问题,期望能够使 Windows 上的行为与 POSIX 系统一致,确保在 Windows 平台上也能正确地处理文件权限。
close to open 一致性
默认情况下,JuiceFS 遵循 close-to-open 一致性模型,这意味着在文件关闭之后,下次打开该文件时,用户能够看到之前写入的数据,即使是在不同的机器上也是如此。
然而,在 WinFsp 及 Windows 系统中,文件关闭的处理方式与此不同。WinFsp 的默认行为并不会在文件关闭时,异步等待 FUSE 应用程序处理完数据。具体来说,当应用程序调用 CloseHandle 关闭文件句柄时,经由 WinFsp 的 Windows 应用程序并不会阻塞在 CloseHandle 的调用处,而是直接返回,哪怕本地还有未上传至云端的数据。这种方式导致了 close-to-open 一致性无法得到保证,甚至本机用户,在下一次打开此文件时,依旧可能读取到的是错误的文件长度信息。
为了解决这个问题,我们已经向 WinFsp 的源代码做出相应调整,以确保在 Windows 平台上也能够维持 close-to-open 一致性,确保数据的同步和一致性不受影响。
内核 cache manager 使用
在 WinFsp 的处理过程中,默认情况下它并未利用内核的 Cache Manager,这直接导致了文件读取性能较差。Cache Manager 通常会负责缓存和预读操作,这有助于提升文件系统的读取效率。然而,在 WinFsp 的默认配置下,Cache Manager 并未得到有效利用,从而影响了系统的性能表现。
为了解决这一问题,我们在 FSD 层面进行了问题定位和修复,并研究了如何在 WinFsp 中正确启用 Cache Manager。我们的目标是通过正确启用和配置缓存管理,显著提升文件读取性能,并优化系统的整体表现。

03 未来计划
我们将持续聚焦 Windows 客户端的可用性,包括 bug 修复、功能扩展以及对软链接(symlink)的支持;性能提升也是我们不断努力的方向。随着使用场景日益复杂,数据规模不断扩大,且涉及的技术点较为广泛,Windows 客户端 的优化将是一个长期过程。
此外,许多用户反馈希望能够与 AD 域账号进行集成。在 Windows 环境下,与 AD 域账号的绑定将是一个非常实用的特性,我们正在研究这一需求。如果大家有相关的建议或希望实现的功能,欢迎与我们讨论。
最后,感谢大家对 Windows 版本的支持,欢迎大家体验和反馈,希望我们能为大家提供更好的使用体验。