以往升级 JuiceFS 版本或修改挂载参数时,用户需要重新挂载文件系统,这会暂时中断业务。作为基础服务的文件系统一旦中断,便会影响系统中其他组件的正常运行。为了避免这类问题,我们在最新发布的 JuiceFS v1.2 版本中引入了平滑升级功能,允许用户在不暂停服务的情况下进行重启或升级。
01 使用
平滑升级的使用非常简单,只需要使用新的二进制或者参数,重新挂载即可。
客户端升级
例如,当前有一个正在运行的挂载进程 juicefs mount redis://127.0.0.1:6379/0 /mnt/jfs -d
,用户希望在不卸载挂载点的情况下部署新的 JuiceFS 客户端,可以执行以下步骤:
# 1. 备份当前二进制
cp juicefs juicefs.bak
# 2. 下载新的二进制覆盖当前 juicefs 二进制
# 3. 再次执行 juicefs mount 命令完成平滑升级
juicefs mount redis://127.0.0.1:6379/0 /mnt/jfs -d
动态调整挂载参数
在上述进程中,当用户希望在不卸载挂载点的情况下将日志级别调整为 debug,可以执行以下命令:
# 调整日志级别
juicefs mount redis://127.0.0.1:6379/0 /mnt/jfs --debug -d
02 实现
FUSE 请求处理流程
要达成在不中断读写请求的情况下完成升级,首先需要理解用户读写请求的整个流程。
如上图所示,当用户使用 juicefs mount xxxxx /jfs
并 open /jfs/a.txt
JuiceFS 和操作系统的主要行为如下:
juicefs mount
后,JuiceFS 内部的go-fuse
模块会open /dev/fuse
获取mount fd
- 用户发起
open
系统调用,系统调用进入 VFS 层,VFS 层将请求转入 FUSE 层 - FUSE 层根据协议将
open
请求按照格式写入mount fd
中 go-fuse
模块读取mount fd
并在解析请求后调用 JuiceFS 对应实现go-fuse
将本次请求的处理结果按照格式写入mount fd
- 内核态的 FUSE 模块读取
mount fd
得到本次请求的处理结果
通过以上示例流程可以发现 /dev/fuse
设备文件就是 JuiceFS 用户态文件系统与内核的沟通桥梁,两者通过 juicefs mount
时 go-fuse
模块 open /dev/fuse
设备文件得到的文件句柄 mount fd
进行沟通。内核写入请求,JuiceFS 借助 go-fuse
处理并返回请求,所以两者的关系可以简单的看做客户端-服务器协议,其中内核作为客户端请求服务,用户态的 JuiceFS 扮演着服务器端的角色,处理这些请求。
核心:如何在进程间传递文件句柄 mount fd
?
通过分析 FUSE 请求处理流程,不难发现如果能确保内核请求在过程中不发生丢失或错误,就能顺利完成 JuiceFS 的平滑升级,即使请求被延迟处理也是可以接受的。这里的关键就是维持首次挂载所获取到的文件句柄 mount fd 的一致性。新进程必须继承并使用旧进程的 mount fd
,因为这样对于内核来说一切都没变,所以业务对这个过程无感知。这里需要注意的是 FUSE 设备每次被 open 都会分配新的 session。新的 session 无法访问到之前旧 session open 的资源,所以要做到平滑升级,我们必须保持旧的 mount fd
。
在介绍如何实现 mount fd
的一致性具体操作之前,首先需要了解从 v1.2 开始,JuiceFS 由一个进程拆分为两个进程——守护(daemon)和服务进程(service)。当服务进程异常退出时,守护进程会重新启动服务进程,这个过程中,只读请求不会受到影响,写的请求会失败。平滑升级时新旧守护进程,守护进程与服务进程之间,我们都采用 Unix domain socket 传递文件句柄。
上图展示了平滑升级过程中 mount fd
和状态信息如何传递的流程。具体的操作步骤,从 1 到 7,在图中相应地标记着。
- 首次 mount 时旧服务进程通过
open /dev/fuse
设备拿到mount fd
- 旧服务进程传递
mount fd
给旧守护进程 - 新守护进程从旧守护进程中获取
mount fd
- 新守护进程发送 SIGHUP 信号给旧服务进程,旧服务进程此时会停止接收新的 FUSE 请求,并且 flush 已经写入的数据到对象存储。如果某些请求在 3 秒内没有完成则会中断这些请求。
- 旧服务进程收到 SIGHUP 信号后在
/tmp
目录下生成中间状态文件。 - 新服务进程加载中间状态文件
- 新服务进程从新守护进程获取
mount fd
升级过程中,JuiceFS 会将内存中新写入的数据全部持久化到对象存储,并且将打开的文件句柄保存到磁盘上,新的进程启动后会恢复这些打开的文件,保证后续的 FUSE 操作没有问题。当某个 FUSE 请求未能在升级期间完成,他会被强制中断,可能会对业务产生影响,建议在负载比较低的时候进行升级操作。
经过上面一系列流程后新 JuiceFS 服务进程就已经获取到了旧 JuiceFS 使用的 monut fd,新服务进程处理从 mount fd 中读取到的请求即可。如下图流程所示:
03 注意事项
- 目前 v1.2-beta1 平滑升级仅支持 linux 平台,v1.2 正式发布时将同时支持 MacOS 平台。
- 平滑升级要求新旧进程的 JuiceFS 客户端版本都至少为 v1.2 版本。
- 新的挂载参数中的 FUSE 参数应该与旧的挂载参数保持一致,否则平滑升级会在当前挂载点上继续覆盖挂载。
- 升级过程中,未能 3 秒内完成的 FUSE 请求可能会被中断,某些业务如果不能自动重试中断的文件系统操作可能会受影响。
欢迎大家下载试用:https://github.com/juicedata/juicefs/releases/tag/v1.2.0-beta1