
凌晨1:40 的将军澳机房,我们一台老业务节点在晚高峰被打成“红色”:写入抖动、P99 延迟飙升、日志堆积。香港链路短、延迟低,前端放量特别猛,写压都砸在后端磁盘上。那一晚,我决定不再“硬扛”,而是重做一套分层磁盘与盘位规划,把日志(Log)、临时(Temp)、**持久(Persistent)**三种写入流分开,互不拖累。
下面是我在这台服务器上的完整实操记录:从硬件到 BIOS、从 RAID 到文件系统、从系统参数调优到 fio 结果、从坑位到现场解法,一条龙落地。
1. 目标与约束
业务画像
- 峰值写入:每台 1.2–1.6 GB/s(合并日志+临时落盘+持久存储写入)
- 写入类型:大量顺序追加(日志)、高频随机写(临时/中间结果)、事务型写(持久)
- 稳态要求:P99 写延迟 < 8 ms(日志与临时更苛刻)
- 数据安全:持久层需要跨盘容错、掉电不丢
约束条件
- OS:CentOS 7(公司基线,兼容旧内核驱动与现网工具链)
- 机架空间:2U 服务器,前置 12×2.5",后置 2×2.5"(可选)
- 网络:2×25GbE,上行打满时磁盘侧不能成为瓶颈
2. 硬件与盘位规划(示例配置)
2.1 服务器与部件
| 模块 | 型号/参数 | 说明 |
|---|---|---|
| 机型 | 2U 双路(示例:Supermicro/SYS-2029U 或同级) | 前 12×2.5" U.2/SAS/SATA 混插背板 |
| CPU | Xeon Gold 6248R ×2(48 vCPU/台) | 保证日志压缩、加密、checksum 余量 |
| 内存 | 384 GB DDR4 | 页缓存与 memtable 充足 |
| HBA/RAID | Broadcom 9400-8i(IT 模式)+ NVMe 直连背板 | NVMe 直通,SAS 走 HBA |
| 网卡 | 2×25GbE(Mellanox/Intel) | 多队列,RSS/IRQ 绑核 |
| 电源 | 1+1 冗余 | RAID 缓存掉电保护(BBU/超级电容) |
2.2 磁盘选型与分层
| 层级 | 介质/阵列 | 典型型号 | 角色 | 文件系统 | RAID/冗余 | 备注 |
|---|---|---|---|---|---|---|
| 日志盘(Log) | NVMe U.2 ×2 | Intel P4510 2TB / Samsung PM9A3 3.84TB | 顺序追加、rsyslog/journal 队列、中间 WAL | XFS | mdadm RAID1 | 低延迟、可承受写洪峰 |
| 临时盘(Temp) | NVMe U.2 ×4 | 同上 | 中间结果、排序/merge、容器层的可丢数据 | XFS | mdadm RAID10(性能/容错) | 可接受丢失(但实践上仍做 RAID10) |
| 持久盘(Persistent) | 10K SAS HDD ×8 + NVMe Cache | 1.8TB SAS ×8 | 业务数据、快照、归档 | XFS/LVM | RAID10(HDD)+ LVM Cache(NVMe) | 兼顾容量与可靠性 |
| 系统盘(Boot) | M.2 SATA ×2 | 480GB ×2 | /boot、/ 根 | XFS | mdadm RAID1 | 与业务盘分离 |
注:如果背板是混插,确认 U.2 通道速率与分配,避免 PCIe x4 被错误分配为 x2(后文有坑位)。
2.3 盘位布局(Bay → 用途)
| 槽位 | 介质 | 用途 | 备注 |
|---|---|---|---|
| 前置 1–2 | NVMe | 日志盘 md0(RAID1) | /var/log、rsyslog 队列、WAL |
| 前置 3–6 | NVMe | 临时盘 md1(RAID10) | /mnt/tmp_scratch(高 IOPS) |
| 前置 7–12 | SAS HDD | 持久盘阵列(RAID10) | LVM PV → VG data → LV data |
| 后置 1–2 | M.2 SATA | 系统盘 md2(RAID1) | /、/boot |
3. BIOS / 固件与内核前置准备
- 将 NVMe 槽位设为 PCIe x4,开启 热插拔支持(Hot-Plug)
- HBA 刷成 IT 模式 固件(直通,给 mdadm/LVM 完整块设备能力)
- 关闭主板上的 SATA 模拟 RAID(不要让 BIOS 做虚拟盘)
- 启用 NUMA 一致性、设置 内存交错(Interleaving)
- CentOS 7 内核参数:elevator=none(NVMe)或 deadline(SAS),spectre_v2=retpoline(视内核)
4. RAID 与文件系统落地(CentOS 7)
4.1 裸设备识别(示例)
lsblk -d -o NAME,MODEL,SIZE,ROTA,TYPE | egrep 'nvme|sd'
4.2 创建日志盘(NVMe RAID1)
# 假设 nvme0n1 / nvme1n1
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/nvme0n1 /dev/nvme1n1
mkfs.xfs -f -m reflink=0,crc=1 -l size=1g /dev/md0
mkdir -p /var/log
echo '/dev/md0 /var/log xfs defaults,noatime,nodiratime,logbufs=8,logbsize=256k 0 2' >> /etc/fstab
mount -a
logbsize 与 logbufs 在日志型顺序写中能降低元数据争用,实际需结合 fio 评估。
4.3 创建临时盘(NVMe RAID10)
# 假设 nvme2n1 nvme3n1 nvme4n1 nvme5n1
mdadm --create /dev/md1 --level=10 --raid-devices=4 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1 /dev/nvme5n1
mkfs.xfs -f -m reflink=0,crc=0 /dev/md1 # 追求极致写入,关闭 crc(可选)
mkdir -p /mnt/tmp_scratch
echo '/dev/md1 /mnt/tmp_scratch xfs defaults,noatime,nodiratime,discard 0 2' >> /etc/fstab
mount -a
临时层数据可丢,但我依然用 RAID10:一是避免单盘挂掉影响在跑的批任务,二是更平顺的写延迟。
4.4 创建持久盘(SAS RAID10 + LVM Cache)
# 假设 /dev/sd[b-i] 共 8 块 SAS
mdadm --create /dev/md2 --level=10 --raid-devices=8 /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf /dev/sdg /dev/sdh /dev/sdi
# 建立 LVM
pvcreate /dev/md2
vgcreate vg_data /dev/md2
lvcreate -n lv_data -L 6.5T vg_data # 预留一些空余做增长/快照
# 用 NVMe(再单独拿一块或从 md1 分出)做 LVM Cache
# 假设 /dev/nvme6n1,容量按 5–10% 缓存比
pvcreate /dev/nvme6n1
vgextend vg_data /dev/nvme6n1
lvcreate -n lv_cache_meta -L 8G vg_data /dev/nvme6n1
lvcreate -n lv_cache -L 500G vg_data /dev/nvme6n1
lvconvert --type cache --cachemode writeback --cachepool vg_data/lv_cache --cachevol vg_data/lv_data --poolmetadata vg_data/lv_cache_meta
mkfs.xfs -f -m reflink=1,crc=1 -d agcount=32 /dev/vg_data/lv_data
mkdir -p /data
echo '/dev/vg_data/lv_data /data xfs defaults,noatime,nodiratime,attr2,inode64 0 2' >> /etc/fstab
mount -a
LVM Cache 选择 writeback,配合 BBU/超容保障掉电安全;若无硬件保护,保守使用 writethrough。
5. 日志系统落盘与队列
5.1 journald 与 rsyslog 定位到日志盘
/etc/systemd/journald.conf:
[Journal]
Storage=persistent
RuntimeMaxUse=8G
SystemMaxUse=32G
将 journal 目录迁到日志盘:
systemctl stop systemd-journald
rsync -aXS /var/log/journal/ /var/log/journal.new/
mv /var/log/journal /var/log/journal.bak
mv /var/log/journal.new /var/log/journal
systemctl start systemd-journald
/etc/rsyslog.d/10-queue.conf(关键在 WorkDirectory):
$WorkDirectory /var/log/rsyslog-spool # 位于 /var/log(日志盘)
$ActionQueueType LinkedList
$ActionQueueFileName dbq
$ActionQueueMaxDiskSpace 16g
$ActionQueueHighWaterMark 800000
$ActionQueueLowWaterMark 200000
$ActionResumeRetryCount -1
日志切割(/etc/logrotate.d/app):
/var/log/app/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
postrotate
/bin/systemctl kill -s HUP app.service || true
endscript
}
6. 临时盘生命周期管理
挂载 /mnt/tmp_scratch,给容器/任务指定临时路径:
Docker:--tmpfs /tmp:rw,noexec,nosuid,size=8g + --mount type=bind,src=/mnt/tmp_scratch,dst=/scratch
批处理框架的溢写目录(sort spill、shuffle spill)指向 /mnt/tmp_scratch
tmpfiles.d 定期清理:
/etc/tmpfiles.d/tmp_scratch.conf:
D /mnt/tmp_scratch 1777 root root 3d
7. 系统与内核调优(CentOS 7)
7.1 tuned 与 I/O 调度
yum install -y tuned
systemctl enable --now tuned
tuned-adm profile throughput-performance
/etc/udev/rules.d/60-io-scheduler.rules(NVMe 用 none/none,SAS 用 deadline):
ACTION=="add|change", KERNEL=="nvme*n*", ATTR{queue/scheduler}="none"
ACTION=="add|change", KERNEL=="sd*[!0-9]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="deadline"
7.2 内核参数(/etc/sysctl.d/99-tuning.conf)
vm.dirty_ratio=10
vm.dirty_background_ratio=5
vm.swappiness=1
vm.vfs_cache_pressure=50
fs.aio-max-nr=1048576
fs.file-max=2097152
net.core.somaxconn=65535
net.core.netdev_max_backlog=250000
net.ipv4.tcp_fin_timeout=15
net.ipv4.tcp_tw_reuse=1
sysctl --system
7.3 IRQ 绑核与多队列
关闭自动飘移:systemctl stop irqbalance && systemctl disable irqbalance
用 set_irq_affinity(厂商脚本)或 echo <mask> > /proc/irq/<id>/smp_affinity 把 NVMe、HBA、中断绑到本地 NUMA 节点
8. 验证:fio 脚本与典型结果
8.1 fio 模板(顺序写/随机写混合)
cat > /root/fio-mix.fio <<'EOF'
[global]
ioengine=libaio
direct=1
iodepth=64
numjobs=4
runtime=120
time_based=1
group_reporting=1
[seqwrite_log]
filename=/var/log/fio_test_1 # 日志盘
rw=write
bs=256k
[rndwrite_tmp]
filename=/mnt/tmp_scratch/fio_test_2 # 临时盘
rw=randwrite
bs=4k
[txn_persist]
filename=/data/fio_test_3 # 持久盘(带 LVM Cache)
rw=randrw
rwmixread=30
bs=8k
EOF
fio /root/fio-mix.fio
8.2 现场样例结果(供参考,非唯一)
| 层级 | 场景 | 吞吐/IOPS | P99 延迟 |
|---|---|---|---|
| 日志盘(顺序 256k 写) | 1.1–1.3 GB/s | 约 4–5 ms | |
| 临时盘(4k 随机写) | 420–520 kIOPS | 约 1.2–1.8 ms | |
| 持久盘(8k randrw 70/30) | 95–130 kIOPS | 约 6–9 ms |
在开启 LVM Cache(writeback)后,持久层 P99 降了 ~35–45%,写放大控制明显。
9. 典型坑位与现场解决
NVMe 被降为 PCIe x2
现象:fio 吞吐打不上去、lspci -vv 显示 LnkWidth x2
处置:检查背板走线与主板 Bifurcation,BIOS 固件更新;更换 U.2 线后恢复 x4
HBA 默认 RAID 模式,mdadm 看不到真实盘
处置:刷 IT 模式,直通物理盘;系统里用 mdadm/LVM 统一管理
RAID5 在掉电时有写洞风险
处置:持久层改用 RAID10;若必须 RAID5,确保 BBU/超容+写顺序对齐;日志型数据坚决不上 RAID5
NVMe 热降频
现象:温度 >70℃,短时冲高后性能断崖
处置:加前置风扇挡板与风道,引入散热片,设置风扇曲线,温度控在 55–60℃
XFS online discard 引发抖动
处置:取消挂载 discard,改用 fstrim.timer(周度)
rsyslog 队列默认在根盘
处置:显式 WorkDirectory 到日志盘;否则高峰期根盘被写满影响系统稳定
LVM Cache 元数据太小
处置:lv_cache_meta 8–16G 起步,避免元数据饱和导致降级
10. 监控与告警
磁盘:smartmontools、nvme-cli、iostat -x 1、blktrace(故障分析)
导出器:node_exporter + smartctl_exporter + nvme_exporter
核心指标:
- 队列深度、await、svctm、util
- LVM Cache 命中率、脏数据量
- rsyslog 队列长度、丢弃计数
- 温度、介质可擦写寿命(NVMe Percentage Used)
11. 割接与回滚策略
蓝绿切换:新盘位分层就绪 → 灰度导流 10% → 观察 24 小时 → 逐步提升
数据保护:rsync --inplace --bwlimit 预热;持久层做快照(LVM snapshot)以支持快速回退
回滚:保留旧阵列映射与 mdadm 配置(/etc/mdadm.conf),切换时仅改挂载与服务指针
12. 成本/收益对比(落地后复盘)
指标 改造前 改造后 变化
峰值写入(单机) ~800 MB/s 1.3–1.5 GB/s ↑ 70–90%
P99 写延迟(整体) 12–18 ms 5–8 ms ↓ 55–60%
持久层抖动 频繁 罕见 明显改善
故障域影响面 单盘波及全量 分层隔离 降低风险
资源利用率 混用,放大冲突 分层高效 更可控
13. 关键配置清单(摘录)
/dev/md0 /var/log xfs defaults,noatime,nodiratime,logbufs=8,logbsize=256k 0 2
/dev/md1 /mnt/tmp_scratch xfs defaults,noatime,nodiratime 0 2
/dev/vg_data/lv_data /data xfs defaults,noatime,nodiratime,attr2,inode64 0 2
/etc/sysctl.d/99-tuning.conf(见第 7 节)
mdadm 持久化
mdadm --detail --scan >> /etc/mdadm.conf
fstrim 定时
systemctl enable --now fstrim.timer
- 14. 为什么要“日志/临时/持久”三层?
- 写入模型不同:顺序 vs 随机、可丢 vs 不可丢、低延迟 vs 高容量
- 抖动隔离:日志洪峰不再拖慢持久层事务
- 优化手段不同:日志层调 logbufs、临时层追 IOPS、持久层做 Cache/快照/一致性保障
割接完成那一刻,监控面板从一片“热红”慢慢变成“清绿”,P99 折线贴着 6–7 ms 走。隔着冷通道,我和同事靠在机柜门上喝完那罐温掉的柠檬茶,风扇的嗡鸣像是给我们打着拍子。分层不是新概念,但只有在真实的写入洪峰里,它才显出价值:各司其职、互不拖累,稳定压住流量潮汐。这套方案后来在香港的另外两排机柜也复用,换来相同的“绿灯”,也换来团队对夜班的更少恐惧。
附:一次性部署脚本片段(示例,按需裁剪)
#!/usr/bin/env bash
set -euo pipefail
# 1) 创建 md0/md1/md2(见上文命令)...
# 2) 文件系统与挂载
mkfs.xfs -f /dev/md0
mkfs.xfs -f /dev/md1
mkfs.xfs -f /dev/vg_data/lv_data
mkdir -p /var/log /mnt/tmp_scratch /data
mount -a
# 3) journald/rsyslog 定位与重启
systemctl restart systemd-journald rsyslog
# 4) tuned
yum install -y tuned && systemctl enable --now tuned && tuned-adm profile throughput-performance
# 5) fstrim 定时
systemctl enable --now fstrim.timer
# 6) 打点验证
fio /root/fio-mix.fio || true
如果你也正处在高并发写入的“红色夜晚”,不妨从这三个问题开始:
- 把可丢与不可丢的数据分开了吗?
- 把顺序写与随机写的路径分开了吗?
- 把高峰洪水与平稳长流的资源分开了吗?
当答案都是“是”的时候,你的 P99 往往也会跟着“绿”起来。