硬盘不会突然坏,它会先发出很多次警告
服务器运维里最让人措手不及的故障,不是 CPU 跑满、不是内存溢出,而是硬盘悄无声息地坏掉。前者有明确的现象和恢复路径,后者一旦发生,往往意味着数据丢失、站点长时间不可用,运气不好连备份都在同一块盘上一起没了。
但硬盘故障在绝大多数情况下并不是瞬间发生的。现代机械硬盘和固态硬盘都内置了自我监测、分析与报告技术(SMART,Self-Monitoring, Analysis and Reporting Technology),会持续记录上百项运行指标:重映射扇区数、待映射扇区数、寻道错误率、通电时间、温度、SATA 链路错误等。这些指标中的很多会在硬盘彻底失效前几个月甚至一年就开始变化。问题在于,默认情况下没有任何人会去主动读这些数据,除非你专门配置了监控。
这篇文章讲清楚怎么用 smartctl 读懂硬盘健康状态、哪些指标是真正的死亡预警、如何设置自动巡检与邮件告警,以及发现坏道之后怎么安全地迁移数据。
先装工具,再认识你的硬盘
Debian/Ubuntu 上安装 smartmontools:
apt-get update
apt-get install -y smartmontools
# 确认服务已启动(它会定期自检并记录日志)
systemctl enable --now smartd
systemctl status smartd --no-pager
# 列出所有磁盘,先搞清楚设备名
lsblk -d -o NAME,SIZE,TYPE,MODEL
# 典型输出:
# NAME SIZE TYPE MODEL
# vda 40G disk Virtual Disk (云服务器虚拟盘)
# sda 1.8T disk ST2000DM008-2FR102
# nvme0n1 476.9G disk Samsung SSD 980 PRO设备名规律:传统的 SATA/SAS 盘是 /dev/sda、/dev/sdb;NVMe 固态盘是 /dev/nvme0n1;云服务器(KVM/Xen)常见 /dev/vda。
关键区别在这里:普通 SATA 盘可以把 SMART 数据从任意分区读出来,但 RAID 卡后面的盘和 NVMe 盘必须指定正确的设备类型和通道。命令写错会直接报 Operation not supported 或 Unable to detect device type:
# 普通 SATA 盘
smartctl -a /dev/sda
# NVMe 固态盘(必须加 -d nvme)
smartctl -a -d nvme /dev/nvme0n1
# 位于 RAID 卡后面的盘(LSI/MegaRAID,需要指定控制器编号和槽位)
smartctl -a -d megaraid,0 /dev/sda
# 位于 SATA 端口倍增器后面的盘
smartctl -a -d sat /dev/sda每台机器的硬件组合不同,建议先用 smartctl --scan 让工具自己探测一遍,它会输出每块盘推荐的设备类型,照着用就不会错:
smartctl --scan
# /dev/sda -d scsi # /dev/sda, SCSI device
# /dev/nvme0n1 -d nvme # /dev/nvme0n1, NVMe device读懂 SMART 报告:哪些数字真正要命
一块健康的 SATA 盘执行 smartctl -a /dev/sda,输出分几段。第一段是全局结论,务必先看这里:
=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED
SMART Attributes Data Structure revision number: 16
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
1 Raw_Read_Error_Rate 0x000f 117 099 006 Pre-fail Always - 156743512
5 Reallocated_Sector_Ct 0x0033 100 100 036 Pre-fail Always - 0
9 Power_On_Hours 0x0032 088 088 000 Old_age Always - 10847
12 Power_Cycle_Count 0x0032 100 100 020 Old_age Always - 43
187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0
194 Temperature_Celsius 0x0022 032 050 000 Old_age Always - 32
196 Reallocated_Event_Count 0x0032 100 100 000 Old_age Always - 0
197 Current_Pending_Sector 0x0012 100 100 000 Old_age Always - 0
198 Offline_Uncorrectable 0x0010 100 100 000 Old_age Offline - 0
199 UDMA_CRC_Error_Count 0x0032 200 200 000 Old_age Always - 0这个表格怎么读?VALUE 是归一化后的健康分数(100 最好,越低越差),THRESH 是厂商设定的失败阈值,RAW_VALUE 是原始计数值。真正需要盯住的是下面这几个属性,它们几乎可以直接判定硬盘的生死:
ID 5 Reallocated_Sector_Ct(重映射扇区数):这是判断硬盘健康最核心的指标。当硬盘读某个扇区失败时,会把数据搬到备用扇区池重新映射,这个计数就加一。RAW_VALUE 大于 0 说明已经出现过坏道;如果持续增长,说明坏道正在扩散,盘基本可以准备换了。这块盘出现任何非零值都不该继续承载关键数据。
ID 197 Current_Pending_Sector(当前待映射扇区数):这是「可疑扇区」——硬盘读取时校验失败、但还没决定是否重映射的扇区。它的危险在于:这些扇区里的数据可能已经读不出来了,而硬盘还没把它们隔离。一旦这个值大于 0,意味着某次读取可能会返回 I/O 错误,甚至导致文件系统损坏。这个值长期不为零,比 ID 5 更紧急。
ID 198 Offline_Uncorrectable(离线不可纠正扇区):离线自检时发现的不可纠正错误,同样是坏道的强信号。
ID 187 Reported_Uncorrect:硬件 ECC 无法纠正的错误总数,大于 0 就要警惕。
ID 199 UDMA_CRC_Error_Count(SATA 链路错误):这个指标很特别,它通常不代表硬盘本身要坏,而是数据线或接口接触不良。值持续增长时,第一件事是换一根 SATA 线、重新插拔接口、检查背板供电,往往换线就解决了。不要一看到这个数字就急着换盘。
ID 194 Temperature_Celsius(温度):RAW_VALUE 就是当前温度摄氏度数。机械盘长期超过 45℃、固态盘超过 60℃ 会显著缩短寿命。如果温度偏高,先检查机箱风道和硬盘位有没有被灰尘堵死。
ID 9 Power_On_Hours(通电时间):运维里有个不完全可靠但有用的经验值——机械盘通电约 3 万小时后进入高故障期(平均无故障时间 MTBF 通常在几十万小时量级,但这是统计值,个体差异极大)。更重要的是留意 ID 240 Head_Flying_Hours 与启停次数,频繁启停的盘损耗更快。
NVMe 固态盘的读法完全不同
NVMe 盘不用上面这套属性表,它输出的是一组更直观的百分比和字节数:
smartctl -a -d nvme /dev/nvme0n1
# 关键字段:
# Critical Warning: 0x00 <- 必须为 0,非 0 就是紧急
# Temperature: 38 Celsius
# Available Spare: 100% <- 剩余备用块百分比
# Available Spare Threshold: 10% <- 低于此值判为失败
# Percentage Used: 12% <- 寿命消耗,等于 SSD 的油表
# Data Units Read: 184,231,552 [94.3 TB]
# Data Units Written: 91,204,338 [46.7 TB]
# Media and Data Integrity Errors: 0 <- 必须为 0
# Error Information Log Entries: 0固态盘要看 Percentage Used(寿命消耗百分比,按 TBW 计算)、Available Spare(剩余备用块,跌破阈值就危险)和 Media and Data Integrity Errors(数据完整性错误,必须为 0)。Critical Warning 只要非 0x00,就说明固件已经判定盘处于异常状态,应该立即安排更换。
跑一次自检:短期与长期测试
SMART 属性是「被动记录」,还可以主动触发硬盘做一次全盘扫描测试。测试分两种,用途不同:
# 短测试:1-2 分钟,检查磁头、电路、伺服系统,日常巡检够用
smartctl -t short /dev/sda
# 长测试:机械盘可能跑数小时到十几小时,会完整扫描所有扇区,能发现潜在坏道
smartctl -t long /dev/sda
# 查看测试进度和结果
smartctl -l selftest /dev/sda重要提醒:长测试会占用大量 I/O,生产服务器上务必挑业务低峰期执行,并且不要在同一天对所有盘同时跑。磁盘持续满负荷读写时,你的网站响应时间会明显变差。如果服务器跑在云上,某些虚拟盘根本不支持自检,命令会直接返回不支持,这时只能依赖属性数据。
配置自动巡检与告警
人不会每天登录服务器看 SMART,所以必须自动化。smartd 是 smartmontools 自带的守护进程,配置好之后会在异常时发邮件。编辑 /etc/smartd.conf:
# 全局默认:关闭自动扫描,改用下面显式指定的条目
DEVICESCAN -d removable -n standby -m root -M exec /usr/share/smartmontools/smartd-runner
# 更推荐:显式指定每块盘,规则更精确
/dev/sda -a -o on -S on -s (S/../.././02|L/../../6/03) \
-m admin@example.com -M executabl
上面这行参数的含义:-a 启用全部监测;-o on 开启离线数据收集(硬盘空闲时自动做自检);-S on 保存属性变化值以追踪趋势;-s (S/../.././02|L/../../6/03) 表示每天凌晨 2 点跑短测试、每周六凌晨 3 点跑长测试;-m 指定告警邮箱;-M exec 允许调用外部脚本处理告警。
改完配置重载:
systemctl restart smartd
systemctl status smartd --no-pager
# 验证配置语法与当前状态
smartctl --health /dev/sda
journalctl -u smartd -n 30 --no-pager注意:云服务器上多数发行版默认没有配置本地 MTA(邮件传输代理),smartd 想发邮件也发不出去。这时有两个可行方案。方案一是装一个轻量 MTA 并配置 SMTP 中继(比如 msmtp),让 root 的邮件真正能送到外部邮箱。方案二是干脆不用邮件,写一个自己的巡检脚本挂到 crontab 里,发现异常时通过 webhook 推送到企业微信/钉钉/Telegram,这种方式在云环境里更可靠,也更容易和现有告警渠道统一。
一个实用的自写巡检脚本
#!/bin/bash
# /usr/local/bin/disk_health_check.sh
# 检查所有磁盘的 SMART 关键指标,异常时输出告警文本
set -uo pipefail
ALERT=""
for dev in $(lsblk -dno NAME | grep -E '^(sd|nvme|vd)' | sed 's|^|/dev/|'); do
# 自动探测设备类型
if [[ "$dev" == *nvme* ]]; then
DT="nvme"
else
DT="auto"
fi
out=$(smartctl -H -A -d "$DT" "$dev" 2>/dev/null || continue)
# 1) 全局健康结论
if echo "$out" | grep -q "FAILED"; then
ALERT="${ALERT}[严重] ${dev} SMART 全局判定 FAILED\n"
fi
# 2) 关键属性非零即报警(仅机械盘属性存在时)
for attr in Reallocated_Sector_Ct Current_Pending_Sector Offline_Uncorrectable; do
raw=$(echo "$out" | awk -v a="$attr" '$2==a {print $10}')
if [[ -n "${raw:-}" && "$raw" =~ ^[0-9]+$ && "$raw" -gt 0 ]]; then
ALERT="${ALERT}[警告] ${dev} ${attr}=${raw}\n"
fi
done
# 3) NVMe 关键字段
spare=$(echo "$out" | awk -F: '/Available Spare:/ {gsub(/[ %]/,"",$2); print $2}')
used=$(echo "$out" | awk -F: '/Percentage Used:/ {gsub(/[ %]/,"",$2); print $2}')
crit=$(echo "$out" | awk -F: '/Critical Warning:/ {gsub(/ /,"",$2); print $2}')
[[ -n "${crit:-}" && "$crit" != "0x00" ]] && ALERT="${ALERT}[严重] ${dev} Critical Warning=${crit}\n"
[[ -n "${used:-}" && "$used" =~ ^[0-9]+$ && "$used" -ge 80 ]] && ALERT="${ALERT}[警告] ${dev} 寿命已用 ${used}%\n"
[[ -n "${spare:-}" && "$spare" =~ ^[0-9]+$ && "$spare" -le 20 ]] && ALERT="${ALERT}[警告] ${dev} 备用块仅剩 ${spare}%\n"
done
if [[ -n "$ALERT" ]]; then
echo -e "服务器 $(hostname) 磁盘健康告警:\n$ALERT"
# 推送到通知渠道(按需替换 URL)
curl -s -X POST "https://你的webhook地址" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"磁盘健康告警\n$(echo -e "$ALERT")\"}" > /dev/null
exit 1
fi
echo "所有磁盘健康检查通过"
exit 0挂到 crontab,每天检查一次:
chmod +x /usr/local/bin/disk_health_check.sh
# 每天早上 8 点执行,结果同时写日志
( crontab -l 2>/dev/null; echo "0 8 * * * /usr/local/bin/disk_health_check.sh >> /var/log/disk_health.log 2>&1" ) | crontab -
# 手动验证一次
/usr/local/bin/disk_health_check.sh发现坏道之后怎么办
查出问题只是第一步,处理方式错了反而会造成更大损失。请按下面的顺序来:
第一步,立刻停止写入,先备份。这是最重要的一条。当 ID 197 或 ID 198 非零时,硬盘正处于「数据可能读不出来」的状态,任何写入操作都可能把数据写到已经损坏的扇区上,让情况恶化。此时应该第一时间把重要数据复制到另一块盘或远端存储,而不是急着跑修复工具。
第二步,用 ddrescue 而不是 dd 做镜像。如果盘已经出现读取错误,直接 dd 会在遇到坏扇区时反复重试直到超时,效率极低。GNU ddrescue 会记录已成功读取的区间到日志文件,遇到坏块就跳过继续,之后再回头重试,且支持断电续传:
apt-get install -y gddrescue
# 第一次镜像:0 字节重试,快速跳过坏块
ddrescue -d -r0 /dev/sda /mnt/backup/sda.img /mnt/backup/sda.log
# 第二次:对失败区域重试 3 次,先把能救的救出来
ddrescue -d -r3 /dev/sda /mnt/backup/sda.img /mnt/backup/sda.log
# 注意:/mnt/backup 必须是另一块物理盘,别放在同一块出问题的盘上第三步,判断是否还有修复价值。重映射扇区数很少且不再增长、待映射扇区在重写后归零的盘,可以降级用于非关键用途(临时文件、日志缓存)。但如果坏道在持续增加,任何软件手段都救不了——硬盘内部的备用扇区池是有限的,耗尽即彻底失效。这时候正确做法是尽快把服务迁移到新盘,而不是抱着侥幸心理继续跑。
第四步,追查根因。坏道往往是结果不是原因。散热不良导致的长期高温、电源供电不稳、机箱震动、供电不足的廉价硬盘背板,都会加速硬盘老化。只换盘不解决环境问题,新盘同样会很快出问题。
把磁盘监控变成日常习惯
理想的做法是把 SMART 检查接入现有监控体系。如果你已经搭了 Prometheus,可以用 node_exporter,它自带 smartctl 采集器,只要在启动参数里加上 --collector.smartctl 并确保 smartmontools 已安装,就能把 node_smartmon_... 系列指标拉进 Prometheus,然后配置告警规则,比如重映射扇区数增长、待映射扇区大于零、SSD 寿命消耗超过 80% 等等。配合前面文章里讲过的告警推送链路,硬盘一旦出现早期异常就会直接推到你手机上。
对于只有一两台服务器的小站长,最务实的方案就是前面那段巡检脚本加一份周报式的人工复核:脚本负责每天发现硬指标异常,每周花五分钟看一眼 smartctl -a 的温度与属性趋势。这两件事加起来不到半小时的配置成本,能帮你避免一次痛彻心扉的数据丢失。
硬盘故障从来不是「会不会」的问题,而是「什么时候」的问题。提前读懂它发出的信号,在它彻底罢工之前把数据挪走,这才是运维里真正值钱的功夫。