生成式 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 延迟仍依赖缓存层补偿 |
给正在选型团队的清单:
- 先算文件数:如果预期超过 10 亿文件,元数据引擎必须选 TiKV/Redis Cluster,单节点 Redis 扛不住。
- 量缓存盘寿命:NVMe SSD 在持续写入缓存场景下寿命消耗快,建议选高 DWPD 型号并监控磨损指标。
- 压测预热时间:
juicefs warmup大目录耗时与网络带宽正相关,训练启动流程要预留预热窗口。 - 规划 QoS 隉流:多业务共享同一 JuiceFS 时,对象存储侧需要按 bucket 或 prefix 做带宽/请求率限制,防止训练吞吐挤掉推理延迟。
- 元数据备份:Redis AOF 或 TiKV 快照必须定期备份,元数据丢失意味着整个文件系统不可恢复——对象存储里的数据块没有元数据就无法还原目录结构。
小米的实践证明了一点:AI 时代的存储问题不是"存多少",而是"怎么让训练、推理、归档三种 IO 模式在同一套系统上各取所需"。JuiceFS 的数据-元数据分离 + 多级缓存架构,给了这个问题的工程解法。上面的示例可以直接在测试环境跑起来,验证后再决定是否往生产推进。