AI 时代统一存储:小米如何用 JuiceFS 扛住千亿文件与 EB 级规模

2026-05-12 19 预计阅读时间:1 分钟
来源:my.oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:13 分钟

生成式 AI 和智能驾驶的爆发,把存储系统逼到了墙角——模型训练要吞吐、推理要低延迟、数据上云要弹性,而传统方案往往只能满足其中一项。小米在过去两年里,把散落的多套存储统一到 JuiceFS 之上,搭建了容量层、性能层和缓存层三层架构,最终扛住了千亿级文件、EB 级存储的规模。这篇文章拆解这套架构的核心设计,并给出可以直接拿去改造的 JuiceFS 部署示例。

为什么统一存储成了 AI 战略的前提

大模型训练一次动辄读取数百万文件(样本图片、标注 JSON、checkpoint),智驾训练更是在海量路采数据上反复迭代。如果每个业务线各自搭建 HDFS、Ceph、NAS,会出现三个硬伤:

  • 数据流转成本高:训练产出要推到推理集群,中间得跨系统拷贝,耗时且易出错。
  • 元数据瓶颈:千亿文件场景下,传统文件系统的目录索引和 inode 管理会先于容量崩掉。
  • 冷热错配:训练要吞吐、推理要延迟、归档要便宜,一套存储无法同时满足。

小米的解法是:用 JuiceFS 作为统一入口,把后端拆成三层,各司其职。

三层架构拆解

容量层——兜底 EB 级规模

容量层负责"存得住"。底层是对象存储(小米内部为自建 S3 兼容集群),JuiceFS 把所有文件数据块写入对象存储,元数据则交给 Redis/TiKV 等独立引擎。这种分离意味着:

  • 对象存储天然弹性,扩容不需要停服。
  • 元数据引擎可以单独调优,不受数据块 IO 的干扰。
  • 千亿文件的 inode 信息只占元数据引擎的内存/磁盘,不挤占数据通道。

关键数字:平台已支撑千亿级文件数量和 EB 级存储规模,这靠的就是数据与元数据彻底解耦。

性能层——喂饱训练吞吐

大模型和智驾训练对带宽极度敏感。容量层的对象存储延迟在毫秒到百毫秒级,直接用会拖慢 GPU 利用率。性能层的核心手段是本地缓存:JuiceFS Client 在计算节点上挂载本地 SSD 作为缓存目录,热点数据先落盘,后续读取直接命中本地 IO。

缓存策略的几个实操要点:

  • 缓存目录选 NVMe SSD:顺序读吞吐可到 GB/s 级,远超网络拉取。
  • 缓存淘汰用 LRU + 分片亲和:同一训练任务的分片尽量缓存到同一节点,减少跨节点重复拉取。
  • 预热机制:训练启动前,用 juicefs warmup 把指定目录预灌到本地缓存,避免首轮迭代冷启动抖动。

缓存层——加速推理与在线访问

推理场景的访问模式不同于训练:单次请求读少量文件(模型权重、配置),但要求 P99 延迟极低。缓存层在性能层之上再加一级——分布式缓存集群(可基于 Redis 或专用缓存中间件),把高频推理依赖的权重文件元数据和热点小块数据缓存在内存中,使推理节点的 open/read 路径几乎不走对象存储。

这一层也承担了多系统接入的简化职责:推理服务、大数据上云任务、训练集群都指向同一个 JuiceFS 挂载点,数据流转从"拷贝"变成"权限切换"。

实践:用 JuiceFS 搭一个三层存储原型

下面给出一个可以在测试环境直接运行的 JuiceFS 部署示例,模拟小米三层架构的核心配置。假设你有一台 Linux 机器(或 VM),已安装 Docker。

第一步:启动元数据引擎和对象存储

# 启动 Redis 作为元数据引擎(生产环境建议用 TiKV 或 Redis Cluster)
docker run -d --name juicefs-meta \
  -p 6379:6379 \
  redis:7 redis-server --appendonly yes --maxmemory 512mb

# 启动 MinIO 作为兼容 S3 的对象存储(模拟容量层)
docker run -d --name juicefs-data \
  -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=admin -e MINIO_ROOT_PASSWORD=admin123 \
  minio/minio server /data --console-address ":9001"

运行后确认服务正常:

docker ps | grep -E 'juicefs-meta|juicefs-data'
# 应看到两个容器处于 Up 状态

第二步:创建 JuiceFS 文件系统

# 下载 JuiceFS 客户端
curl -fsSL https://juicefs.com/releases/juicefs -o juicefs
chmod +x juicefs

# 格式化——元数据指向 Redis,数据块指向 MinIO
./juicefs format \
  --storage minio \
  --bucket http://127.0.0.1:9000/juicefs-bucket \
  --access-key admin \
  --secret-key admin123 \
  redis://127.0.0.1:6379/1 \
  myjfs

输出类似:

2024/xx/xx xx:xx:xx.xxxxxxx [INFO] Meta address: redis://127.0.0.1:6379/1
2024/xx/xx xx:xx:xx.xxxxxxx [INFO] Data use minio(http://127.0.0.1:9000/juicefs-bucket)
2024/xx/xx xx:xx:xx.xxxxxxx [INFO] Volume name: myjfs
...

第三步:挂载并启用本地缓存(模拟性能层)

# 创建本地缓存目录(生产环境挂 NVMe SSD)
mkdir -p /tmp/jfs-cache

# 挂载 JuiceFS,指定缓存目录和大小
./juicefs mount \
  --cache-dir /tmp/jfs-cache \
  --cache-size 10G \
  --writeback \        # 开启回写模式,写入先落本地缓存再异步上传
  redis://127.0.0.1:6379/1 \
  /mnt/myjfs

验证挂载:

df -h /mnt/myjfs
# 应看到 myjfs 挂载点,容量显示为对象存储可用空间

# 写入测试文件
echo "hello unified storage" > /mnt/myjfs/test.txt
cat /mnt/myjfs/test.txt

第四步:预热热点数据(训练前灌缓存)

# 模拟训练数据目录
mkdir -p /mnt/myjfs/training-data
for i in $(seq 1 100); do
  dd if=/dev/urandom bs=1M count=10 2>/dev/null | \
    cat > /mnt/myjfs/training-data/sample-${i}.bin
done

# 预热——把 training-data 目录全部灌入本地缓存
./juicefs warmup /mnt/myjfs/training-data/

# 预热后,后续读取将直接命中本地 SSD,不再走 MinIO
time cat /mnt/myjfs/training-data/sample-1.bin > /dev/null
# 对比预热前后的耗时差异

第五步:推理场景的内存缓存加速(模拟缓存层)

JuiceFS 本身的客户端缓存已经覆盖本地 SSD 级别。对于推理场景要进一步压延迟,可以在挂载参数中叠加内核层面的优化:

# 重新挂载,增加更激进的缓存参数
./juicefs mount \
  --cache-dir /tmp/jfs-cache \
  --cache-size 50G \
  --open-cache 3.0 \     # 文件 open 结果缓存 3 秒,减少元数据查询
  --attr-cache 3.0 \     # 属性缓存
  --entry-cache 3.0 \    # 目录项缓存
  --writeback \
  redis://127.0.0.1:6379/1 \
  /mnt/myjfs

open-cache / attr-cache / entry-cache 三项让短时间内的重复 open/stat 调用直接在内存返回,推理服务频繁读取同一权重文件时 P99 延迟可压到微秒级。

注意:以上示例为单机测试配置。生产部署时,元数据引擎应替换为 TiKV 或 Redis Cluster,对象存储替换为自建/云上 S3 集群,缓存目录挂载 NVMe SSD,并配合 Kubernetes CSI Driver 做集群级挂载。

Kubernetes 集群级挂载参考

小米的 AI 训练和推理跑在 Kubernetes 上,JuiceFS 通过 CSI Driver 注入 Pod。以下 YAML 是一个最小化部署片段,可直接改造使用:

# juicefs-csi-driver 部署(简化版,生产请参考官方 Helm Chart)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: juicefs-csi-node
  namespace: juicefs-system
spec:
  selector:
    matchLabels:
      app: juicefs-csi-node
  template:
    metadata:
      labels:
        app: juicefs-csi-node
    spec:
      containers:
        - name: juicefs-mount
          image: juicedata/juicefs-csi-driver:v0.25.0
          securityContext:
            privileged: true
          env:
            - name: JFS_META_URL
              value: "redis://redis-cluster.juicefs-system:6379/1"
            - name: JFS_STORAGE
              value: "minio"
            - name: JFS_BUCKET
              value: "http://minio.juicefs-system:9000/juicefs-bucket"
            - name: JFS_ACCESS_KEY
              value: "admin"
            - name: JFS_SECRET_KEY
              valueFrom:
                secretKeyRef:
                  name: juicefs-secret
                  key: secret-key
            - name: JFS_CACHE_DIR
              value: "/mnt/ssd-cache"   # 挂载节点本地 NVMe
            - name: JFS_CACHE_SIZE
              value: "100G"
          volumeMounts:
            - name: ssd-cache
              mountPath: /mnt/ssd-cache
      volumes:
        - name: ssd-cache
          hostPath:
            path: /mnt/nvme-ssd        # 节点上的 NVMe SSD 路径
            type: DirectoryOrCreate

训练 Pod 通过 PVC 挂载:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: training-data-pvc
spec:
  accessModes:
    - ReadWriteMany           # 多节点并行读,训练集群刚需
  storageClassName: juicefs-sc
  resources:
    requests:
      storage: 10Ti           # JuiceFS PVC 容量声明为逻辑值,不受物理限制

---
apiVersion: batch/v1
kind: Job
metadata:
  name: model-training
spec:
  template:
    spec:
      containers:
        - name: trainer
          image: your-training-image:latest
          command: ["python", "train.py", "--data-dir", "/data"]
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: training-data-pvc

ReadWriteMany 让多个训练 Pod 同时读同一数据集,这正是统一存储消除数据拷贝的关键——数据一份存底,多节点共享挂载。

落地考量与取舍

小米这套架构不是银弹,选型时有几个边界需要正视:

维度 优势 风险/代价
元数据分离 千亿文件不挤数据通道 Redis/TiKV 需独立高可用运维,元数据引擎宕机 = 全局不可用
本地缓存 训练吞吐提升数倍 缓存盘故障会触发回填抖动;回写模式下数据有短暂不一致窗口
统一挂载 数据流转从拷贝变权限切换 所有业务共享同一底层,容量层压力需要细粒度 QoS 隉流
对象存储后端 EB 级弹性扩容 小文件场景下对象存储的 PUT/GET 延迟仍依赖缓存层补偿

给正在选型团队的清单:

  1. 先算文件数:如果预期超过 10 亿文件,元数据引擎必须选 TiKV/Redis Cluster,单节点 Redis 扛不住。
  2. 量缓存盘寿命:NVMe SSD 在持续写入缓存场景下寿命消耗快,建议选高 DWPD 型号并监控磨损指标。
  3. 压测预热时间juicefs warmup 大目录耗时与网络带宽正相关,训练启动流程要预留预热窗口。
  4. 规划 QoS 隉流:多业务共享同一 JuiceFS 时,对象存储侧需要按 bucket 或 prefix 做带宽/请求率限制,防止训练吞吐挤掉推理延迟。
  5. 元数据备份:Redis AOF 或 TiKV 快照必须定期备份,元数据丢失意味着整个文件系统不可恢复——对象存储里的数据块没有元数据就无法还原目录结构。

小米的实践证明了一点:AI 时代的存储问题不是"存多少",而是"怎么让训练、推理、归档三种 IO 模式在同一套系统上各取所需"。JuiceFS 的数据-元数据分离 + 多级缓存架构,给了这个问题的工程解法。上面的示例可以直接在测试环境跑起来,验证后再决定是否往生产推进。


相关推荐