我之前管过几十台生产服务器,说句实话,运维里最磨人的不是CPU飙高,也不是服务宕机,而是存储出问题。CPU满了顶多卡一会儿,服务挂了可以重启,但磁盘满了、文件系统变只读、inode耗尽,那真是数据库写不进去、日志落不了盘,整个业务直接僵住。而且存储的报错往往还很“温柔”,一开始只是慢,等你发现的时候通常已经快没救了。所以Linux存储管理这块,我建议每个运维和后台开发都当成基本功来练,别等出了问题再翻文档。
这篇内容我打算从存储栈的整体思路讲起,把磁盘分区、LVM、文件系统选型、性能监控、常见故障排查这几个环节串起来。文章里用的命令都是我在生产环境实测过、反复踩过坑之后留下来的“保守方案”,不一定是最炫的,但一定是最稳的。不同基础的读者都能看:刚入门的人可以照着一步步操作,老手可以直接跳到故障排查和性能诊断那两节对照一下自己的处理思路。
1. 存储管理的全局视角与核心概念
1.1 存储栈到底长什么样
先说清楚一件事:Linux的存储不是“一块硬盘”那么简单,它是一个分层的栈。从上往下,大概是这样的关系:
应用进程 → 文件系统 → 块设备层 → 磁盘驱动 → 物理磁盘
这里面每一层都有自己的职责。应用读写文件时,文件系统负责把“文件”这个逻辑概念转成“块”的读写请求,块设备层负责处理这些请求的调度和排队,最终由磁盘驱动把指令发给物理硬件。我们在终端里看到的/dev/sda、/dev/nvme0n1,其实是这个栈的底层入口,是块设备节点。
理解这个分层特别重要,因为不同的问题出在不同层。比如df -h看磁盘满了,那是文件系统层的容量问题;iostat看到util居高不下,那是底层设备的性能问题;dmesg里刷I/O error,那可能是硬件链路问题。很多人一遇到存储问题就只知道“重启”“删文件”,就是因为没有建立这个分层概念。
1.2 我总结的存储管理三原则
这几年处理过的存储故障没有一百也有八十个,我自己总结了三句话:先规划再动手,留冗余别赌运气,监控比急救重要。
先说规划。一台新服务器到手,第一件事就是根据业务类型把存储布局定下来。系统盘、数据盘、日志盘分开,别把所有东西塞进一个分区。日志盘放独立分区还有个好处,就是日志写满时不会拖垮系统盘,业务还能续命。我见过太多人图省事,一个根分区挂到底,最后日志把根分区塞满,连ssh都登不进去。
再说冗余。磁阵也好、云盘也好,生产环境一定要有冗余设计,要么RAID,要么LVM的快照,要么干脆靠云平台的多副本。别相信“单块盘很稳定”这种话,硬盘的故障率是实实在在的,而且往往在你最忙的时候坏。
最后说监控。存储问题的特点是“慢刀子割肉”,它不会突然崩溃,而是逐渐变满、逐渐变慢。所以存储监控的指标一定要提前配好:磁盘使用率、inode使用率、I/O等待、读写延迟,这些指标告警的阈值分级设好,比如使用率80%提醒、90%警告、95%紧急。有了监控,很多故障根本轮不到“排查”,在萌芽期就被处理掉了。
2. 磁盘识别、分区与设备管理
2.1 从设备命名到磁盘信息排查
Linux下磁盘设备的命名规则算是新手比较容易懵的地方。老式SATA/SCSI硬盘叫/dev/sda、/dev/sdb这种,字母按识别顺序排列;NVMe固态硬盘叫/dev/nvme0n1,其中0是控制器编号,n1是命名空间编号;云服务器的虚拟磁盘可能是/dev/vda、/dev/vdb。
这些命名并不是永久固定的,重启后盘符可能变化,所以生产环境里千万别在脚本里硬编码盘符,至少要用UUID或者LVM的逻辑卷名来引用设备。我在早期维护一台机器时,就因为脚本里写死了/dev/sdb,结果某次系统重启后新硬盘抢占了盘符,脚本往原来的盘上写数据,差点出事。后来所有挂载点全部改成UUID,再也没出过这种问题。
排查磁盘信息最常用的几个命令,建议形成肌肉记忆:
lsblk # 查看块设备树状结构,信息最直观 lsblk -f # 同时显示文件系统类型和UUID fdisk -l # 查看磁盘分区表和容量 blkid # 查看分区的UUID、文件系统类型 df -hT # 查看文件系统挂载和使用情况 dmesg | grep sd # 查看新挂载磁盘的内核日志lsblk是我最推荐优先使用的命令,它能把“磁盘→分区→挂载点”的层级关系用树状展示出来,一眼就能看清整台机器的存储结构。刚接手一台陌生服务器,我第一件事永远是lsblk -f,先把家底盘清楚。
2.2 MBR和GPT怎么选
分区表是磁盘的“目录册”,决定磁盘能被怎么切分。目前主流就两种:MBR和GPT。
MBR是传统方案,优点是兼容性极好,老机器、老系统都认识;缺点是单盘最大只能支持约2TB,而且最多只能有4个主分区(或者3个主分区+1个扩展分区,扩展分区里再切逻辑分区)。GPT是新一代标准,单盘容量上限大得多(9.4ZB),分区数量基本不受限,还带了校验机制,分区表损坏时能自动恢复。
我的选择标准很明确:只要是新部署的机器,一律用GPT,不管磁盘大小。别觉得2TB以下用MBR没毛病,以后扩容、迁移的时候,GPT的灵活性能省下大量麻烦。另外UEFI引导的机器必须配GPT,这个几乎已经是共识了。只有一种情况我还会用MBR,就是维护那种特别老的、只支持BIOS引导又不支持UEFI的遗留设备。
创建GPT分区表用parted比较顺手:
parted /dev/sdb mklabel gpt parted /dev/sdb mkpart primary 1MiB 100%2.3 用fdisk完成一次实际分区
分区这个操作本身不难,但很多人容易在细节上翻车。我以一个真实的扩容场景举例:新加一块200GB的数据盘/dev/sdb,计划切成一个分区挂载到/data。
# 1. 查看磁盘是否被系统识别 lsblk # 2. 用fdisk进入交互式分区 fdisk /dev/sdb # 交互操作如下: # 输入 n 新建分区 # 输入 p 主分区 # 分区号直接回车默认 # 起始扇区直接回车默认 # 结束扇区输入分区大小,比如 +200G # 输入 w 保存并退出分区完成后,需要让内核重新读取分区表,再格式化、挂载:
partprobe /dev/sdb # 让内核重新读取分区表 mkfs.ext4 /dev/sdb1 # 格式化 mkdir -p /data mount /dev/sdb1 /data # 临时挂载 echo '/dev/sdb1 /data ext4 defaults 0 2' >> /etc/fstab # 永久挂载这里要特别提醒两个坑。第一个是分区之前务必确认磁盘是空的,或者你确认要覆盖的那块盘,拿lsblk和fdisk -l反复核对,真的有人在生产服务器上把系统盘当成空盘重新分区,数据全没的时候哭都来不及。第二个坑是/etc/fstab写错会导致重启起不来,改动之后先用mount -a测试一下配置有没有问题,再决定是否重启。
3. LVM:生产环境的首选存储方案
3.1 LVM的核心逻辑:三层映射
直接给一个生活化的类比:传统分区就像在布上直接剪出一块块互不相通的布片,剪坏了整块布就废了;LVM则像是先建一个仓库,再在仓库里按需隔房间,每个房间还能在仓库有空间的情况下随时扩大缩小。
LVM(Logical Volume Manager)把存储拆成三层:PV(物理卷)、VG(卷组)、LV(逻辑卷)。PV可以是一块磁盘或一个分区,是整个系统的基础积木;多个PV汇聚在一起组成一个VG,相当于一个可弹性分配的总空间池;在VG上再分出来的一个个LV,才是我们实际格式化挂载、让应用使用的“逻辑磁盘”。
为什么要费这么一层劲?因为LVM解决了传统分区最头疼的问题:动态扩容。传统分区切好了大小就固定了,空间不够时要么重新分区加盘、要么靠软链接转移到新目录,都很折腾。LVM可以在线扩容LV和文件系统,业务不中断;空间碎了也不怕,VG作为池子会自动调配。生产环境里几乎是我首选方案的默认配置。
LVM还有快照功能,可以快速做只读快照用于备份或测试,虽然生产库级别用LVM快照不算最佳实践,但对一些小规模应用、或临时需要一致性备份的场景,非常实用。
3.2 从零到一:把一块新盘变成LVM
假设新磁盘/dev/sdc要加入存储体系,完整流程如下:
# 1. 创建PV pvcreate /dev/sdc1 # 2. 创建VG,名称为vgdata vgcreate vgdata /dev/sdc1 # 3. 在VG上创建LV,大小100G,名称为lvdata lvcreate -L 100G -n lvdata vgdata # 4. 格式化并挂载 mkfs.xfs /dev/vgdata/lvdata mkdir -p /data mount /dev/vgdata/lvdata /data记住一个原则:PV直接建在分区上,不建议直接建在整块磁盘上。原因是如果把PV直接建在/dev/sdc这种整盘设备上,后续磁盘要挪走、替换或者做分区调整时非常被动,而在分区上建PV则灵活很多,而且分区也方便你打标签识别。虽然直接pvcreate /dev/sdc能少一步,但长远来看是给自己埋雷。
查看LVM各层信息的命令是pvs、vgs、lvs,它们分别展示物理卷、卷组和逻辑卷的状态。刚开始用LVM的人很容易搞混,我的记忆口诀是:p是地砖、v是仓库、l是房间。
3.3 在线扩容:最常用的生产操作
LVM的扩容是整个存储管理里出场率最高的操作。磁盘不够用了,想从100G扩到150G,步骤是:
# 1. 先看VG里有没有足够空闲空间 vgs # 2. 扩展LV lvextend -L 150G /dev/vgdata/lvdata # 3. 关键一步:同步扩展文件系统 # ext4用这个: resize2fs /dev/vgdata/lvdata # xfs用这个: xfs_growfs /data第三步是最容易忘记的。很多新手执行完lvextend后,因为看不到空间变化,以为自己操作失败,然后重复执行。实际上lvextend扩的是LV层,文件系统的容量需要额外同步,这一步漏了,你扩多少都没有意义。我的习惯是扩完LV后立刻用df -hT验证。
如果VG里的空闲空间也不够,则需要先加新盘进来扩容VG:
pvcreate /dev/sdd1 vgextend vgdata /dev/sdd1卷组池子变大了,再执行前面提到的lvextend和文件系统扩展,整个流程一气呵成。
3.4 LVM缩容、删除与日常注意事项
扩缩容里我要专门强调一句:我基本不建议生产环境执行缩容操作。LVM缩容的流程要先缩文件系统,再缩LV,顺序反了文件系统直接损坏。而在线缩容文件系统(尤其是xfs)支持极差,连ext4的在线缩容也要求先解除挂载。所以一旦LV建大了,宁可留在那浪费一点空间,也别为了“省空间”去折腾缩容。缩容操作可以放在测试环境练手,生产环境不要贪这种好处。
删除LVM更干脆:
lvremove /dev/vgdata/lvdata vgremove vgdata pvremove /dev/sdc1删除之前务必备份数据,这个不用我多说。日常维护中我还习惯用lvs -o +lv_size,lv_attr来看看LV的读写状态,lv_attr里如果出现s,说明这个LV有快照存在,删除原LV会连带影响快照,这个需要尤其注意。
4. 文件系统选型与挂载优化
4.1 ext4、xfs、btrfs到底怎么选
文件系统是存储管理中最影响日常体验的环节。Linux下主流文件系统就那几个:ext4、xfs、btrfs,偶尔还会遇到zfs(在Linux上通常通过OpenZFS模块使用)。
直接给结论:
| 文件系统 | 优势 | 局限 | 推荐场景 |
|---|---|---|---|
| ext4 | 兼容性最好、工具链成熟、修复工具可靠 | 单文件性能和xfs比稍弱、在线扩展速度一般 | 系统盘、通用场景、老旧系统 |
| xfs | 大文件和高并发读写性能强、在线扩容快、数据分配策略先进 | 不能在线缩容、单文件系统损坏时修复难度较高 | 数据盘、数据库、文件服务器 |
| btrfs | 自带快照、子卷、校验和、压缩 | 稳定性历史上有波折、性能调优复杂 | 个人桌面、实验环境、对快照有强需求的场景 |
生产环境我个人的组合习惯是:系统盘用ext4,数据盘用xfs。原因很简单,系统盘上的文件小而碎,ext4对这种场景充足且修复工具完善;数据盘往往放数据库和日志,xfs的大文件处理能力更优。而且xfs的在线扩容(xfs_growfs)非常稳,几乎不会出问题。
btrfs我劝新手别一上来就在生产环境里试,它的功能和坑一样多。我自己用过一段时间,快照确实方便,但在某些版本上出现过性能和稳定性问题,如果团队没有专门研究它的人,慎用。
4.2 mkfs、mount与fstab参数细节
格式化时,我建议明确指定文件系统参数,而不是全用默认值。比如用ext4格式化数据盘:
mkfs.ext4 -m 0 -T news /dev/vgdata/lvdata-m 0表示不预留给root的块,默认是5%,大容量磁盘上这5%可能浪费几十GB;数据盘一般不需要预留,但系统盘一定别用-m 0,系统盘要给root留出救援空间,否则根分区满了连救援模式都进不去。
挂载参数对性能和可靠性的影响,很多人会忽视。推荐生产环境的数据盘挂载选项:
mount -o noatime,nodiratime,defaults /dev/vgdata/lvdata /datanoatime能减少每次读文件时的元数据更新,在频繁读的场景下减少不必要的磁盘写操作,性能提升非常明显。SSD上配合discard或定期fstrim也很关键,我在4.3里有完整说明。
/etc/fstab的配置格式是:设备名 UUID 挂载点 文件系统类型 挂载参数 dump fsck。强烈建议用blkid查UUID来写,不要直接写/dev/sda1这种动态盘符:
UUID=8a2a4c01-1b2b-4c5d-9f6e-abcdef123456 /data xfs defaults,noatime 0 2最后那一列数字,0表示不备份,2表示fsck检查顺序。系统盘通常写1,数据盘写2。机动点在于:LVM逻辑卷的UUID也是固定的,写成/dev/vgdata/lvdata这种路径在多数情况下也稳定,因为LVM的命名不像盘符那样容易漂移,但我依然推荐统一用UUID,减少特例。
4.3 SSD优化、TRIM与定期维护
SSD普及之后,挂载参数里多了一个必须关注的概念:TRIM。简单说,SSD删除文件后,需要让主控知道哪些块可以清理回收,否则长期下来写入性能会严重下降。Linux下有两种启用方式,一是挂载时加discard参数,二是通过fstrim定期执行。
我个人更推荐后者。discard参数是在每个文件删除的瞬间都发TRIM命令,在小文件频繁删除时反而增加开销;而fstrim是定时批量回收,对性能影响更小。建议通过cron或systemd timer每周执行一次:
fstrim -av系统可以用systemctl enable --now fstrim.timer直接启用自带的定时任务。另外SSD还有一个优化点:noatime挂载参数的收益在SSD上依然存在,别因为是固态就觉得无所谓,减少不必要的元数据写就是延长寿命。
还有个容易忽略的点,SSD上的分区对齐。分区起始扇区默认从2048(即1MiB对齐)起步,基本都满足现代SSD要求。如果你看到老磁盘的分区扇区起始值是63,很有可能是老的MBR对齐方式,读写性能会有明显损耗。这个可以用fdisk -l查看,正常人不会再遇到,但维护老机器时会看到。
5. 磁盘性能问题诊断与监控
5.1 性能观测:从iostat到iotop
存储问题分容量和性能两条线,容量问题好判断,性能问题往往更隐蔽。我排查存储性能问题时的命令队列是:iostat看总体、iotop看进程、vmstat看等待、sar看历史。
iostat是最核心的,重点看这几个指标:
iostat -x 1%util:设备忙绿度,接近100%说明设备已经接近饱和,但单独看这个值会被SSD的多队列特性误导,SSD即使饱和util也可能看起来不高,还要结合await来判断await:I/O请求的平均等待时间(毫秒),机械盘正常应该在10ms左右,超过20ms就要注意了;SSD通常在1ms以内svctm:实际服务时间,这个值在新型设备上往往不准确,参考意义有限r/s和w/s:每秒读/写请求数,结合块大小可以看出是大量小IO还是少量大IO
iotop用来定位具体是哪个进程在疯狂读写:
iotop -oP-o只看有IO的进程,-P显示进程名而不是线程。我曾经定位过一个数据库卡顿问题,最后就是用iotop发现有个备份脚本在凌晨全量备份时把IO吃满了,白天的业务查询全在排队。
vmstat 1里的wa列代表CPU等待IO完成的时间比例,如果这个值持续超过30%,基本可以断定存储是瓶颈,CPU再闲也没用,因为大量时间花在等I/O上。
5.2 经典的性能排查路径
存储性能问题的排查,我按这个顺序来:
第一步,确认问题是不是全局性的。用top看负载,用vmstat看wa,如果整机都卡,那重点看共享存储那层;如果只有某个应用卡,接网络连接查它的日志和锁。
第二步,用iostat -x 1确认是哪块盘出问题。多块盘的环境,别想当然认为是数据盘,有时候日志盘先爆满导致整个文件系统阻塞,也会拖垮业务。
第三步,定位到具体进程。iotop看实时PID,lsof看进程打开的文件,很多性能问题其实是应用层写法造成的,比如大量小文件随机读写。有一次我发现数据库查询慢,查到最后竟是因为临时表放在了机械盘上,把临时目录挪到SSD后性能立刻上来了。
第四步,检查文件系统层就是查询是否满了,包括inode。磁盘满时表现就是写入卡住,这个是假性性能问题,但最容易被忽略。所以遇到性能问题我都习惯顺手df -h和df -i看一眼。
第五步,看硬件层。dmesg | grep -i error查看有没有I/O错误,用smartctl -a /dev/sda查看磁盘健康状态。如果SMART里Reallocated_Sector_Ct在持续增长,这块盘基本要准备换了。
整个排查路径的核心思路是从全局到局部、从硬件到应用逐层缩小范围,不要一上来就被业务方带偏去调数据库参数。
6. 存储常见问题与应急处理
6.1 磁盘明明没满却报“No space left on device”
这个问题只要做久了运维一定遇到过。df -h一看磁盘空间还剩几十GB,但应用就是写不进去文件,报错还是“磁盘满”。
这里往往是两种原因:第一是inode耗尽,文件总的数量达到了上限,而不是容量不够。可以用df -i确认,如果IUsed%到了100%,说明inode用完了。这种情况多见于大量小文件场景,比如消息队列的积压文件、缓存目录、session文件等。解决方式是找出这些海量小文件的目录并清理。
df -i find /data -xdev -type f | wc -l # 看看文件数量到底多少第二个原因是删除文件后空间未释放。进程还在持有已删除文件的文件句柄,导致磁盘空间被占用却不显示。用lsof | grep deleted找到那些被删但还被进程keep的文件,确认后重启进程或让进程重新打开日志文件,空间就会释放。
我曾处理过一个案例,日志轮转脚本把日志文件删了,但应用进程一直握着旧文件句柄,结果磁盘使用率卡在90%几天不动,重启应用后空间立刻掉到30%。所以日志轮转一定要用copytruncate或让应用自己重新打开文件的方式,而不是直接rm。
6.2 inode耗尽深度排查
inode问题在文件数量很大的目录上特别常见。一个目录如果堆了几百万个小文件,就算每个文件只有几KB,也会很快把inode空间吃光。
排查inode到底谁占用的方法是:
for dir in /data/*/; do echo "$dir: $(find $dir -xdev -type f | wc -l)" done也可以直接查看各目录的子目录数量:ls -l /data | wc -l。找到目标后,最简单的清理思路是按时间删除,保留最近N天文件,旧文件分批删除:
find /data/cache -type f -mtime +30 -delete注意批量删除海量小文件时不要用rm -rf直接整个目录删,系统会长时间卡在删除操作上,我的经验是先mv到临时目录再后台删,这样业务路径立刻释放,删除操作放到后台慢慢跑:
mv /data/cache /data/cache_trash_$(date +%s) nohup rm -rf /data/cache_trash_* &6.3 突发只读与文件系统修复
Linux在检测到文件系统异常时会强制将挂载点切换为只读,防止进一步写入破坏数据。如果你发现目录只能读不能写,第一反应别慌,按下面步骤处理。
先确认挂载状态:
mount | grep /data touch /data/test.txt # 看报什么错如果确实变成只读,一般要先把业务停掉或降级(只读状态重启服务可能起不来),然后卸载再修复。修复前最好先做块级别的备份,尤其是数据重要的场景,用dd备份整盘或分区。
umount /data fsck -y /dev/vgdata/lvdatafsck执行时要保持耐心,大分区跑几十分钟都很正常。修复完重新挂载:
mount /data不要带-o ro,默认读写挂载看能否成功。这里有个关键的坑:如果你没有umount就直接fsck,大概率会把文件系统搞出更严重的损坏。fsck必须要求分区处于未挂载状态,这句话我一年要说十次。
还有一类只读不是故障,是配置:某些系统(比如/etc/fstab里意外加上了ro参数)会开机时以只读方式挂载。这种情况检查fstab,去掉ro再重新挂载即可,不必折腾fsck。
6.4 根分区满导致的“假死”急救
根分区满是最绝望的场景之一,因为连rm都经常无法正常执行,很多命令需要写入临时文件或日志,结果又失败。遇到根分区99%、系统趋近假死,我的急救顺序是:
第一,立刻看是否有大文件可以腾空间:
du -sh /* 2>/dev/null | sort -rh | head du -sh /var/* 2>/dev/null | sort -rh | head重点查/var/log、/tmp、/var/cache这几个容易养肥的目录。
第二,清理日志和包管理器缓存。journalctl --vacuum-size=100M清掉systemd日志历史,yum clean all或apt clean清掉软件包缓存,通常能瞬间回收好几个GB。
第三,如果空间实在太紧张,连命令都执行不了,可以先尝试删除/var/log下最老的日志文件救急。真要救不回来,只能重启系统进入单用户模式,在最小环境里清理。所以这就是我前面强调系统盘要留5%的原因,这5%才是真正保命的救命稻草。
最后分享几个实操习惯
存储管理这块,我用得最顺手的一个习惯是:每台服务器上线时就把所有存储相关配置整理成文档。分区表结构、LVM布局、文件系统挂载参数、监控指标清单,全部写清楚。故障发生时最忌讳现场翻历史,配置文档能让你在慌乱的时刻一眼定位问题。
另一个习惯是任何涉及删除、缩容、重新分区的操作前,先在测试环境完整走一遍再上生产。有人说“生产环境的存储操作没法完全模拟”,确实,但流程可以演练,命令可以验证。我因为在测试环境练过,实际操作时手忙脚乱的情况少了很多。
最后再分享一个很多人不注意的小技巧:定期给服务器做一次存储体检,内容包括df -h、df -i、iostat -x、smartctl -a四项。每项耗时不超过一分钟,却能让你在磁盘故障发生前就发现隐患。存储这东西,平时多看一眼,关键时刻少熬一夜。