随着大模型流行,GPU 算力资源正变得日益稀缺,传统的“算力跟着存储跑”的策略需要转变为“存储跟着算力跑”。为了确保数据一致性和管理的便捷性,企业通常在特定地区的公有云上选择对象存储作为所有模型数据的集中存储点。当进行计算任务调度时,往往需要人工介入,手动进行数据拷贝和迁移方法不仅成本高昂,还存在管理和维护的复杂性,包括权限控制等问题都极为棘手。

JuiceFS 企业版的 “镜像文件系统” 功能允许用户从一个地区自动复制元数据到多个地区,形成一对多的复制模式。在多云架构下,该功能在确保数据一致性的同时,大幅降低人工运维的工作量。

最新的 JuiceFS 企业版 5.1 中, 镜像文件系统除了支持读取,还新增了可直接写入的功能。本文将探讨镜像文件系统的读写实现原理。

01 为什么需要镜像文件系统

让我们设想这样一个场景,某用户的文件系统部署在北京,但北京地区的 GPU 资源供给不足,而该用户在上海还有可用的 GPU 资源。这时用户想在上海运行模型训练任务,有两个简单的方案:

  1. 直接在上海挂载北京的文件系统。理论上来说,只要北京与上海之间的网络连接顺畅,上海的客户端确实就能访问数据以进行训练。然而实际情况是,文件系统的访问通常涉及到频繁的元数据操作,而由于两地的网络延迟较大,性能结果往往都无法达到预期。
  2. 在上海建立新的文件系统,在训练前拷贝所需数据集到上海。这样做的优点是可以保证上海训练任务的性能。但缺点也是很明显的,一方面构建新文件系统需要较高的硬件成本,另一方面每次训练前同步数据也提高了运维的复杂性。

综上所述,这两个简单的方案都无法令人满意。为此 JuiceFS 企业版提供了镜像文件系统功能。它允许用户为已有文件系统创建一个或多个完整的镜像,这些镜像会自动从源端同步元数据,这样在镜像区域的客户端可以就近访问文件系统,来得到高性能的体验。由于可以只镜像元数据,并且同步过程是自动的,因此相较于之前提到的方案二而言,镜像文件系统在成本与运维复杂性上都有明显的优势。

02 镜像文件系统原理

JuiceFS 企业版的架构与社区版相似,都包括客户端、对象存储以及元数据引擎。区别在于社区版的元数据引擎通常采用第三方数据库如 Redis、TiKV、MySQL 等,而企业版则配备了自研的高性能元数据服务,其中的元数据引擎由一个或多个 Raft 组组成,其架构图如下:

得益于元数据与数据分离的架构设计,用户在创建镜像文件系统时可以独立选择是否镜像元数据和是否镜像数据。两者皆配置镜像的架构如下:

此时,镜像的元数据服务其实跟源端的元数据服务同属一个 Raft 组,只是它们的角色是 learner。在源端发生元数据更新时,服务会自动推送变更日志到镜像端,并在镜像服务中进行回放。这样,镜像文件系统的存在并不会影响源端文件系统的性能表现,只是镜像的元数据版本会略落后一点点。

数据的镜像也是采用异步复制的方式,由指定配置的节点进行自动同步。不同的是,对镜像区域的客户端而言,它仅访问本区域的元数据,但是可以同时访问两个区域的对象存储。实际读取数据时,客户端优先从本区域读取;如果查找不到所需的对象,再尝试从源端区域读取。

一般而言,数据本身的体量较大,再拷贝一份的成本也比较高,因此另一种更推荐的方式是仅镜像元数据,并且在镜像区域构建一套分布式缓存组来提升读取数据的速度,示意如下:

JuiceFS 镜像文件系统推荐使用方法:两区域共用同一个对象存储,镜像区域搭建分布式缓存组来提升性能

这种使用方式尤其适合模型训练等可以提前准备数据集的场景。用户在执行训练任务前,先通过 juicefs warmup 命令将所需数据对象拉取到镜像区域的缓存组中,接下来的训练就能在镜像区域内完成,且性能与在源端(假设也配置了类似的分布式缓存组)基本一致。

03 实验性新功能:可写镜像文件系统

在之前的版本中,镜像客户端默认为只读模式,因为镜像元数据本身只支持读取,所有的修改操作必须在源端执行。然而,随着用户需求的增加,我们注意到一些新的使用情况,例如在数据训练过程中产生的临时数据。用户希望避免维护两个不同的文件系统,并期望镜像端也能支持少量写操作

为了满足这些需求,我们在 5.1 版本中引入了 “可写镜像文件系统” 功能。在设计这项功能时,我们主要考虑三个方面:首先是系统的稳定性,这是必须保证的;其次是两端数据的一致性;最后是写入的性能。

最初,我们探索的一种直接方案是允许元数据镜像也能处理写操作。然而,在开发中我们发现,当需要将两端的元数据更新进行合并时,会面临非常复杂的细节处理和一致性问题。因此我们还是维持 “仅源端元数据可写” 的设计。为了处理镜像客户端的写请求,有两个可选的方案:

方案一:客户端将写请求发送至镜像的元数据服务,然后由其转发到源端。源端接收到请求后开始执行操作,并在完成后将元数据同步回镜像端,并最终返回。这个方法的优点是客户端操作简单,只需发送请求并等待响应。然而,这样会使元数据服务的实现变得复杂,因为需要管理请求的转发和元数据的同步。此外,由于链路较长,任何环节的错误都可能导致请求处理出错。

方案二:客户端不仅连接镜像的元数据服务,还直接连接源端的元数据服务。客户端内部进行读写分离,读请求仍然发送至镜像端,但将写请求发送至源端。这种方法虽然使客户端的处理逻辑复杂化,但简化了元数据服务的实现,让它们仅需做很小的适配改动即可。对整个系统而言,这样的做法稳定性也更高。

考虑到服务的简洁性和可靠性,我们最终选择了方案二,具体如下图所示。相较于原来的架构而言,这个方案主要多了一条镜像客户端发送写请求到源端元数据服务的流程。

以下将以创建一个新文件(create 请求)为例对此流程进行详细的介绍。假设源端和镜像端的元数据服务分别是 A 和 B,镜像客户端为 C,请求的完成大致分为 5 步:

  1. 客户端发送写请求:C 首先将创建文件的 create 请求发送至 A。
  2. 源端服务响应:A 在处理请求后,发送 create OK 告知 C 文件已成功创建,并在响应中附带 A 的元数据版本号(假设为 v1)。
  3. 变更日志推送:A 在发送回复给客户端的同时,也会立即生成一条变更日志,并将其推送给 B。
  4. 客户端发送等待请求:C 接收到源端的成功回复后,会检查自己的镜像元数据缓存,看其版本是否也达到了 v1。如果没有,客户端会发送一条 wait 消息给 B,并附上版本号 v1。
  5. 镜像端服务响应:B 收到等待消息后,检查自己的元数据版本。如果已经达到 v1,则立即回复 wait OK 给 C;否则的话就将请求放入内部队列,等自己的版本号更新到 v1 以后再发送回复。

C 在第 4 步确认镜像版本已经达到 v1,或者第 5 步收到 wait OK 后返回给上层应用。无论哪种情况,都表示 B 已经包含了本次 create 的修改,因此后续 C 在读取时,就能访问到最新的元数据。另外,由于步骤 2 和 3 几乎是同时发生的,所以大部分情况下 wait 消息都能被立即处理并返回。

镜像客户端的读操作也有类似的检查版本的机制。具体而言,C 在发送读请求前,会先比较其缓存中源端服务和镜像端服务的元数据版本号;如果源端的版本号更新,则会先发送 wait 消息给 B,等到其版本也更新上来后再处理原来的读请求。遗憾的是,C 缓存的源端版本号并不一定是最新的(比如其长时间未发送过写请求的情况),也就是说该机制只是尽可能地让 C 能读到较新的数据,但并不保证其一定是最新的(可能会有小于 1 秒的滞后,与原有的只读镜像相同)。

最后,我们通过一个稍复杂些的读写混合的例子,来简要说明使用 JuiceFS 镜像文件系统给用户带来的直接收益。

需求是客户端 C 希望在 /d1/d2/d3/d4 目录下创建一个新文件 newf。按照文件系统的设计,C 需要逐级查找路径上的每一个目录和文件,并在确认文件不存在后再发送创建请求。现假设 C 到 A 和 B 的网络延迟分别是 30ms 和 1ms,C 尚未建立元数据缓存,并且忽略 A 和 B 的请求处理时间。

使用镜像文件系统的情况:C 的读请求都由 B 处理,只有最后的创建文件请求需要发往 A。总耗时大概需要 1 * 2 * 6(mirror lookup) + 30 * 2(source create) + 1 * 2(mirror wait) = 74ms。

没有使用镜像文件系统的情况:如果直接在镜像区域挂载源文件系统,C 的每个请求都需要跟 A 交互,那么总耗时就需要 30 * 2 * 6(source lookup) + 30 * 2(source create) = 420ms,是前者的 5 倍还多。

04 小结

在 AI 研究中,由于 GPU 资源的成本极高,多云架构已成为众多企业的标配。通过使用 JuiceFS 镜像文件系统,用户可创建一个或多个完整的文件系统镜像,这些镜像会自动从源端同步元数据,使得镜像区域的客户端能够就近访问文件,从而提供高性能并减少运维工作量。

在最新的 JuiceFS 5.1 版本中,我们对镜像文件系统进行了重要的优化,新增了允许写入的功能,使得企业能够在任何数据中心通过统一的命名空间访问数据。同时在保证数据一致性的前提下,享受就近缓存的加速效果。希望通过这篇文章分享的实现思路与尝试,为用户提供一些见解与启发。

详解 JuiceFS 在多云架构下的数据同步与一致性的更多相关文章

  1. Hadoop详解(06) - Yarn平台架构和资源调度器

    Hadoop详解(06) - Yarn平台架构和资源调度器 Yarn平台架构 Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于一个分布式的操作系统平台,而MapReduce等运算程 ...

  2. Mysql加锁过程详解(9)-innodb下的记录锁,间隙锁,next-key锁

    Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...

  3. MapReduce过程详解(基于hadoop2.x架构)

    本文基于hadoop2.x架构详细描述了mapreduce的执行过程,包括partition,combiner,shuffle等组件以及yarn平台与mapreduce编程模型的关系. mapredu ...

  4. Netty4详解三:Netty架构设计(转)

    http://blog.csdn.net/suifeng3051/article/details/28861883?utm_source=tuicool&utm_medium=referral ...

  5. PullToRefresh使用详解(一)--构建下拉刷新的listView

    前言:前几天写了篇关于PullToRefresh控件的DEMO导入的博客,但由于当时没有用到,所以就没细往下讲,现在开始到了实战阶段,用到了PullToRefresh的listView样式,网上有讲的 ...

  6. 多云架构下,JAVA微服务技术选型实例解析

    [摘要] 本文介绍了基于开源自建和适配云厂商开发框架两种构建多云架构的思路,以及这些思路的优缺点. 微服务生态 微服务生态本质上是一种微服务架构模式的实现,包括微服务开发SDK,以及微服务基础设施. ...

  7. Redis面试热点工程架构篇之数据同步

    温馨提示 更佳阅读体验:[决战西二旗]|Redis面试热点之工程架构篇[2] 前言 前面用了3篇文章介绍了一些底层实现和工程架构相关的问题,鉴于Redis的热点问题还是比较多的,因此今天继续来看工程架 ...

  8. 详解Windows Server 2008 R2下安装Oracle 11g

    本篇文章转载 http://www.it165.net/database/html/201212/3385.html 一.安装前的准备工作: 1. 修改计算机名: 服务器的计算机名称对于登录到Orac ...

  9. 40+倍提升,详解 JuiceFS 元数据备份恢复性能优化之路

    JuiceFS 支持多种元数据存储引擎,且各引擎内部的数据管理格式各有不同.为了便于管理,JuiceFS 自 0.15.2 版本提供了 dump 命令允许将所有元数据以统一格式写入到 JSON 文件进 ...

  10. tcl/tk实例详解——返回一个文件夹下所有文件的绝对路径

    http://blog.csdn.net/dulixin/article/details/2133840 #所有代码如下,使用注释的方式讲解脚本#修改好文件夹和保存结果路径,可以把本文件直接拷贝进tc ...

随机推荐

  1. 阿里2021年春季实习笔试题(最后一道大题)(2020 China Collegiate Programming Contest, Weihai Site) (C. Rencontre codeforces.com/gym/102798)

    实验室的慕师弟phd马上要毕业了,正准备先找个实习,投了阿里2021年春季实习的招聘,遇到最后一道编程大题没有思路事后找到了该题的最原始出处,即 2020 China Collegiate Progr ...

  2. [CEOI2011] Matching 题解

    前言 题目链接:洛谷. 在上一题之后,模拟赛又放了一道 KMP 重定义相等的问题,但是寄了,故再记之. 题意简述 现在给出 \(1 \sim n\) 的排列 \(p\) 和序列 \(h_1, h_2, ...

  3. [COCI 2023/2024 #2] Zatopljenje 题解

    UPDATE on 2024.4.25 改掉奇怪压行码风,并稍作排版. 前言 题目链接:洛谷. 题目分析 首先发现区间中的个数等于 \(\texttt{高度大于 x 的位置的个数} - \texttt ...

  4. [学习笔记] 树链剖分(重链剖分) - 图论 & 数据结构

    树链剖分 树链剖分,用于解决一系列的树中链上问题的算法(数据结构).其实对于树链修改和树链求和问题可以使用更加方便的树上差分解决,但是对于像求树链最大(小)权值之类的更复杂的问题,差分就显得不够用了. ...

  5. dubbo序列化问题(三)子类覆盖父类字段hession反序列化获取不到 转

    在进行dubbo开发中遇到一个问题,当是用hession2进行序列化时,子类和父类有相同的字段时,hession2反序列化获取不到该字段数据,如下: import java.io.Serializab ...

  6. 从海量信息中脱颖而出:Workflow智能分析解决方案,大语言模型为AI科技文章打造精准摘要评分体系(总篇章)

    从海量信息中脱颖而出:Workflow智能分析解决方案,大语言模型为AI科技文章打造精准摘要评分体系(总篇章) 1.简介 该项目整合了编程.AI.产品设计.商业科技及个人成长等多领域的精华内容,源自顶 ...

  7. 能否自定义一个String类使用

    先说下结论,可以自定义包名不为java.lang的String类,区别包名是可以正常使用的. 包名不为java.lang package com.seven.jvm; public final cla ...

  8. 十五分钟两百行代码,手写一个vue项目全局通用的弹框

    前言: 我们在写vue项目时,弹框是非常常用的组件,并且在同一个项目中,弹框大多类似.所以我们可以抽离封装出一个通用的弹框: 因为vue3可向下兼容,所以作者这边会使用vue2的写法,vue3写法大同 ...

  9. 树上倍增求 LCA 模板

    void dfs(int x,int fa,int d){ deep[x]=d;dp[x][0]=fa; for(int i=1;i<=lg2[deep[x]];++i){ dp[x][i]=d ...

  10. 在 Windows 中启用 Administrator 帐户

    打开管理员终端. 启用: net user administrator /active:yes 关闭: net user administrator /active:no