
一批训练集、特征表、模型文件反复被拉取,每次都从 RustFS 桶走一遍网络,带宽账单和延迟都扛不住。Alluxio 在客户端和 RustFS 之间架一层分布式内存缓存:读过的对象块留在 worker 内存盘里,重复读取直接命中,回源次数肉眼可见地降下来。
什么时候该在 RustFS 前面架缓存
读多写少、数据可重算、对尾延迟敏感的场景最划算,比如特征 serving、训练集预热、多人共用的 Parquet 数据集。要注意的是,Alluxio 只缓存读过的块,写入是穿透的,不会替你做写聚合;数据集几乎每跑必变、且每次都是全新内容时,缓存命中率会很低,这层反而白架。单节点 demo 用 Docker standalone 跑就行,生产别直接拿来上,至少要多 worker 才有冗余。
起一个单节点的 Alluxio
Alluxio 镜像每次只启动一个进程,先起 master 再起 worker。master 管元数据与调度,worker 管实际的数据块缓存,二者加起来靠 --shm-size 2g 撑起内存盘。worker 的内存盘放在 /dev/shm,Docker 默认只给 64MB,必须显式加大,否则 worker 立刻退出报 tmpfs is smaller than the configured size。这里有个硬约束:--shm-size 2g 给的共享内存必须不小于 alluxio.worker.ramdisk.size,否则 worker 起来发现内存盘比配置小就直接退出,所以二者要一起调、且 shm 略大一点留余量。master 本身不缓存数据,只存命名空间与块位置元数据,所以 master 容器不需要大 shm。
docker run -d --name alluxio-master --hostname alluxio --network oo-rustfs_default \
-p 19998:19998 -p 19999:19999 --shm-size 2g \
-e ALLUXIO_JAVA_OPTS="-Dalluxio.master.hostname=alluxio -Dalluxio.worker.ramdisk.size=1G" \
alluxio/alluxio:2.9.4 master
docker exec alluxio /entrypoint.sh worker &
19998 是 master 的 RPC 端口,19999 是 Web UI,挂载和读写都走 master 协调。
把 RustFS 桶挂成 Alluxio 路径
凭证选项必须用完整的 alluxio.underfs.s3.* 键名,短的 s3a.* 或 aws.* 键 CLI 会接受,但 UFS 客户端会忽略,最后报 InvalidAccessKeyId。disable.dns.buckets 强制路径风格,IP 形式的端点必须要它:
docker exec alluxio alluxio fs mount \
--option alluxio.underfs.s3.accessKeyId=<your-access-key> \
--option alluxio.underfs.s3.secretKey=<your-secret-key> \
--option alluxio.underfs.s3.endpoint=http://<your-rustfs-endpoint>:9000 \
--option alluxio.underfs.s3.disable.dns.buckets=true \
--option alluxio.underfs.s3.path.style.access=true \
/rustfs s3://alluxio-demo/
官方用 Alluxio 2.9.4 对 rustfs/rustfs-x86-musl:v2.3.1 验证过这套挂载参数,挂载成功会回 Mounted s3://alluxio-demo/ at /rustfs。一个 Alluxio 集群可以挂多个 RustFS 桶,每个桶挂到不同路径,客户端统一从 Alluxio 读,缓存也按路径各自独立。路径风格(disable.dns.buckets + path.style.access)之所以必须开,是因为 RustFS 的端点通常是 IP 或裸主机名,virtual-hosted 风格会把桶名塞进主机名去解析,在 IP 端点上必然失败,只有 path-style 能把桶名留在 URL 路径里。

读命中与写回都走桶
列举挂载点并读取一个预置对象:
docker exec alluxio alluxio fs ls /rustfs
docker exec alluxio alluxio fs cat /rustfs/rustfs-test.txt
PERSISTED 标记表示事实来源在 RustFS,worker 在首次读取后缓存数据块。要看缓存是否真的生效,可以反复 cat 同一个文件并观察 worker 日志里的 block cached 计数不再增长,说明后续读都命中内存。写入则穿透到底层:
echo "written via alluxio cache to rustfs" > /tmp/rt.txt
docker cp /tmp/rt.txt alluxio:/tmp/rt.txt
docker exec alluxio alluxio fs copyFromLocal /tmp/rt.txt /rustfs/alluxio-write.txt
用 rc ls rustfs/alluxio-demo/ 在 RustFS 侧能看到 alluxio-write.txt,说明写穿透生效。copyFromLocal 会保留源文件的目录层级写进桶,所以不指定目标名时,子目录会原样映射到对象前缀,这点在把一整棵本地目录树搬进桶、再当分布式数据集读时很方便。但要记住,写入走的是穿透语义,落盘即落到 RustFS,worker 内存里那份只是顺带缓存,删了 worker 缓存不影响桶里的数据。
缓存读写类型怎么配
Alluxio 对每一次读写都有「读类型」和「写类型」两个开关,决定数据要不要进 worker 内存。读类型 alluxio.user.file.readtype.default 取 CACHE 时,读过的块会留在 worker 内存供后续命中;写类型 alluxio.user.file.writetype.default 取 THROUGH 时,写入既要落本地缓存也要穿透到 RustFS,取 MUST_CACHE 则只落缓存、不保证回源。上面的 demo 走的是默认行为,生产里按「是否容忍缓存丢失」来选:可重算的训练集用 CACHE/THROUGH 最省带宽,不可重算且要强一致的结果则让写直接 THROUGH。对不能重算的结果,写类型一律取 THROUGH,宁可多走一遍网络也别让数据只躺在易失的 worker 内存里。
缓存策略与边界
缓存只覆盖读过的块,且 worker 内存盘是易失的,容器一重启缓存就清空,下次读又得回源,所以这层是加速而非持久层。多个 worker 之间缓存不共享,某一块只缓存在其中一个 worker 上。生产可以配缓存 TTL 与淘汰策略,让冷块让出内存,但这一切都在 Alluxio 侧,RustFS 桶里永远存的是完整对象。换句话说,Alluxio 挂掉不影响数据,只是退回「客户端直连 RustFS」的慢路径。
命中率怎么看、内存盘怎么估
想确认缓存真的在干活,看 worker 的指标或日志里的块缓存计数,也可以用 alluxio fs getCapacity 看已用内存盘,再用 alluxio fs ls -h 看具体文件是 CACHED 还是 PERSISTED。内存盘大小按「热数据集」来估,而不是全量:只把会被反复读的那部分放得进 ramdisk.size,其余让回源去取。demo 里 1G 内存盘只是能跑起来的下限,生产按实际热集扩容,且 --shm-size 始终留一点余量。单节点 standalone 没有元数据高可用,master 挂了整簇不可用,生产要上内嵌 journal 或 Raft 元数据存储,这一步本指南没覆盖,按需查官方运维文档。
和直连 RustFS 比的账
加这层之后,热读省的是网络带宽和 RustFS 的读请求压力,代价是 worker 的内存成本和一份需要运维的组件。数据集几乎每跑必变、且每次都是全新内容时,命中率很低,这层基本白架,还不如让客户端直连 RustFS 省事。所以上不上缓存,先量一遍「重复读比例」,再决定值不值。

三个配置陷阱
tmpfs is smaller than the configured size 后退出:容器没加 --shm-size 2g,或 ramdisk.size 调得比 shm 大。挂载成功但 fs ls 返回 InvalidAccessKeyId:挂载选项用了短键名,UFS 客户端只认 alluxio.underfs.s3.* 完整键。报 S3 client v2 does not support global bucket access:路径风格没开,补 disable.dns.buckets=true。
接下来该做的
验证通过的挂载,下一步用访问密钥管理给生产换专用凭证。想在同一桶之上配缓存策略、TTL 与多层存储,先看 S3 兼容性说明,再查 Alluxio UFS 文档。官方接入指南在 storage/alluxio,Alluxio 项目见 GitHub,RustFS 仓库在 github.com/rustfs/rustfs。