如何在香港机房落地“课件”冷热分层:用 Ceph/MinIO 结合 NVMe 的高命中率与低成本实践

凌晨 3 点过十,我把工牌贴在胸前,推开北角那扇总也不太好使的防火门。走廊里空调轰鸣。今天要把“课件”分发系统的冷热分层上线——白天同学们点开 PPT/PDF/视频的时候再也别卡;夜里把冷数据挪到便宜的盘上省钱。
我随手摸了下新装的 U.2 NVMe,温度还好;交叉检查了 ToR 交换机两条 10GbE 上联的 LACP,绿色闪烁得很扎实。凌晨 5 点前,我得把 Ceph 的冷层和 NVMe 的热层跑顺,还要留出回滚窗口。
下面,我把完整的实操从“架构选择—部署—优化—观测—故障与修复—成本与命中率模型”一口气讲完。全文基于 CentOS 7 的环境(我们客观上受限于某些历史镜像和内网依赖),同时给出 Ceph RGW 方案 与 MinIO 方案 两条路径:我最终线上使用了 Ceph 冷+Nginx(NVMe)热缓存,但把 MinIO 的落地细节也一并写出来,方便你按团队栈选择。
业务目标与设计取舍
目标
- 学期高峰(晚 8–11 点)P95 ≤ 80 ms、P99 ≤ 150 ms(静态课件 PDF/MP4/Zip 为主)。
- 峰值 8–12k RPS,长期月均 3–5 PB 级别访问。
- 热数据(7 天内、Top 20% 课件)命中率 ≥ 85%;冷数据走低成本容量层。
- 三年 TCO 降 35%+,盘位与电力在香港机房尤需精打细算。
两条技术路线(均基于 NVMe 热层)
Ceph RGW(对象存储) + HDD 冷层 + NVMe 前置缓存(Nginx proxy_cache)
- 热层 = 前端 NVMe 缓存(读放大强、命中快)
- 冷层 = Ceph OSD(HDD + Erasure Coding)
- 迁移 = RGW Lifecycle(按“天数/前缀/标签”转冷池)
- 优点:成熟、可大规模、成本结构清晰;缺点:学习曲线略陡。
MinIO 分布式(热集群用 NVMe、冷集群用 HDD) + ILM 远端转储
- 热层 = MinIO(EC) 跑 NVMe;冷层 = 另一组 MinIO(EC) 跑 HDD
- 迁移 = MinIO ILM Transition 到远端(冷集群)
- 优点:部署快、S3 生态友好;缺点:两套集群与跨集群网络需设计好容灾。
为什么不用 Ceph 的缓存分层(cache tier)?
旧式 cache tier 已不推荐;我们采用“前端代理缓存 + 后端存储分层(生命周期)”的组合,简单、可控、可观测。
机房与硬件参数(香港实配)
机柜与网络
- 机柜:42U,双市电,UPS,A/B 路供电。
- ToR:2× 10/25GbE 交换机(我们实际跑 10GbE,日后可上 25GbE)。
- 服务器对接:Bond(LACP) + VLAN,公有/集群网络分离。MTU 1500(后文有坑)。
服务器分层
- 前端缓存/网关节点(3 台)
- CPU:Xeon Silver 4310(或同级)
- 内存:64–128 GB
- NVMe:2× 3.84 TB U.2(RAID0 做缓存目录或独立挂载)
- 网卡:2× 10GbE
- 角色:Nginx(或 MinIO 热集群节点)
存储节点(6 台)
- CPU:Gold 5220(或同级)
- 内存:128–256 GB
- HDD:8× 18 TB 7200RPM(SATA/SAS 混编避免同批次故障)
- NVMe(小容量):1× 800 GB(BlueStore DB/WAL)
- 网卡:2× 10GbE
- 角色:Ceph OSD(或 MinIO 冷集群节点)
电力与散热
- NVMe 温控目标:< 60°C;HDD 扇区错误/坏道监控;
- U.2 托架风道要关注(后文“坑”内有细节)。
架构总览(ASCII 示意)
[Users/Clients]
|
[CDN/或直连]
|
[Nginx proxy_cache on NVMe] <-- 热层(命中 ≥85% 目标)
| \
| \ (可选:MinIO 热集群 on NVMe)
|
[Ceph RGW S3 Endpoints] <-- 统一对象访问
|
[Ceph Pools]
├─ hot-placement (replicated on NVMe OSD) [可选/小规模]
└─ cold-placement (EC k=6 m=3 on HDD OSD) [主容量层]
^
| (RGW Lifecycle Transition by prefix/tags/days)
实际线上:热缓存在 Nginx,后端只用 冷池(EC on HDD);极少量高频对象可单独放在 NVMe 小池(非必须)。
系统与网络基础调优(CentOS 7)
内核 & 系统
# 文件句柄与连接
echo "* soft nofile 1048576" >> /etc/security/limits.conf
echo "* hard nofile 1048576" >> /etc/security/limits.conf
# sysctl(网络与队列)
cat >> /etc/sysctl.d/99-tuning.conf <<'EOF'
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_rmem=4096 87380 268435456
net.ipv4.tcp_wmem=4096 65536 268435456
net.core.somaxconn=65535
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=10
vm.swappiness=1
EOF
sysctl -p /etc/sysctl.d/99-tuning.conf
Bond + VLAN(示意)
# ifcfg-bond0
BONDING_OPTS="mode=802.3ad miimon=100 lacp_rate=1 xmit_hash_policy=layer3+4"
# 分别为公有/集群 VLAN 配置 ifcfg-bond0.<vid>,保持 MTU=1500(后文讲为什么别盲目开 9000)
NVMe 挂载(ext4/xfs 皆可)
mkfs.xfs /dev/nvme0n1
mkdir -p /var/cache/nginx
echo "/dev/nvme0n1 /var/cache/nginx xfs defaults,noatime,nodiratime 0 0" >> /etc/fstab
mount -a
方案 A:Ceph RGW 冷层 + Nginx(NVMe) 热缓存
1) Ceph 集群部署(CentOS 7,Nautilus,ceph-deploy)
注:CentOS 7 环境更稳的是 Nautilus(v14)。需要所有节点 NTP 同步,禁用防火墙/SELinux(或放行)。
# 在一个部署节点(含免密 SSH):
yum install -y python2 python-pip
pip install ceph-deploy==2.0.1 # 与 Nautilus 匹配
# 预装 Ceph 仓库
cat > /etc/yum.repos.d/ceph.repo <<'EOF'
[ceph-noarch]
name=Ceph noarch
baseurl=https://download.ceph.com/rpm-nautilus/el7/noarch
enabled=1
gpgcheck=0
EOF
# 创建集群
mkdir ~/ceph-cluster && cd ~/ceph-cluster
ceph-deploy new mon1 mon2 mon3
# ceph.conf 基本项
cat >> ceph.conf <<'EOF'
osd_pool_default_size = 3
osd_pool_default_min_size = 2
mon_allow_pool_delete = true
osd_memory_target = 4294967296 # 4G
bluestore_cache_size_hdd = 1073741824 # 1G
public_network = 10.0.10.0/24
cluster_network = 10.0.20.0/24
EOF
ceph-deploy install --release nautilus mon1 mon2 mon3 osd[1-6]
ceph-deploy mon create-initial
# 准备 OSD(示例:HDD 作为 data,NVMe 作为 db/wal)
ceph-deploy osd create --bluestore --data /dev/sdb --block-db /dev/nvme0n1 osd1
# 其余节点类推……
创建冷层 EC 池与 RGW
# EC 配置:k=6, m=3 适合 6~9 台/盘宽,按实际盘宽调整
ceph osd erasure-code-profile set ec63 k=6 m=3 crush-failure-domain=host
ceph osd pool create cold.ec 128 128 erasure ec63
ceph osd pool application enable cold.ec rgw
# 安装 RGW(3 节点)
ceph-deploy rgw create rgw1 rgw2 rgw3
# RGW placement:将默认 bucket 指向 cold.ec
radosgw-admin zonegroup get > zg.json
radosgw-admin zone get > zone.json
# 在 zone.json 的 placement_targets 中新增 cold-placement,指向 cold.ec
# 并将 default_placement 改为 cold-placement
radosgw-admin zone set --infile zone.json
radosgw-admin period update --commit
要不要 NVMe 热池?
可选:为极少量超热对象建 replicated NVMe 池(size=2),另建 placement hot-placement,配合对象标签(如 hot=true)或专用前缀,以 Lifecycle 不转冷;但我们线上依赖 Nginx 缓存 即可达到 85%+ 命中,不再复杂化后端。
2) RGW Lifecycle(N 天后转冷、或按前缀/标签)
{
"Rules": [
{
"ID": "cold-after-7d",
"Filter": { "Prefix": "courseware/" },
"Status": "Enabled",
"Transitions": [
{ "Days": 7, "StorageClass": "cold-placement" }
]
}
]
}
上传方式(S3 兼容):
aws --endpoint-url http://rgw1:80 s3api put-bucket-lifecycle-configuration \
--bucket cw-bucket --lifecycle-configuration file://lifecycle.json
3) 前端 Nginx 基于 NVMe 的热缓存
# /etc/nginx/conf.d/cw.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cwcache:200m
max_size=2500g inactive=7d use_temp_path=off;
map $request_method $purgeable { "PURGE" 1; default 0; }
server {
listen 80 reuseport;
server_name cw.example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache cwcache;
proxy_cache_key "$scheme://$host$request_uri";
proxy_cache_valid 200 302 7d;
proxy_cache_valid 206 1d; # 断点续传
proxy_ignore_headers Set-Cookie;
add_header X-Cache $upstream_cache_status;
proxy_pass http://rgw-upstream; # 指向 RGW VIP/Upstream
}
# 手动清理(灰度期有用)
if ($purgeable) { proxy_cache_purge cwcache "$scheme://$host$request_uri"; }
}
upstream rgw-upstream {
server rgw1:80 max_fails=3 fail_timeout=10s;
server rgw2:80 max_fails=3 fail_timeout=10s;
server rgw3:80 max_fails=3 fail_timeout=10s;
keepalive 100;
}
缓存预热(开学周)
# 预热 Top N 清单
cat hotlist.txt | xargs -n1 -P16 -I{} curl -s -o /dev/null "http://cw.example.com/{}"
4) Ceph/网络关键优化
- OSD:osd_memory_target=4G(HDD),NVMe 小池则 2–3G;bluestore_cache_* 按介质区分;
- CRUSH:failure-domain=host,不同机位/机柜打散;
- 网络:LACP xmit_hash_policy=layer3+4,RGW keepalive=100,somaxconn 提升;
- 对象并发:RGW rgw_thread_pool_size=2048(视 CPU/内存),限制单连接带宽防止缓存雪崩。
方案 B:MinIO 热冷双集群 + ILM 迁移(可选)
1) 分布式 MinIO(热集群 on NVMe)
四节点示例,每节点 2× NVMe:
useradd -r minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio && mv minio /usr/local/bin/
cat >/etc/systemd/system/minio.service <<'EOF'
[Unit]
Description=MinIO
After=network.target
[Service]
User=minio
Group=minio
Environment="MINIO_VOLUMES=http://node1/export{1...2} http://node2/export{1...2} http://node3/export{1...2} http://node4/export{1...2}"
Environment="MINIO_OPTS=--address :9000 --console-address :9001 --quiet"
ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
LimitNOFILE=1048576
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now minio
2) 冷集群(HDD)与远端目标
# 在热集群上配置远端(指向冷集群)
mc alias set hot http://hot:9000 ACCESSKEY SECRETKEY
mc alias set cold http://cold:9000 ACCESSKEY SECRETKEY
mc admin bucket remote add hot/cw-bucket http://cold:9000/cw-bucket \
--service replication --replicate "delete,delete-marker,metadata"
# ILM:7 天转储
mc ilm add hot/cw-bucket --transition-days 7 --transition-tier "cold-tier"
MinIO 新版中“tier/remote”命名可能有差异,基本思路是 把冷集群作为远端,按 ILM 迁移。
前端依旧可使用 Nginx proxy_cache 在 NVMe 上做热读,MinIO 本身也可直接顶流量,但我们在高峰时更偏爱“前端统一缓存 + 后端存储聚合”的模式。
观测:命中率、延迟与吞吐(上线一周数据)
| 指标 | 上线前(直读存储) | 上线后(Nginx+NVMe 缓存) |
|---|---|---|
| 峰值总 RPS | 8.4k | 11.6k |
| 缓存命中率(整体) | - | 87.3% |
| P95(静态 PDF 2–10MB) | 180 ms | 62 ms |
| P99(静态 PDF 2–10MB) | 410 ms | 136 ms |
| 后端 RGW 请求量(峰值) | 8.4k | 1.9k |
| Ceph OSD 后端读吞吐(峰值) | 5.2 GB/s | 1.1 GB/s |
| NVMe 写放大(缓存段) | - | 1.4×(按小时均值) |
采集来源:Nginx log → Prometheus(exporter) → Grafana;RGW/OSD 指标用 ceph mgr/prometheus;命中率按 $upstream_cache_status 汇总。
成本与容量规划(三年 TCO 粗算)
假设
- NVMe 3.84TB/U.2 单价 X(USD),功耗 ~8–12W;
- HDD 18TB 单价 Y(USD),功耗 ~6–9W;
- 香港电价按 0.2 USD/kWh 粗估;
- 机柜与带宽成本略。
结果(以 6 存储节点、48 盘 HDD、冷层 EC k=6 m=3)
- 有效冷容量 ≈ 18TB×48×(6/9)= 576 TB(扣除校验和 CRUSH 打散)
- NVMe 热缓存 3 台×(2×3.84TB) ≈ 23 TB(可命中 Top 20% 对象)
- 按 85% 命中,后端读压力降至 ~15%,电力与盘磨损显著下降。
- 三年摊销:在同样的服务质量下,TCO 下降 ~35–42%(取决于 NVMe 比例与机柜/电价)。
关键不是 NVMe 多,而是 命中率曲线:课件访问非常“尖峰 + 长尾”,Top 10–20% 对象贡献 80%+ 的请求。把它们留在前端 NVMe,就赢了。
踩过的坑与现场解法
MTU 9000 带来的“偶发性超时”
现象:跨交换机链路特定目的网段偶发 200–300ms 抖动。
根因:上游运营商与某跳点不支持巨帧,路径 MTU 不一致。
解法:全链路回退 MTU=1500,吞吐受损很小,抖动彻底消失。
U.2 NVMe 温度触发降频
现象:晚高峰写入穿透上升,缓存写尾延迟跳高。
根因:风道被理线带半遮,盘托架局部回风不畅。
解法:改理线、换高压风扇、增一组导风罩;温度由 68°C 降至 56°C。
Ceph OSD 内存顶满 OOM
现象:HDD OSD 偶发 OOM。
根因:osd_memory_target 未生效 + 监控刷新峰值。
解法:固定 osd_memory_target=4G,限制 mgr 模块抓取频率,问题消失。
Nginx 缓存目录 inode 打满
现象:max_size 还剩很多,写入失败。
解法:levels=1:2 + 预估对象数量增大 inode(ext4 时 mkfs -N);我们最后统一 XFS。
RGW Lifecycle 没触发
现象:对象未转冷。
根因:规则里 Prefix 少了末尾 /;
解法:加 / 并 period update --commit,次日凌晨生效。
LACP 单流瓶颈
现象:单连接速率卡在单链路上限。
解法:xmit_hash_policy=layer3+4,并行连接足够多即可;关键路径用多连接(Range/多段)。
回滚与演练
灰度开关:Nginx 里保留直连后端的 upstream 并加 map 做分流,命中低于 70% 或后端 5xx 提升即刻切回。
数据一致性:对象写入路径始终直达 RGW;缓存只做 GET,不缓存非 200/206。
灾备:Ceph 三监视器多故障域;MinIO 冷集群(若选)在另一机柜;每日做 radosgw-admin bucket check/mc admin heal。
日常运维要点(Checklist)
- 每日查看:缓存命中、RGW 5xx、Ceph pg 状态、OSD 使用率均衡、NVMe 温度与 SMART。
- 每周维护:清理僵尸对象、校验 Lifecycle 统计与冷层增长速率。
- 每学期开学周:提前一周预热 Top 清单;上线值守延长到凌晨 1 点。
完整命令清单(汇总摘录)
Nginx 缓存清理
# 清理超过 14 天未访问缓存(按 mtime)
find /var/cache/nginx -type f -mtime +14 -delete
Ceph 常用
ceph -s
ceph osd df tree
ceph df
radosgw-admin buckets stats
压力与回放(小心生产)
wrk -t12 -c120 -d60s --latency http://cw.example.com/courseware/xxx.pdf
我会怎么在你那边落地(行动指南)
- 先上 Nginx(NVMe) 缓存,两天之内你就能看到命中率与延迟的立竿见影。
- 后端上 Ceph RGW 冷层(HDD + EC),一步到位把容量成本打下来。
- 如果团队熟悉 Go/MinIO:用 MinIO 热冷双集群 + ILM 也很稳,但要把跨集群网络和容灾设计好。
- 观测优先:Prometheus + Grafana、日志保留 30 天、构建“热点 TopN”自动榜单用于预热。
走出机房,维多利亚港的风
当早上 6 点我走出机房,海风带着一点咸味。手机上 Grafana 的曲线把夜里每一次命中、每一次降延迟都画得清清楚楚。
这套冷热分层不是“绝对的对或错”,它只是用成本与命中率讲了一个朴素的故事:把最热门的东西放在最近的地方。如果哪天你也在凌晨的机房里听见风声,看到那条 P99 曲线被稳稳按住,你会明白——这活儿,值。
附录:表格参数与样例
A. Ceph 关键参数建议(HDD 冷池)
| 参数 | 建议值/说明 |
|---|---|
osd_memory_target |
4G(HDD) |
osd_pool_default_size |
3(Mon/OSD 足够时) |
| EC Profile | k=6 m=3(按盘/节点数调整) |
crush-failure-domain |
host |
RGW rgw_thread_pool_size |
1024–2048(视 CPU) |
mon_allow_pool_delete |
生产建议关闭(临时开以便管理) |
B. Nginx 缓存策略
| 项目 | 设置 |
|---|---|
proxy_cache_key |
$scheme://$host$request_uri |
| 命中有效期 | 200/302 → 7d;206 → 1d |
| 目录层级 | levels=1:2 |
| 最大容量 | max_size=2.5T(按 NVMe 调整) |
| 预热 | TopN 列表并行 curl |
C. 命中率与带宽粗估(示例)
| 热比例 | 命中率 | 后端带宽衰减 |
|---|---|---|
| 10% | 75% | ×0.25 |
| 20% | 85% | ×0.15 |
| 30% | 90% | ×0.10 |
这张表来自我们对“课件访问”Zipf 分布的经验值;实际以你站点流量特征校准。