
夜里 23:40,我在香港将军澳的机房,风幕帘后面是 1U 的高转风扇和一箱刚从深水埗拿回来的“精选二手盘”。客户白天临时加单:明早 8 点前把 Kafka 与 ClickHouse 的历史分区盘位扩上去,预算紧张,只能先用“过渡盘”。二手盘能不能上?怎么上?——这不是“敢不敢”的问题,而是有没有一套可信的验收与风控流程。下面就是我那晚真刀真枪跑出来的一套流程与标准,第二天我们按期上线,而且到现在还稳。
环境与硬件基线(那晚用的真实配置)
机房:香港 TKO,冷热通道,机柜供电 2×16A,环境温度 24–26℃
服务器:
- Supermicro 1029U(1U,双 LGA3647,Intel Xeon Silver 4216×2,内存 256 GB)
- HBA:LSI 9300-8i(IT 模式,SAS3 12 Gb/s),背板直通
- 系统盘:Intel S4610 480 GB(SATA SSD)
数据盘候选:
- SATA HDD:Seagate Exos 7E8 8 TB(二手,型号近似但批次不同)
- SATA SSD:Samsung PM883 3.84 TB(二手)
- NVMe:Samsung PM9A3 3.84 TB(部分盘来自缓存服务器拆机)
- OS:CentOS 7.9(3.10 内核)
目标用法:
- Kafka broker:HDD RAID10(顺序写 + 大块顺序读)
- ClickHouse 历史分区:NVMe 单盘 + LVM 条带(读多写少)
总流程鸟瞰(能否上,先过这 7 关)
- 资产登记:序列号、固件版本、接口速率、逻辑/物理扇区大小、上电次数与通电时长。
- 固件与连线确认:HBA 是否 IT 模式;SATA 链路是否 6 Gb/s;NVMe 是否 Gen3×4/Gen4×4;MiniSAS 线缆与背板检查。
- 预处理/擦写:对二手盘做安全擦除或全盘写零,确保无历史残留及触发介质重映射。
- SMART/NVMe 基线抓取与判定:核心属性、错误日志、温度、磨损度。
- 快速压力测试(1–2 小时):fio 小样本混合压测 + 短自检。
- 老化与深测(6–24 小时):badblocks 写擦 + SMART 长测 + 连续 fio。
- 打分归档与上线策略:Pass/Borderline/Fail;标注“带病运行”策略与备盘池。
经验法则:如果 时间不够跑完第 6 步,至少把 1–5 步做完整,并设置告警与准备热备。
安装与准备(CentOS 7)
yum install -y smartmontools nvme-cli fio sg3_utils gdisk parted mailx
systemctl enable --now smartd
LSI HBA 必须是 IT 模式直通,否则 SMART 透传不全。lspci | grep -i sas + smartctl -a -d sat /dev/sdX 验证。
关键验收指标与阈值(经验+可操作)
A. SATA/SAS HDD(smartctl -a)
| 指标(属性) | 期望值 | Borderline | Fail | 备注 |
|---|---|---|---|---|
| Reallocated_Sector_Ct(5) | 0 | 1–10(且稳定不增长) | >10 或增长 | 重分配扇区 |
| Current_Pending_Sector(197) | 0 | 1(观测 24h) | ≥2 或增长 | 待定扇区,强信号 |
| Offline_Uncorrectable(198) | 0 | 1 | ≥2 | 不可校正错误 |
| UDMA_CRC_Error_Count(199) | 0 | >0 但修复线缆后不再涨 | 持续增长 | 多为 线缆/背板 问题 |
| Power_On_Hours(9) | <30k | 30–50k | >50k | 二手风险阈值 |
| Temperature_Celsius(194) | 26–40℃ | 41–45℃ | >45℃ 或频繁波动 | 看机房风道 |
B. SATA SSD(smartctl -a)
| 指标 | 期望值 | Borderline | Fail | 备注 |
|---|---|---|---|---|
| Media_Wearout_Indicator(231) | ≥90 | 70–89 | <70 | 越低磨损越高 |
| Wear_Leveling_Count(177/173) | 均衡 | 不均衡轻微 | 明显失衡 | 厂商含义不同 |
| Program_Fail/Erase_Fail | 0 | 1 | ≥2 或增长 | 介质问题 |
| Uncorrectable_Error_Cnt | 0 | 1 | ≥2 或增长 | 数据可靠性 |
| Power_On_Hours | <20k | 20–35k | >35k | SSD 健康随 TBW 走 |
| TBW/写入量 | <额定 TBW 的 60% | 60–85% | >85% | 厂商数据表对照 |
C. NVMe(nvme smart-log 或 smartctl -a /dev/nvmeX)
| 指标 | 期望值 | Borderline | Fail | 备注 |
|---|---|---|---|---|
| Critical Warning | 0 | – | >0 | 立即淘汰 |
| Percentage Used | <20% | 20–60% | >60–80% 视用途 | 接近 100%=寿命将尽 |
| Media and Data Integrity Errors | 0 | 1 | ≥2 或增长 | 关键 |
| Unsafe Shutdowns | <50 | 50–500 | >500 | 看业务特性 |
| Temperature | <70℃ | 70–80℃ | >80℃ 或节流 | 1U 必做风道 |
NVMe 写入换算(以 Data Units Written 为例):
Bytes = DataUnitsWritten × 512 × 1000,TB = Bytes / (10^12)
一键采集与判定脚本(生成 CSV)
仅在测试/验收阶段运行,不要对生产数据盘做 badblocks -w 等破坏性操作。
#!/bin/bash
# /root/disk_acceptance.sh
# 采集 SMART/NVMe 核心指标并给出粗判,输出 CSV
OUT=/root/disk_acceptance_$(date +%F_%H%M).csv
echo "DEV,TYPE,SN,FW,LINK,SECT,POH,REALLOC,PENDING,OFFUNC,UDMA_CRC,MWI/Wear,PerUsed,MediaErr,UnsafeSD,TEMP,VERDICT" > $OUT
judge() {
local poh=$1 r=$2 p=$3 u=$4 mwi=$5 per=$6 me=$7 temp=$8
# 简化判分:任何致命错误直接Fail,其它按阈值打分
if [[ $p -ge 2 || $me -ge 2 || $temp -ge 85 ]]; then echo "FAIL"; return; fi
if [[ $r -gt 10 ]]; then echo "FAIL"; return; fi
if [[ $u -gt 0 ]]; then
# 线缆类,先标记Borderline,修复后复测
echo "BORDERLINE(UDMA_CRC)"
return
fi
# SSD/NVMe寿命维度
if [[ $per != "-" && $per -gt 60 ]]; then echo "FAIL(WEAR)"; return; fi
if [[ $mwi != "-" ]]; then
if [[ $mwi -lt 70 ]]; then echo "FAIL(WEAR)"; return; fi
if [[ $mwi -lt 90 ]]; then echo "BORDERLINE(WEAR)"; return; fi
fi
# POH 经验阈
if [[ $poh -gt 50000 ]]; then echo "BORDERLINE(AGE)"; return; fi
echo "PASS"
}
for d in /dev/sd?; do
[[ -b $d ]] || continue
info=$(smartctl -i $d)
sn=$(echo "$info" | awk -F: '/Serial Number/ {gsub(/^[ \t]+/,"",$2);print $2}')
fw=$(echo "$info" | awk -F: '/Firmware Version/ {gsub(/^[ \t]+/,"",$2);print $2}')
sect=$(blockdev --getss $d 2>/dev/null)
poh=$(smartctl -A $d | awk '/Power_On_Hours/ {print $10}')
rsc=$(smartctl -A $d | awk '/Reallocated_Sector_Ct/ {print $10}')
pend=$(smartctl -A $d | awk '/Current_Pending_Sector/ {print $10}')
offu=$(smartctl -A $d | awk '/Offline_Uncorrectable/ {print $10}')
crc=$(smartctl -A $d | awk '/UDMA_CRC_Error_Count/ {print $10}')
temp=$(smartctl -A $d | awk '/Temperature_Celsius/ {print $10}')
mwi=$(smartctl -A $d | awk '/Media_Wearout_Indicator/ {print $10}')
link=$(smartctl -i $d | awk -F: '/SATA Version is/ {print $2}' | xargs)
ver=$(judge "${poh:-0}" "${rsc:-0}" "${pend:-0}" "${crc:-0}" "${mwi:--}" "-" "0" "${temp:-0}")
echo "$d,SATA/SAS,$sn,$fw,$link,$sect,$poh,${rsc:-0},${pend:-0},${offu:-0},${crc:-0},${mwi:--},-,-,${temp:-0},$ver" >> $OUT
done
for n in /dev/nvme*n1; do
[[ -b $n ]] || continue
dev=${n%n1}
sn=$(nvme id-ctrl $dev | awk -F: '/sn/ {print $2}' | xargs)
fw=$(nvme id-ctrl $dev | awk -F: '/fr/ {print $2}' | xargs)
per=$(nvme smart-log $dev | awk -F: '/percentage_used/ {print $2}' | tr -dc '0-9')
me=$(nvme smart-log $dev | awk -F: '/media_errors/ {print $2}' | tr -dc '0-9')
us=$(nvme smart-log $dev | awk -F: '/unsafe_shutdowns/ {print $2}' | tr -dc '0-9')
poh=$(nvme smart-log $dev | awk -F: '/power_on_hours/ {print $2}' | tr -dc '0-9')
temp=$(nvme smart-log $dev | awk -F: '/temperature/ {print $2}' | tr -dc '0-9')
link="PCIe"
sect=$(cat /sys/class/block/${n#/dev/}/queue/hw_sector_size)
ver=$(judge "${poh:-0}" "0" "0" "0" "-" "${per:-0}" "${me:-0}" "${temp:-0}")
echo "$dev,NVMe,$sn,$fw,$link,$sect,$poh,0,0,0,0,-,${per:-0},${me:-0},${us:-0},${temp:-0},$ver" >> $OUT
done
echo "Output: $OUT"
示例 CSV(节选)
DEV,TYPE,SN,FW,LINK,SECT,POH,REALLOC,PENDING,OFFUNC,UDMA_CRC,MWI/Wear,PerUsed,MediaErr,UnsafeSD,TEMP,VERDICT
/dev/sdb,SATA/SAS,ZDH1234,SC61,SATA 6.0 Gb/s,512,28450,0,0,0,0,-,-,-,12,33,PASS
/dev/sdc,SATA/SAS,ZDH9876,SC61,SATA 3.0 Gb/s,512,61234,2,0,0,0,-,-,-,5,41,BORDERLINE(AGE)
/dev/nvme0,NVMe,S6UNNN,DXM91A0Q,PCIe,512,1520,0,0,0,0,-,12,0,21,54,PASS
/dev/nvme1,NVMe,S6UXXX,DXM91A0Q,PCIe,512,9800,0,0,0,0,-,67,0,87,78,FAIL(WEAR)
预处理与破坏性测试(谨慎执行)
- 务必确认目标盘无重要数据!
全盘写零(触发潜在坏块重映射)
# SATA/SAS
blkdiscard /dev/sdX 2>/dev/null || dd if=/dev/zero of=/dev/sdX bs=1M status=progress oflag=direct
# NVMe(更快)
blkdiscard /dev/nvme0n1
badblocks(写擦四模式,耗时长)
badblocks -wsv -b 4096 /dev/sdX
这一步能逼出“将坏未坏”的边缘块,最花时间,我通常在上线前夜只跑小样本或只对 Borderline 盘位跑。
FIO 压测模板(快速与长跑)
/root/fio-smoke.job(10–20 分钟快速验证 IOPS/吞吐与延迟抖动)
[global]
ioengine=libaio
iodepth=64
direct=1
time_based=1
runtime=600
group_reporting=1
numjobs=1
bs=128k
[read]
rw=read
filename=/mnt/testfile
[write]
rw=write
执行:
mkdir -p /mnt/test && mount -o noatime,nodiratime /dev/sdX1 /mnt/test
fallocate -l 10G /mnt/test/testfile
fio /root/fio-smoke.job
umount /mnt/test
长跑混合(用于 Kafka/OLAP 模拟):
fio --name=randrw --filename=/dev/nvme0n1 --ioengine=libaio --direct=1 \
--bs=4k --iodepth=128 --numjobs=4 --rw=randrw --rwmixread=70 \
--time_based --runtime=21600 --group_reporting
判定与上线策略(打分法)
我用一个简单的三色灯逻辑归档:
- PASS(绿):关键错误为 0;寿命<60%(NVMe/SATA SSD);HDD 重映射 0 且 Pending 0;温度正常。
- BORDERLINE(黄):寿命 60–85%、POH 偏高(HDD >50k)、链路告警(修复后复测);只上非核心集群或做副本/副分片。
- FAIL(红):Pending≥2、MediaErr≥2、NVMe Percentage Used>80%、持续 CRC 增长、温度节流;直接淘汰。
上线时的工程补偿:
- Kafka:副本数 ≥3、min.insync.replicas 合理;坏盘优先放 broker 上“历史分区”而非活跃分区。
- ClickHouse:历史分区在 Cold Tier;有 1.5× 冗余与每日校验任务。
- 备盘池:N+2(至少两块热备),我在香港习惯 10–15% 的物理冗余。
真实坑与现场解法(那晚踩过的都在这)
UDMA_CRC 飙升:有一块 Exos 的 UDMA_CRC_Error_Count 从 0→56。
原因:MiniSAS 线缆接头松、背板金手指有灰。
解法:换线+酒精清洁背板,复测 2 小时未再增长,标记 BORDERLINE(UDMA_CRC),仅上冷数据池。
SATA 只协商到 3 Gb/s:SATA Version is: SATA 3.0 Gb/s。
原因:老背板通道;更换到另一槽位后恢复 6 Gb/s。
原则:3 Gb/s 直接判 FAIL(除非业务确实不敏感带宽)。
NVMe 温度节流:PM9A3 在 1U 里 78–80℃,percentage_used 仅 12%,但 长跑掉速。
解法:加 1.5 mm 散热垫+导风片,BMC 风扇策略从 Standard→Full,温度降到 62℃,稳定 PASS。
扇区大小不一致:混有 4Kn 与 512e,mdadm 提示不一致。
解法:统一做 512e(或在创建阵列时显式对齐:--data-offset),并在 LVM 层设置 pe_alignment。
HBA 非 IT 模式:某台机器的 3108 仍是 RAID 模式,smartctl 看不到原生属性。
解法:夜里现场刷 IT 固件(自带 U 盘),风险大,建议平时先准备好 IT 固件卡或备机。
入池与部署(示例:HDD 做 RAID10,XFS)
# 分区对齐
parted -s /dev/sdb mklabel gpt
parted -s /dev/sdb mkpart primary 1MiB 100%
# 其它盘同样处理...
# 阵列
mdadm --create /dev/md10 --level=10 --raid-devices=4 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 \
--metadata=1.2 --chunk=512
mdadm --detail --scan >> /etc/mdadm.conf
# LVM
pvcreate /dev/md10
vgcreate vg_kafka /dev/md10
lvcreate -n lv_kafka -l 100%FREE vg_kafka
# 文件系统(Kafka 推荐 XFS)
mkfs.xfs -f /dev/vg_kafka/lv_kafka
mkdir -p /data/kafka && echo "/dev/vg_kafka/lv_kafka /data/kafka xfs noatime,nodiratime,inode64 0 0" >> /etc/fstab
mount -a
调优小记(CentOS 7)
# 读 ahead
blockdev --setra 1024 /dev/md10
# I/O 调度
for d in /sys/block/md10/queue/scheduler; do echo none > $d; done
echo 0 > /sys/block/md10/queue/add_random
echo 4096 > /sys/block/md10/queue/nr_requests
监控与告警(最少配置)
smartd(每小时短检,异常邮件)
/etc/smartd.conf:
DEVICESCAN -H -l error -f -n standby,never -s (S/../.././01) -m admin@example.com
systemctl restart smartd
生产里我会加 smartctl_exporter/node_exporter 自定义采集,把关键指标(Pending/MediaErr/PerUsed)打到 Prometheus,增长即告警。
二手盘“能不能上”的决策表(一页带走)
| 盘型 | 必过项 | 可接受项(带标注) | 直接淘汰 |
|---|---|---|---|
| HDD | Pending=0、OffUnc=0、Realloc≤10 且不增长、SATA 6 Gb/s | POH 30–50k;UDMA_CRC>0 但修复线后不再涨 | Pending≥2;持续 CRC;>50k 且温度/噪音异常 |
| SATA SSD | 无 Program/Erase Fail;MWI≥90 | MWI 70–89;TBW 在 60–85% | MWI<70;Uncorrectable>0 且增长 |
| NVMe | Critical Warning=0;MediaErr=0;PerUsed<60% | PerUsed 60–80%(只上冷数据) | PerUsed>80%;温度>80℃ 或节流 |
成本与风控小结(香港场景下的取舍)
- 采购价:在香港,企业级二手盘常见比同型号全新便宜 30–60%。
- 备件与冗余:务必拉高到 10–15% 的物理冗余,且准备 同型号同固件 的热备。
- 时间预算:完整“长测”建议 ≥12 小时/批次;若赶上线,至少完成 基线 + 快压 + 短检 并安排后补长测。
- 运营侧:定期校验与滚动替换,把 Borderline 盘逐步从热层迁到冷层,最终下线。
凌晨 3 点,最后一块 Borderline 的 Exos 仍然稳在 0 Pending;NVMe 的温度被压到 62℃;Kafka 的 RAID10 顺利同步。清晨 6:30,我在机房走道上喝完那杯冰美式,给客户发了上线短信:按计划切分区、加副本、放量。
二手盘不是不能上,而是必须带着数据、标准和风控去上。只要你的验收清单够硬、监控告警够勤、冗余备件够多,它就能像那天的风一样,呼呼作响但不夺命。