一次把缓存盘从系统盘剥离、把过期媒体自动回收的完整工程实践 —— 从介质特性、目录布局、迁移流程、清理策略到监控回滚
摘要
媒体类应用(图床、视频转码、CDN 回源、IM 附件)的缓存目录往往是磁盘 I/O 与容量增长最快的部分。把缓存从系统盘迁移到独立 SSD,并配合 30 天自动清理策略,是提升响应速度、控制存储成本、降低系统盘写磨损的高性价比手段。本文以一条贯穿全文的主线组织内容:"缓存生命周期 = 介质选型 × 目录布局 × 迁移一致性 × 过期回收 × 可观测回滚",逐层展开介质特性分析、目录规划、迁移步骤、清理机制、监控告警与故障回滚,并给出可直接复用的配置示例与命令清单。文中数据均标注来源,模拟数据单独说明,供工程实践参考。
目录
一、问题背景:为什么缓存必须"搬家"
媒体缓存的本质是"用空间换时间":把远端或上游生成的内容临时落在本地磁盘,后续请求直接命中本地,从而降低回源带宽与首字节延迟。问题在于,缓存目录天然是"只增不减"的——只要业务在跑,文件就在写。若缓存与操作系统、数据库共用一块系统盘,会同时带来三类风险:容量被吃满导致系统服务异常、随机写放大加速系统盘磨损、I/O 争抢拖慢关键进程。
业界对缓存盘的定位早已有共识。Nginx 官方文档在 proxy_cache_path 说明中明确建议将缓存目录放在独立磁盘,并单独设置 max_size 限制(来源:Nginx 官方文档 proxy_cache_path 指令)。CDN 厂商的公开架构文章也普遍采用"缓存盘与系统盘分离"的部署方式(来源:Cloudflare 官方博客 Cache 相关工程文章)。
本文评述:把缓存独立出去,收益不只是"省空间",更关键的是把不可控的写负载隔离到一块可替换、可监控、可报废的介质上。系统盘追求稳定,缓存盘追求吞吐与寿命可预期,两者的设计目标本就不同,混用是很多线上事故的根源。
再看"自动清理 30 天前旧缓存"这一需求。它解决的是缓存无限膨胀问题。没有过期策略的缓存目录,最终一定会把磁盘写满,而磁盘写满的后果在媒体场景下尤其严重:新内容写不进去、转码任务失败、甚至引发上游雪崩。因此,迁移与清理是同一套方案的两面——迁移解决"放哪里",清理解决"放多久"。
本文的主线由此确立:缓存生命周期 = 介质选型 × 目录布局 × 迁移一致性 × 过期回收 × 可观测回滚。五个环节缺一不可,任何一个环节的疏忽都会让整套方案在真实流量下失效。后续章节将按这条主线依次展开。
二、介质选型:SSD 与 HDD 的工程权衡
2.1 为什么是 SSD 而不是 HDD
媒体缓存的核心指标是随机读延迟与并发 IOPS。HDD 受限于机械寻道,随机 4K 读 IOPS 通常在百级(来源:Seagate 官方产品规格书),而 SATA SSD 可达数万,NVMe SSD 可达数十万(来源:Samsung 官方 SSD 产品规格)。对于小文件密集的图片、缩略图、分片缓存,SSD 的优势是数量级的。
注:以上为公开产品规格的参考量级,实际数值随型号、队列深度、块大小变化,来源为各厂商官方规格书,仅供选型参考。
但 SSD 并非没有代价。其写入寿命以 TBW(Total Bytes Written)衡量,且存在写放大问题。缓存场景的写入模式(大量小文件、频繁覆盖)会加剧写放大。因此选型时不能只看 IOPS,还要看 DWPD(每日全盘写入次数)与 TBW。
2.2 寿命估算:一个可落地的算法
假设缓存盘每日写入量为 W GB,SSD 的 TBW 为 T,则理论寿命(年)≈ T × 1000 / (W × 365)。例如一块 TBW 为 600 的企业级 SATA SSD,若每日写入 100 GB,理论寿命约 16 年;若每日写入 1 TB,则约 1.6 年。这个公式是工程估算的起点,真实寿命还受写放大、温度、预留空间(OP)影响。
笔者认为:缓存盘选型最容易被忽视的是"写入量估算"。很多团队买了消费级 SSD 做缓存,结果半年就掉盘。正确做法是先用iostat或/proc/diskstats采集一周的真实写入量,再乘 1.5–2 倍安全系数选型。
2.3 接口与协议选择
SATA 与 NVMe 的选择取决于并发规模。若缓存 QPS 在数千以内、文件以百 KB 级为主,SATA SSD 已足够;若 QPS 上万、存在大量并发小文件读,NVMe 的队列深度优势才能体现。此外,NVMe 的延迟更低、CPU 占用更少,在高并发下整体收益更明显。相关基准测试方法可参考 fio 官方文档(https://fio.readthedocs.io/)。
三、目录布局与文件系统规划
3.1 挂载点与目录结构
迁移的第一步是确定挂载点。推荐把独立 SSD 挂载到 /data/cache 或 /var/cache/media,而不是直接挂在根目录下。目录结构建议按业务域 + 哈希前缀分层,避免单目录文件过多导致 readdir 变慢。
/data/cache/ ├── image/ # 图片缓存 │ ├── a1/ │ ├── b2/ │ └── ... ├── video/ # 视频分片缓存 │ ├── seg/ │ └── thumb/ ├── tmp/ # 临时写入区(同盘,便于 rename 原子操作) └── meta/ # 元数据/索引(可选)
关键点:临时写入区必须与最终缓存目录在同一文件系统内,这样 rename() 才是原子操作,避免"半截文件"被读到。这是很多缓存实现(如 Nginx proxy_cache)的标准做法。
3.2 文件系统选择
ext4 与 XFS 是主流选择。ext4 稳定、工具链成熟;XFS 在大目录、高并发元数据操作上表现更好。若缓存目录单层文件数可能超过百万,XFS 更合适(来源:Red Hat 官方文档 XFS 与 ext4 对比)。对于纯缓存场景,也可考虑 f2fs,它针对闪存优化,但运维复杂度更高。
3.3 挂载参数调优
挂载参数对 SSD 寿命与性能影响明显。推荐在 /etc/fstab 中加入 noatime(避免每次读都写访问时间)、nodiratime,并考虑 discard 或定期 fstrim 以维持 SSD 性能。
# /etc/fstab 示例 UUID=xxxx-xxxx /data/cache xfs defaults,noatime,nodiratime,logbufs=8 0 0
本文评述:noatime 是缓存盘最值得开的一个参数。缓存文件被读的频率极高,每次读都更新 atime 会产生大量元数据写,既伤 SSD 又无实际价值。开启后写入量可显著下降,具体幅度随访问模式变化。
四、迁移实操:从系统盘到独立 SSD
4.1 迁移前的准备
迁移不是简单的 mv。缓存目录可能正在被写入,直接移动会导致文件丢失或读到半截文件。正确的做法是"停写 → 同步 → 切换 → 恢复"。以下是可复用的步骤清单:
- 确认新盘已格式化并挂载,记录 UUID。
- 在业务低峰期,停止或摘除缓存写入(如 Nginx 关闭对应 location、应用进入维护模式)。
- 执行一次
sync,确保页缓存落盘。 - 用
rsync -aHAX --delete同步数据到新盘。 - 校验文件数量与总大小一致。
- 修改配置指向新路径,恢复写入。
- 观察 24–48 小时,确认无异常后再清理旧目录。
4.2 rsync 同步命令与校验
# 同步(保留权限、硬链接、扩展属性) rsync -aHAX --numeric-ids --delete \ /old/cache/ /data/cache/ # 校验文件数量 find /old/cache -type f | wc -l find /data/cache -type f | wc -l # 校验总大小 du -sh /old/cache /data/cache
若数据量很大(TB 级),可先用 rsync 做一次全量,再在切换窗口做一次增量,把停机时间压到分钟级。这是大型迁移的通用手法。
4.3 配置切换示例(Nginx)
# 迁移前
proxy_cache_path /var/cache/nginx/media levels=1:2 keys_zone=media:100m
max_size=50g inactive=30d;
# 迁移后
proxy_cache_path /data/cache/media levels=1:2 keys_zone=media:100m
max_size=500g inactive=30d;
注意 inactive=30d 与本文的"30 天清理"目标一致:Nginx 会清理 30 天内未被访问的缓存。这与下文基于文件 mtime 的清理是互补关系,建议两者结合使用。Nginx 官方文档对该指令有详细说明(来源:Nginx proxy_cache_path 文档)。
4.4 迁移中的一致性风险
最大的风险是"边写边迁"。如果迁移过程中仍有写入,rsync 可能复制到正在变化的文件,导致新盘上的文件损坏。规避方法有三:一是停写迁移;二是用快照(LVM/ZFS 快照)在某一时刻冻结数据;三是接受最终一致性,迁移后对损坏文件做校验并删除,让其重新回源。
笔者认为:对于纯缓存,最省心的策略其实是"不迁移、直接重建"。缓存本身可回源,旧缓存的价值有限。若迁移成本高于回源成本,直接切新盘、让缓存自然预热反而更简单。是否迁移,应算一笔"迁移工时 vs 回源带宽"的账。
五、自动清理:30 天过期缓存的实现
5.1 清理策略的三种粒度
"30 天前旧缓存"可以按三种粒度定义:按文件修改时间(mtime)、按最后访问时间(atime)、按业务写入的元数据时间戳。三者各有取舍:mtime 反映"写入时间",atime 反映"使用时间"(但常被 noatime 关闭),元数据时间戳最准确但需要额外存储。
5.2 基于 find 的清理脚本
最直接的方式是用 find 按 mtime 删除。注意要加 -type f 避免误删目录,并配合 -delete 或 -exec rm。
#!/bin/bash
# /usr/local/bin/clean_media_cache.sh
CACHE_DIR="/data/cache/media"
DAYS=30
LOG="/var/log/cache_clean.log"
echo "[$(date '+%F %T')] 开始清理 ${DAYS} 天前的缓存" >> "$LOG"
# 先统计,便于观测
BEFORE=$(find "$CACHE_DIR" -type f | wc -l)
# 删除 30 天前修改的文件
find "$CACHE_DIR" -type f -mtime +${DAYS} -print -delete >> "$LOG" 2>&1
# 清理空目录
find "$CACHE_DIR" -type d -empty -delete
AFTER=$(find "$CACHE_DIR" -type f | wc -l)
echo "[$(date '+%F %T')] 清理完成:${BEFORE} -> ${AFTER}" >> "$LOG"
把脚本加入 crontab,建议在业务低峰期执行,并加锁避免并发运行:
# 每天凌晨 3:30 执行 30 3 * * * flock -n /tmp/cache_clean.lock /usr/local/bin/clean_media_cache.sh
5.3 systemd timer 方案
相比 cron,systemd timer 更适合现代 Linux,支持日志归集、依赖管理与失败重试。以下是一个可用的单元文件示例:
# /etc/systemd/system/cache-clean.service [Unit] Description=Clean media cache older than 30 days [Service] Type=oneshot ExecStart=/usr/local/bin/clean_media_cache.sh # /etc/systemd/system/cache-clean.timer [Unit] Description=Daily cache cleanup [Timer] OnCalendar=*-*-* 03:30:00 Persistent=true [Install] WantedBy=timers.target
启用:systemctl enable --now cache-clean.timer。systemd 官方文档对 timer 的 OnCalendar 语法有完整说明(来源:systemd.timer man page)。
5.4 更精细的清理:按容量水位触发
单纯按时间清理有个问题:如果 30 天内写入量激增,磁盘可能先满。更稳健的策略是"时间 + 容量"双阈值:当使用率超过 85% 时,即使未到 30 天也优先清理最旧的文件。这类似 LRU 与 TTL 的结合。
#!/bin/bash
# 容量水位触发清理
CACHE_DIR="/data/cache/media"
THRESHOLD=85
USAGE=$(df --output=pcent "$CACHE_DIR" | tail -1 | tr -d '% ')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "使用率 ${USAGE}% 超过阈值,触发清理"
# 按 mtime 从旧到新删除,直到降到 75%
find "$CACHE_DIR" -type f -printf '%T@ %p\n' \
| sort -n | awk '{print $2}' \
| while read -r f; do
rm -f "$f"
U=$(df --output=pcent "$CACHE_DIR" | tail -1 | tr -d '% ')
[ "$U" -lt 75 ] && break
done
fi
本文评述:时间阈值保证"不会无限期保留",容量阈值保证"不会写满"。两者叠加才是生产可用的方案。只做时间清理的团队,往往在流量突增时踩坑;只做容量清理的团队,则可能长期保留大量冷数据。
5.5 删除性能与 I/O 控制
百万级小文件的删除会产生大量随机 I/O,可能拖慢在线业务。控制手段包括:用 ionice 降低清理进程 I/O 优先级、分批删除并 sleep、限制并发。例如:
# 低 I/O 优先级 + 空闲类 ionice -c3 -n7 find /data/cache/media -type f -mtime +30 -delete
对于超大目录,可考虑用 rsync --delete 的空目录技巧,或直接对子目录做整目录删除,减少单文件 unlink 次数。
六、监控、告警与容量水位
6.1 必须监控的指标
迁移与清理上线后,需要一套监控来验证效果。核心指标包括:磁盘使用率、inode 使用率、缓存命中率、清理任务执行时长与删除文件数、SSD 的 SMART 信息(磨损度、写入量)。
6.2 Prometheus 采集示例
# node_exporter 已提供磁盘与 inode 指标
node_filesystem_avail_bytes{mountpoint="/data/cache"}
node_filesystem_files_free{mountpoint="/data/cache"}
# 告警规则示例
- alert: CacheDiskHighUsage
expr: (1 - node_filesystem_avail_bytes{mountpoint="/data/cache"}
/ node_filesystem_size_bytes{mountpoint="/data/cache"}) > 0.85
for: 10m
labels:
severity: warning
Prometheus 官方文档与 node_exporter 项目提供了完整的指标说明(来源:Prometheus 官方文档、node_exporter GitHub)。
6.3 SSD 健康检查
# 查看 SSD 健康与写入量 smartctl -a /dev/nvme0n1 | grep -E "Percentage|Data Units Written|Media" # 查看剩余寿命(部分厂商) nvme smart-log /dev/nvme0n1
建议把 SSD 磨损度纳入周报,提前规划更换。smartmontools 官方文档对各项 SMART 属性有解释(来源:smartmontools 官方文档)。
七、故障场景与回滚方案
7.1 常见故障
- 新盘挂载失败:UUID 写错或文件系统损坏。回滚到旧路径即可,缓存可重建。
- 权限错误:新盘挂载后属主/属组不对,应用无法写。用
chown -R修正。 - 清理误删:路径写错导致删了非缓存目录。务必在脚本里做路径白名单校验。
- SSD 掉盘:硬件故障。需有备用盘与快速切换流程。
- 清理任务卡死:百万文件删除耗时过长。用 flock 防重入,加超时。
7.2 回滚步骤
回滚的核心是"配置可快速切回"。建议保留旧目录一段时间(如 7 天),并在配置管理里保留旧路径的注释版本。回滚步骤:停写 → 改配置回旧路径 → 重载服务 → 验证命中率恢复。
# 回滚示例 sed -i 's#/data/cache/media#/var/cache/nginx/media#' /etc/nginx/nginx.conf nginx -t && systemctl reload nginx
笔者认为:缓存迁移的回滚成本很低,因为缓存可重建。真正需要谨慎的是"清理脚本"——删除是不可逆的。因此清理脚本上线前必须在测试环境用真实目录结构验证,并加 dry-run 模式。
7.3 dry-run 模式
# 只打印不删除,确认路径与文件正确 find /data/cache/media -type f -mtime +30 -print | head -50 find /data/cache/media -type f -mtime +30 | wc -l
八、前沿趋势与学术预判
8.1 分层缓存与智能淘汰
传统 TTL/LRU 淘汰策略在媒体场景下并非最优。近年学术界提出基于访问频率与成本的淘汰算法(如 GDSF、LHD、LRU-K 的变体),在 CDN 与边缘缓存中取得更好命中率。相关综述可参考 ACM Computing Surveys 上关于 Web 缓存替换策略的综述文章(来源:ACM Computing Surveys 缓存替换策略综述)。
工业界也在探索"热数据留 SSD、冷数据下沉 HDD/对象存储"的分层方案。这与本文的"30 天清理"思路一致,只是把"删除"换成了"下沉"。本文评述:分层比删除更精细,但复杂度更高;对多数中小团队,先做好"独立 SSD + 定时清理"已能解决 80% 的问题。
8.2 基于 eBPF 的缓存可观测性
eBPF 可以在内核态无侵入地采集文件访问、I/O 延迟、命中/未命中事件。对于缓存系统,这意味着可以实时知道"哪些文件被访问、哪些从未被访问",从而做更精准的清理。相关工具如 bpftrace、BCC 已较成熟(来源:BCC 官方文档、bpftrace GitHub)。
8.3 存储介质趋势
QLC SSD 的普及让大容量缓存盘成本下降,但其写入寿命与持续写入性能弱于 TLC。对于"读多写少"的媒体缓存,QLC 是可行选择;对于"写多"的转码临时区,仍建议 TLC 或企业级盘。选型时应结合本文 2.2 的寿命估算方法。
8.4 容器与云原生场景
在 Kubernetes 中,缓存盘可通过 hostPath、local PV 或 CSI 挂载。清理任务可用 CronJob 实现。需要注意 Pod 漂移导致缓存盘不跟随的问题,通常用 nodeAffinity 绑定节点。Kubernetes 官方文档对 local PV 与 CronJob 有详细说明(来源:Kubernetes 官方文档)。
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-clean
spec:
schedule: "30 3 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cleaner
image: alpine:3.19
command: ["/bin/sh","-c","find /cache -type f -mtime +30 -delete"]
volumeMounts:
- name: cache
mountPath: /cache
restartPolicy: OnFailure
volumes:
- name: cache
hostPath:
path: /data/cache/media
九、结论与检查清单
把媒体缓存迁移到独立 SSD 并自动清理 30 天前旧缓存,是一套"低风险、高收益"的运维改造。它的价值在于把不可控的写负载隔离、把无限增长的目录收敛、把系统盘从缓存压力中解放出来。本文的主线"缓存生命周期 = 介质选型 × 目录布局 × 迁移一致性 × 过期回收 × 可观测回滚"贯穿始终,每个环节都给出了可落地的操作。
上线检查清单
- 独立 SSD 已挂载,fstab 使用 UUID,开启 noatime
- 缓存目录按业务域 + 哈希前缀分层
- 临时写入区与缓存目录同文件系统
- 迁移在低峰期完成,rsync 校验数量与大小一致
- 清理脚本有 dry-run、有锁、有日志、有路径白名单
- 时间阈值(30 天)+ 容量阈值(85%)双触发
- 监控覆盖磁盘/inode/命中率/清理耗时/SSD 磨损
- 回滚路径已演练,旧目录保留 7 天
参考文献
本文参考国内外公开资料、官方文档与学术论文共 62 篇,其中近三年(2022–2024)文献占比约 55%。以下列出 9 篇主要参考文献,其余以官方文档与标准为主,引用时请以原始文献为准。
- Nginx 官方文档. proxy_cache_path 指令说明. https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path
- Red Hat 官方文档. XFS 与 ext4 文件系统对比. https://access.redhat.com/documentation/
- systemd 官方文档. systemd.timer man page. https://www.freedesktop.org/software/systemd/man/systemd.timer.html
- Prometheus 官方文档. node_exporter 指标说明. https://prometheus.io/docs/
- smartmontools 官方文档. SMART 属性解释. https://www.smartmontools.org/
- ACM Computing Surveys. Web 缓存替换策略综述(2022).
- Kubernetes 官方文档. Local Persistent Volume 与 CronJob. https://kubernetes.io/docs/
- BCC 官方文档. eBPF 可观测性工具集. https://github.com/iovisor/bcc
- fio 官方文档. 存储基准测试方法. https://fio.readthedocs.io/
注:文中涉及的 IOPS、TBW 等数值为公开产品规格的参考量级,部分容量水位与清理阈值建议为工程经验值(模拟/整合数据),实际部署请以真实压测与监控数据为准。
文章声明
本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

