Linux 磁盘管理这件事,入门门槛看着很低——分个区、格式化、挂进 fstab,三步好像就完事了。但真到半夜被告警叫醒,一台机器根分区使用率 98%、业务写不进去日志的那一刻,能在一堆df、du、lsof的输出里迅速判断出那几十个 G 到底被谁吃掉、能不能在线扩、扩完要不要重启的人,其实并不多。我在实际维护中见过太多"磁盘满了就加盘"的处理方式,结果盘加了三块,根分区还是一样满,因为问题压根不在容量上。这篇内容想做的事情很明确:把 Linux 磁盘管理从块设备层到文件系统层再到挂载层的完整链路拆开讲,重点放在分区对齐、文件系统选型、在线扩容、空间异常排查这几个真正会出事的环节,给出可以直接照着敲的命令和参数,以及那些文档里不会写、但踩过之后印象极深的坑。不管你是刚接手服务器的新手,还是已经能熟练用fdisk但没深究过mkfs参数的老手,下面这些内容应该都能对应到你实际场景里的某一步。
1. 磁盘管理的层级认知与方案选型
1.1 从块设备到文件系统的四层结构
很多人把"磁盘管理"当成一个动作,其实它是一条链。理解这条链的分层,比记住命令重要得多。最底下是物理块设备,内核识别出来后会在/dev下挂一个节点,传统 SATA/SAS 盘是sda、sdb,NVMe 盘是nvme0n1,云主机上的虚拟盘通常是vda、vdb。这一层的特点是按扇区寻址,不关心你往上放什么。
往上一层是分区表,写在磁盘开头的固定位置,描述"这块盘被切成了几段、每段从哪个扇区到哪个扇区"。分区表有两种主流格式,MBR 和 GPT,选哪个后面会细说。分区节点形如sda1、sda2,NVMe 盘则是nvme0n1p1,注意那个多出来的p,它不是可有可无的装饰,脚本里写错这个字符是新手最常见的翻车点之一。
再往上是逻辑卷层,也就是 LVM。它不是必需的,但只要你的业务有"当初分小了后来要扩"的可能,就强烈建议加上。LVM 在分区之上又铺了三层抽象:物理卷 PV、卷组 VG、逻辑卷 LV。这一层的价值就一句话——把物理边界和逻辑边界解耦开,扩容时不用挪数据。
最上面才是文件系统,ext4、XFS、Btrfs 这些,负责把块设备组织成文件、目录、inode 的结构。格式化这一步的本质是在设备上写入文件系统的元数据布局,它决定了你后面能不能缩容、inode 够不够用、大文件写入的性能曲线长什么样。
这四层每一层都有自己的"空间概念"。块设备按字节算,分区表按扇区算,LVM 按 PE(物理扩展块)算,文件系统按块(block)和 inode 算。很多人排查问题绕不出来,就是因为把这几个概念混在一起了。比如一块 1T 的盘,lsblk显示 1T,分区显示 1T,mkfs之后df只有 950G,这不是哪里出错了,是"标称容量 / 1024 换算 / 文件系统元数据占用"三件事叠在一起的结果。
1.2 裸分区、LVM、文件级存储的取舍逻辑
选型这块我给个很直白的判断标准,按"未来会不会变"来分。
裸分区直接格式化适合:一次性规划、容量确定、以后不会调整的场景。比如一个纯做日志归档的盘,需求就是 2T 存 90 天日志,到期滚动删除。这种情况下用裸分区最省事,少一层抽象,出问题时排查路径也短。
LVM适合:容量会增长、需要快照、需要跨盘拼容量的场景。数据库的数据盘、业务的应用盘、虚拟化宿主机的存储池,我基本一律上 LVM。它的代价是轻微的性能开销和多一层的心智负担,但换来的是"扩容时敲三条命令、在线完成、业务无感",这笔账怎么算都划算。
文件级存储(NFS、对象存储)适合:多机共享、容量弹性要求高、单机不需要极致 IO 的场景。比如一堆应用服务器都要读同一批静态资源,那就别在每台机器上放盘了,集中到一处共享出去,容量和一致性都好管。
我踩过的一个典型坑是:早期为了省事,把一台机器的数据盘按 500G 裸分区格式化,半年后数据涨到 480G,告警天天响。想扩?分区后面还有空间,但 ext4 扩容只能在分区末尾延伸,而分区表调整本身就有风险,加上这块盘上还挂着业务,操作窗口很难找。最后是加了一块新盘、用 LVM 重新组织、把数据迁过去,折腾了整整一个通宵。从那以后我的原则就定死了:只要这块盘上的数据有可能增长超过 20%,一律上 LVM。这个判断成本极低,收益极高。
2. 分区表选型与分区实操的关键细节
2.1 MBR 与 GPT 的边界到底在哪
这两个格式的差别,网上一搜一大把,但真正影响你操作的只有几个硬指标。
MBR 写在磁盘第一个扇区,512 字节,其中分区表部分只占 64 字节,每条记录 16 字节,所以最多四个主分区。想要更多就得把其中一个主分区改成扩展分区,再在里面划逻辑分区,这个设计是上世纪留下来的历史包袱。更致命的是它用 32 位记录扇区数,按 512 字节扇区算,单盘上限 2TB。超过 2TB 的盘用 MBR,后面的空间直接不可见,这不是"能用但慢",是压根用不上。
GPT 用 64 位记录 LBA,容量上限在实际场景中等于是没有限制,分区数量默认支持 128 个,而且分区表在磁盘末尾有一份备份,头部还带 CRC 校验。盘头被误写坏的时候,GPT 有机会从备份恢复,MBR 基本只能靠fdisk手动重建。
我的实际选择标准就三条:
- 盘容量 ≥ 2TB,无脑 GPT,没得商量
- 系统盘,2010 年之后的机器全部 GPT,配合 UEFI 启动
- 只做数据盘、容量小于 2TB、跑在老设备上,MBR 也能用,但没理由选它
有个细节要注意:fdisk在新版本里对 GPT 的支持已经不错了,但处理 GPT 时我更习惯用gdisk或者parted,因为它们对 GPT 专属属性(比如分区类型 GUID、分区名)的支持更完整。parted还有个好处是支持-s脚本模式,可以写进自动化流程里。
2.2 分区对齐:一个不影响功能但影响性能的隐形参数
这块是最容易被忽略的。现代硬盘的物理扇区是 4096 字节(4K),但为了兼容,对外报告的逻辑扇区还是 512 字节。如果你的分区起始位置没有落在物理扇区的边界上,一个 4K 的写操作就会被拆成两次读改写,性能损失在机械盘上是肉眼可见的,在 SSD 上虽然主控有映射表兜底,但依然不是最优路径。
判断方法很简单,用parted看:
parted /dev/sdb unit s print输出里Start那一列,如果起始扇区是 2048、4096、6144 这种 8 的倍数(2048 × 512 = 1MiB),说明是对齐的。现代parted和fdisk默认都会帮你按 1MiB 对齐,但如果你手动指定起始扇区,比如图省事从 63 开始(这是老式 MBR 的习惯),就踩坑了。
一个真实案例:某台机器的数据盘 IO 延迟一直偏高,iostat看await是同类机器的两倍,查了半天应用层没发现问题。最后parted一看,分区起始扇区是 63,典型的未对齐。重新分区、迁移数据之后,await直接掉回正常水平。这个参数不改变任何功能,df也不会告诉你哪里不对,但它会一直悄悄吃掉你的 IO 性能。
2.3 分区操作的标准流程与刷新陷阱
完整的分区操作我是这么走的,每一步都有它的必要性:
# 1. 先确认设备名,别搞错盘 lsblk -o NAME,SIZE,TYPE,MOUNTPOINT # 2. 确认这块盘没有被挂载、没有 LVM、没有文件系统签名 blkid /dev/sdb # 3. 分区(GPT,全盘一个分区) parted -s /dev/sdb mklabel gpt parted -s /dev/sdb mkpart primary 1MiB 100% # 4. 让内核重新读取分区表 partprobe /dev/sdb第三步里我用1MiB作为起点而不是0%,这是刻意的——给 GPT 头部留出空间,同时天然完成 1MiB 对齐。终点用100%让parted自己算,比手写具体扇区号安全。
第四步的partprobe是关键。很多人分完区直接mkfs /dev/sdb1,报错 "No such file or directory",就是因为内核还没感知到新分区。如果没有partprobe,退而求其次可以用blockdev --rereadpt /dev/sdb,或者干脆重启——但生产环境为了一个分区表重启机器,这个代价太大了。
注意:如果这块盘上有任何一个分区正在被挂载使用,
partprobe会失败并提示设备忙。这时候不要强行重试,先umount掉相关挂载点,或者确认改动只涉及未使用的分区。
还有个坑是分区表的缓存。有些老内核或者虚拟化环境下,partprobe执行成功但lsblk还是看不到新分区。这时候dmesg -T | tail -20看一眼内核日志,通常会看到类似 "sdb: sdb1" 的识别记录,如果确实识别了但用户态工具没刷新,udevadm settle一下就好。
3. 文件系统创建、挂载与容量规划
3.1 ext4 与 XFS 怎么选,参数怎么定
这两个是绝大多数场景的答案,选错的影响在于"未来能不能缩"和"小文件性能"。
ext4的特点是成熟、工具链完整、支持缩容(离线),inode 数量在mkfs时就固定下来。适合小文件密集、可能需要调整容量的场景,比如系统盘、代码仓库盘、邮件存储。
XFS的特点是并行度高、大文件写入性能好、inode 动态分配、只支持扩容不支持缩容。适合大文件顺序写、高并发读写的场景,比如数据库数据文件、视频存储、日志盘。
创建时的参数,我实际会调整的其实就几个:
# ext4:降低保留块比例,大盘上能省出可观空间 mkfs.ext4 -m 1 -L data01 /dev/vg_data/lv_data # XFS:指定 su/sw 与底层 RAID 条带对齐(如果有 RAID) mkfs.xfs -f -L data01 -d su=64k,sw=4 /dev/vg_data/lv_data-m 1这个参数值得单独说一下。ext4 默认给 root 保留 5% 的空间,本意是防止普通用户把盘写满导致系统服务无法写日志。但一块 2T 的盘,5% 就是 100G,如果你的盘是纯数据盘、root 根本不会往里写东西,这 100G 就是白扔的。降到 1% 是常规做法,能省出 80G 左右。系统盘/建议保持默认或者至少 3%,别动。
XFS 的su/sw参数只在底层有 RAID 或者条带设备时才有意义,su是单个条带单元大小,sw是条带数量。写错了不会报错,但写入时会产生额外的读改写。拿不准的时候干脆不加,XFS 会用设备的默认几何参数,一般也不会太差。
-N参数控制 inode 数量,ext4 默认按每 16K 空间一个 inode 分配。如果你的场景是海量小文件(比如几百万个 1KB 的缓存文件),默认值可能不够用,df -i会告诉你 inode 使用率。反过来,如果盘上全是几个 G 的大文件,默认 inode 就浪费了。这个参数只能在mkfs时设定,tune2fs事后能调但很麻烦,所以规划阶段想清楚很重要。
3.2 fstab 写法:UUID 是底线,nofail 是保险
挂载配置写在/etc/fstab,这个文件写错一行,机器下次启动直接进 emergency mode,远程都连不上。我见过不止一次这种事故。
先看一个我常用的写法:
# 先拿到 UUID blkid /dev/vg_data/lv_data # 输出:/dev/vg_data/lv_data: UUID="a1b2c3d4-..." TYPE="ext4" # 写进 fstab UUID=a1b2c3d4-... /data ext4 defaults,noatime,nodiratime,nofail 0 2几个字段依次是:设备标识、挂载点、文件系统类型、挂载参数、dump 标志、fsck 顺序。
设备标识必须用 UUID 或 LABEL,绝对不要用/dev/sdb1。原因很实在:机器上加了一块盘、换了个 HBA、或者 BIOS 里的磁盘顺序变了,sdb就可能变成sdc,你 fstab 里写的/dev/sdb1指向的可能是另一块完全不相干的盘。UUID 是mkfs时生成的,跟设备节点名无关,稳得多。LVM 的逻辑卷可以用/dev/mapper/vg_data-lv_data或者/dev/vg_data/lv_data,这个路径是 LVM 自己维护的,也稳定。
noatime是必加的。默认情况下每次读文件都会更新 atime(访问时间),这意味着一个纯读操作也会产生写 IO。noatime直接关掉这个行为,在大量小文件读写的场景下能减少可观的写入量。nodiratime是它的目录版本,两个一起加更彻底。
nofail是保命的。如果这块盘因为硬件故障没识别出来,没有nofail的话,systemd 会一直等这个挂载点,超时后进入 emergency mode,机器等于挂了。加了nofail,挂不上就跳过,系统正常启动,你还能 SSH 进去处理。数据盘一定加,系统盘不要加——系统盘都挂了,启动起来也没意义。
fsck顺序那一列,根分区写 1,其他 ext4 分区写 2,XFS 和 swap 写 0(XFS 有自己的日志恢复机制,不走fsck)。这个字段填错了不会立刻出问题,但开机自检的行为会不符合预期。
3.3 容量规划的算法与几个必留的余量
容量规划不是"业务说要 500G 就分 500G"。我给个实际的算法:
第一步,算业务真实需求。数据库的话,看当前数据量 × 预期增长倍数 × 索引和临时空间系数(一般 1.5)。日志的话,看单日写入量 × 保留天数 × 1.3(压缩和峰值系数)。
第二步,加上文件系统开销。ext4 的元数据大概占 1%~3%,XFS 类似,LVM 有 PE 对齐损耗(一般小于 1%)。
第三步,加上安全水位。我一般留 20% 不用。原因有三个:一是很多文件系统在空间使用超过 90% 之后性能会明显下降(尤其是有大量小文件创建删除的场景,碎片率飙升);二是要给临时文件、备份文件、core dump 留出空间;三是真出问题时,你需要有空间去做数据迁移或者临时扩容。
第四步,考虑增长路径。如果按 20% 水位算出来需要 600G,而底层 VG 只剩 200G 空闲,那要么一次规划到位,要么明确记录"这块 LV 未来需要从哪个 VG 扩",别等到时候发现 VG 也没空间了,只能加物理盘。
一个具体例子:某台日志收集机的规划。单日日志写入 12G,保留 180 天,压缩比约 3:1。算下来 12 × 180 / 3 = 720G,加 30% 峰值系数得 936G,加元数据和安全水位,最终规划 1.2T。这个数字给出来的时候业务方觉得"太大了",但实际跑到第 150 天的时候使用率到了 78%,说明这个估算是对的。
4. LVM 组织与在线扩容的完整实操
4.1 PV、VG、LV 三层的关系与创建顺序
LVM 的三层结构,用个类比说清楚:PV 是砖块,VG 是砌好的墙,LV 是从墙上划出来的一块区域。砖块可以随时往墙上加,墙上的区域可以随时往外扩,但你不能凭空变出砖块。
创建顺序是自下而上的:
# 1. 创建 PV,把物理分区或者整块盘纳入 LVM 管理 pvcreate /dev/sdb1 /dev/sdc1 # 2. 创建 VG,把 PV 组织成一个存储池 vgcreate -s 32M vg_data /dev/sdb1 /dev/sdc1 # 3. 创建 LV,从 VG 里划出指定大小的逻辑卷 lvcreate -L 500G -n lv_app vg_data # 4. 格式化并挂载 mkfs.xfs -f /dev/vg_data/lv_app mkdir -p /data/app mount /dev/vg_data/lv_app /data/app第二步的-s 32M是 PE 大小,默认是 4M。这个参数决定了 LV 扩容的最小粒度——PE 是 4M 的话,扩容可以是 4M 的任意倍数;是 32M 的话就是 32M 的倍数。对于大容量的 VG,PE 设大一点能减少 LVM 元数据的体积和管理开销,32M 或者 64M 是常见选择。这个值在vgcreate时定死,后期改起来极其麻烦,规划时留意一下。
lvcreate还有两个常用变体:-l 100%FREE表示把 VG 剩余空间全给这个 LV,-l 50%VG表示按 VG 总量的百分比分配。我个人不太喜欢100%FREE,因为它把后路堵死了,之后想再建第二个 LV 就没空间了。除非你确定这个 VG 只服务一个 LV。
4.2 在线扩容的两种路径与参数计算
扩容分两种情况,处理方式完全不同。
情况一:VG 里还有空闲空间,直接扩 LV。这是最简单的:
# 查看 VG 剩余空间 vgs # 扩 LV,加 200G lvextend -L +200G /dev/vg_data/lv_app # 扩展文件系统 # XFS: xfs_growfs /data/app # ext4: resize2fs /dev/vg_data/lv_app更省事的写法是lvextend -L +200G -r /dev/vg_data/lv_app,-r参数会自动调用对应文件系统的扩容工具。我一开始也不爱用这个参数,怕它认错文件系统类型,实际用下来lvextend对 ext4 和 XFS 的识别是可靠的,后来基本都用-r了。
情况二:VG 没空间了,先扩 VG 再扩 LV。这时候要加新盘:
# 新盘先分区(或者直接用整盘做 PV,LVM 支持) parted -s /dev/sdd mklabel gpt parted -s /dev/sdd mkpart primary 1MiB 100% partprobe /dev/sdd # 加入 VG pvcreate /dev/sdd1 vgextend vg_data /dev/sdd1 # 然后再扩 LV lvextend -L +500G -r /dev/vg_data/lv_app这里的参数计算有个细节:如果你要扩的是"精确到某个目标大小",用-L 800G(目标绝对值)而不是-L +300G(增量)。增量的写法在脚本里反复执行会出问题——跑两遍就扩了两次。我一般把幂等性考虑进去,脚本里优先用绝对值,除非是人工交互操作。
另外lvextend有个-l +100%FREE的用法,把 VG 剩余空间全吃掉。这个在紧急扩容、先把空间用上的场景很实用,但用完记得vgs确认一下,别把别的 LV 的预留空间抢了。
4.3 ext4 与 XFS 在扩容缩容上的行为差异
这块是实操中最容易出偏差的地方,必须说清楚。
扩容方向:
- ext4 支持在线扩容(挂载状态下),也支持离线扩容。
resize2fs /dev/vg_data/lv_app在挂载状态下直接跑就行。 - XFS 支持在线扩容,但扩容的对象是挂载点而不是设备路径。
xfs_growfs /data/app,传设备路径会报错。这个细节很多人第一次用会栽跟头。 - 两者都只能往大了扩,不能往小了缩。
缩容方向:
- ext4 支持缩容,但必须先
umount,再e2fsck -f检查,然后resize2fs缩文件系统,最后lvreduce缩 LV。顺序错了就是数据损坏。而且缩容后的文件系统大小必须小于等于 LV 大小,否则文件系统直接不可用。 - XFS不支持缩容,官方态度很明确:要缩小就备份、重建、恢复。没有取巧的路子。
我的实际建议是:除非万不得已,不要缩容。缩容的风险收益比太差——缩 100G 省下来的空间,可能不够你处理一次数据恢复的时间成本。真觉得空间浪费了,更好的做法是把它当成"预留余量",或者用 LVM 的pvmove把数据挪到别的 PV 上,然后把这整块盘摘出来。
pvmove这个命令值得单独提一句,它是 LVM 里最有价值但最少人用的功能之一:
# 把 /dev/sdb1 上的数据全部迁到 VG 里的其他 PV pvmove /dev/sdb1 # 迁完之后从 VG 移除 vgreduce vg_data /dev/sdb1 pvremove /dev/sdb1整个过程在线完成,业务不中断。换盘、下线老设备、把数据从慢盘挪到快盘,都靠它。唯一的注意点是:目标 PV 上必须有足够空间,pvmove开始前会检查,空间不够直接拒绝。
5. 空间异常的排查路径与实战记录
5.1 df 显示满了但 du 加起来对不上
这是最经典的一类问题,也是我被问得最多的。现象是df -h显示/使用率 95%,但du -sh /*一个个加起来只有 60%。差出来的那部分去哪了?
有三种可能,按出现频率排:
第一种,文件被删除但进程还持有句柄。这是最常见的原因。某个进程打开了一个大日志文件,你rm掉了它,ls看不到,但进程的文件描述符还指着那个 inode,空间不释放。查法:
lsof +L1 # 或者 lsof | grep deleted输出里SIZE/OFF那一列就是被占用的空间,PID是持有它的进程。处理方式是重启该进程,或者更优雅一点:
# 清空文件内容而不删除文件(推荐,不影响进程写入) : > /proc/<PID>/fd/<FD>我遇到过一次,某服务的日志文件被手动rm了,但服务没重启,df一直显示 40G 被占着。定位到之后执行上面那行,空间立刻释放,服务完全无感。
第二种,挂载点覆盖。某个目录原本在根分区上,里面存了文件,后来又在同一位置挂载了新分区,原来的文件就被"遮住"了——du看不到(因为它只统计当前挂载的文件系统),但空间实际还在根分区上占着。查法:
# 先把挂载点 umount,看看那个目录里有没有东西 umount /data ls -la /data如果umount之后发现里面有一堆文件,就是这个原因。把这些文件挪到别处或者删掉,再重新挂载。
第三种,文件系统元数据和预留块。df报的Used + Avail加起来小于Size,差的那部分是 ext4 给 root 保留的 5%。这不是异常,是设计。想让它显示出来,可以tune2fs -m 1调整。
排查顺序我一般是:先lsof +L1,没发现再查挂载点覆盖,最后才怀疑文件系统本身。这个顺序是按概率排的,前面两个命中率加起来超过 90%。
5.2 inode 耗尽与海量小文件的处理
另一种"满了"是 inode 满了,df -h看着还有空间,但就是创建不了新文件,报No space left on device。这时候df -i一看,IUse%100%。
inode 是文件系统里记录文件元信息(权限、大小、数据块位置)的结构,每个文件至少占一个。ext4 在mkfs时就把 inode 总数定死了,默认每 16K 空间一个 inode。一块 1T 的盘大概有 6000 多万个 inode,正常用完全够。但如果你跑的是 session 存储、邮件队列、缓存目录这类场景,几百万个小文件很快就吃完了。
排查哪个目录的小文件最多:
# 统计当前目录下每个子目录的文件数,按数量排序 for d in /*; do echo "$(find $d -xdev -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head -20-xdev参数很关键,它让find不要跨越文件系统边界,否则会统计到别的挂载点里去,结果完全失真。
处理方式:
- 清理:找到根源目录,确认过期文件后删掉。删海量小文件用
rm -rf会很慢,可以用rsync -a --delete empty_dir/ target_dir/的技巧,速度能快一个数量级。 - 调整 inode 数量:
mkfs.ext4 -N 数量只能在格式化时设定,事后调不了。所以如果预判有海量小文件场景,规划阶段就要把 inode 数量调高。 - 换 XFS:XFS 的 inode 是动态分配的,理论上不会出现 inode 耗尽。这是我推荐小文件密集场景用 XFS 的一个重要原因。
5.3 常见问题速查表
我把实际遇到过的问题整理成一张表,出问题时先在这里对一遍,能省不少时间。
| 现象 | 大概率原因 | 排查命令 | 处理方式 |
|---|---|---|---|
df显示满但du对不上 | 已删除文件被进程持有 | lsof +L1 | 清空/proc/PID/fd/FD或重启进程 |
df显示满但du对不上 | 挂载点覆盖了原目录文件 | umount后ls | 迁移或删除原目录文件 |
写入报No space left但df -h有空间 | inode 耗尽 | df -i | 清理小文件或换 XFS |
分区后mkfs报设备不存在 | 内核未刷新分区表 | lsblk、dmesg | partprobe或blockdev --rereadpt |
| 重启后进 emergency mode | fstab 有错误条目 | mount -a测试 | 修正 fstab,数据盘加nofail |
| 盘识别了但容量只有一半 | MBR 分区表的 2TB 限制 | parted print | 转 GPT(需重新分区) |
| IO 延迟异常高、吞吐上不去 | 分区未对齐 | parted unit s print | 重新分区,起始扇区 1MiB 对齐 |
xfs_growfs报错 | 传了设备路径 | - | 改传挂载点路径 |
| 无法缩容 | XFS 不支持 | - | 备份重建,或用pvmove迁移 |
6. 性能调优与硬件健康监控
6.1 挂载参数对 IO 的实际影响
除了前面说的noatime,还有几个参数值得关注。
data=writeback和data=ordered(ext4):后者是默认值,保证数据在元数据提交前落盘,安全性好;前者性能略高,但崩溃时可能看到旧数据。数据库场景我一般不动这个,保持默认。
discard参数:SSD 上开启后,删除文件时会即时发送 TRIM 命令。听起来很好,但实测下来对性能有负面影响,因为每次删除都触发一次 TRIM,在大量小文件删除的场景下会明显拖慢。更好的做法是不开discard,改用定时fstrim:
# 手动执行一次 fstrim -av # 或者配个每周的定时任务 # /etc/cron.weekly/fstrim #!/bin/bash /sbin/fstrim -avfstrim -av会遍历所有支持的文件系统执行 TRIM,-v输出详细结果。这个做法是业界比较公认的方案,兼顾了性能和 SSD 寿命。
IO 调度器这块,NVMe 盘建议用none(也叫noop),因为 NVMe 本身的队列深度和多队列机制已经很完善,内核层的调度反而增加延迟。传统 SATA SSD 用mq-deadline比较均衡。机械盘上如果跑的是数据库这种随机读多的负载,bfq能改善响应时间。修改方式是:
# 查看当前调度器 cat /sys/block/sda/queue/scheduler # 临时修改 echo mq-deadline > /sys/block/sda/queue/scheduler # 永久生效写 udev 规则 # /etc/udev/rules.d/60-ioscheduler.rules ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="mq-deadline"注意这个规则只对sd开头的设备生效,NVMe 盘要单独写KERNEL=="nvme[0-9]n[0-9]"。
6.2 坏道检测与 SMART 监控的正确姿势
磁盘出问题前一般会有征兆,SMART 就是那个"体检报告"。
# 查看完整 SMART 信息 smartctl -a /dev/sda # 只看健康状态 smartctl -H /dev/sda # 启动短自检(约 2 分钟) smartctl -t short /dev/sda # 查看自检结果 smartctl -l selftest /dev/sda重点看几个属性:Reallocated_Sector_Ct(重映射扇区数,非零就要警惕)、Current_Pending_Sector(待映射扇区,非零说明有读不出来的块)、UDMA_CRC_Error_Count(接口 CRC 错误,通常是线缆或背板问题)。这三个值任何一个持续增长,就该准备换盘了。
Reallocated_Sector_Ct我个人的判断线是:小于 10 可以观察,10 到 100 之间要开始准备备件,超过 100 或者短期内快速增长,直接换,别犹豫。这个数字只增不减,涨上去就回不来了。
对于不能停机的场景,badblocks可以做只读扫描:
# 只读非破坏性扫描 badblocks -sv /dev/sdb-s显示进度,-v输出详细。注意不要加-w,那是写测试,会毁数据。扫描一块 4T 的盘大概要十几个小时,建议放在业务低谷期跑,或者用-p参数分段扫描。
监控层面,我一般会把smartctl -H和关键属性的采集接进监控系统,每天跑一次。磁盘故障从来不是突发的,从 SMART 出现异常到真正挂掉,通常有几周甚至几个月的窗口期,抓住这个窗口就能做到无损替换。
7. 我在实际运维中沉淀的几条经验
最后说几个不太适合放进标准流程、但实际很有用的做法。
第一个,新盘上线前先跑一遍写测试。不是为了测性能,是为了确认这块盘不是"到手就坏"的。用fio做一个顺序写:
fio --name=write_test --filename=/dev/sdb --direct=1 \ --bs=1M --size=10G --rw=write --ioengine=libaio --iodepth=16跑完看dmesg有没有 IO 错误,看 SMART 的Reallocated_Sector_Ct有没有变化。我遇到过一块新盘,装上去第二天就开始报重映射扇区,幸好数据还没写进去。
第二个,/etc/fstab改完必须mount -a验证再重启。这个动作花不了十秒,但能避免一次远程救不回来的事故。mount -a会把 fstab 里所有没挂载的条目都挂一遍,有错立刻报出来。
# 改完 fstab 后 mount -a # 无输出就是成功,有报错就逐条排查第三个,给关键分区做容量趋势记录。df的快照本身没意义,有意义的是"过去 30 天这个分区的增长率"。按现在的增速,还有多少天会到 90%。这个数据能让你从"被动救火"变成"提前规划"。我一般是用一个简单的脚本每天跑一次,把df -P的输出追加到一个日志文件里,需要的时候拉出来看看曲线。
第四个,扩容脚本一定要写成幂等的。前面提过-L +200G跑两遍会扩两次的问题。我现在的写法是先算出目标值,再用绝对值扩容,脚本里加上"如果当前大小已经大于等于目标值就跳过"的判断。这个习惯救过我不止一次——半夜迷糊的时候手工执行两遍是常事。
第五个,lsblk是我最常用的一个命令,没有之一。它用树状结构一次展示块设备、分区、LVM、挂载点的完整关系,比fdisk -l、df -h、mount三个命令加起来还好用。尤其是排查"这块盘到底挂到哪了""这个 LV 在哪个 VG 上"这类问题时,一眼就能看明白。建议养成习惯,动手之前先lsblk -f看一眼全局,能避免大部分"改错盘"的惨剧。