香港服务器高并发写入场景的三层磁盘规划:日志盘、临时盘、持久盘的分层与盘位布局
技术教程 2025-09-30 10:37 287


凌晨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 往往也会跟着“绿”起来。