搞Linux这行当,绕不开的就是存储管理。我最早在机房折腾服务器的时候,被磁盘满、IO飙升、挂载失败这些问题折腾得够呛,后来才慢慢摸清楚,存储管理其实是一条完整的链路:从最底层的磁盘识别、分区规划,到文件系统选型、挂载配置,再到LVM逻辑卷管理、容量监控与故障排查。每一个环节都有坑,而且很多坑是文档里不会写清楚的。
这篇东西不打算写成命令大全,而是想把我在生产环境里实际用过、验证过的Linux存储管理思路和操作抖出来。适合刚接触Linux的人,也适合那些被存储问题反复折磨过的运维和开发者。你说你面试Linux岗位,存储这块也是躲不开的考点;你说你在维护服务器,那磁盘规划和故障排查就是基本功。不管是哪种场景,看完这篇,你至少能对存储管理有一个从底层到上层的完整认知,而且可以直接照着实操。
1. 先把家底盘清楚:Linux下的块设备识别与规划
1.1 从lsblk开始的设备视角
很多人一上来就敲fdisk -l,这没错,但我更习惯先用lsblk看整体拓扑。lsblk的输出是树状的,能直观看到一块磁盘被分成了几个分区、每个分区又属于哪个逻辑卷、挂载在哪个目录下。这个视角对定位“我的数据到底放在哪块盘上”极其有用。
$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 500G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 499G 0 part └─vgdata-root 253:0 0 499G 0 lvm /从这个输出能看出来,物理设备叫sda,分了sda1和sda2两个分区,sda2被做成了LVM物理卷,上面建了逻辑卷vgdata-root,最终挂载到根目录。这种层级关系,用fdisk -l是看不出来的。
设备命名的规律也需要心里有数:sd开头的是SCSI/SATA/USB等走通用驱动框架的磁盘,后面的字母按发现顺序排,sda就是第一块,sdb是第二块;nvme0n1是NVMe固态硬盘,0是控制器编号,n1是命名空间里的第一块盘,它下面的分区是nvme0n1p1这种带p的格式;云服务器里常见的vda、vdb是虚拟磁盘。别小看这些命名细节,后面排查“设备名漂移”问题的时候,你会感谢自己懂这些。
还有一个容易被忽略的点:Linux里设备名是内核动态分配的,重启之后/dev/sda和/dev/sdb可能对调。这也是为什么我一直强调,能不用设备名就不用设备名,后面讲/etc/fstab时我会重点展开。
1.2 分区表选型:MBR和GPT别选错
分区表是磁盘最开头的元数据,负责描述这块盘怎么切分。现在的服务器和PC基本都选GPT,除非你有兼容老机器或老系统的特殊需求。
MBR是老古董,有四大限制:单块盘最大只能认2TB,超过的部分要么浪费、要么用非常规手段处理;最多只能有4个主分区,想多分就得靠扩展分区套逻辑分区,麻烦;分区表本身没有冗余校验,坏了就真坏了;只支持传统BIOS引导。GPT则没有2TB上限,主分区数量理论上是128个,并且分区表在磁盘开头和结尾各存一份,有CRC校验,坏了一份还能救回来。现在的UEFI引导也要求磁盘用GPT。
实际操作中,我建议超过2TB的盘一律GPT,小于2TB但用于生产环境的新盘,也没理由不用GPT。分区工具我基本用parted,因为它同时支持MBR和GPT,而且可以写脚本批量处理。
# 使用GPT分区表 $ parted /dev/sdb mklabel gpt # 创建一个从1MiB开始、大小为500GiB的主分区 $ parted /dev/sdb mkpart primary 1MiB 500GiB # 查看结果 $ parted /dev/sdb print说一个细节:parted创建分区时起始位置建议从1MiB开始,不要从旧习惯的0或1MB开始,这样可以保证分区对齐到现代磁盘的物理扇区上。不对齐的话,读写会出现“读一个扇区要碰两次物理块”的情况,性能会打折扣,固态盘上影响更明显。这个细节是我当年做性能测试时发现的,同一块盘,对齐前后随机读写性能能差出百分之二三十。
2. 核心环节:格式化、文件系统选型与挂载
2.1 文件系统选型:ext4、XFS、Btrfs和ZFS怎么选
分区建好之后,要在上面创建文件系统,这就是常说的“格式化”。文件系统选型是存储管理里最容易被低估的决策。我用过的文件系统不少,给你一个真实场景下的选择逻辑。
ext4是Linux的老牌默认文件系统,兼容性极好,几乎所有发行版都能挂载,坏了也好修。如果你不确定该选什么,选ext4不会犯大错。XFS在很多生产环境里是更好的选择,尤其是数据库、虚拟化、大数据这类需要大文件、高并发读写的场景。XFS在并发写入上比ext4强,支持在线扩容(xfs_growfs),而且挂载时用noatime选项能显著减少写操盘次数。
Btrfs适合需要快照、压缩、校验和这类“高级功能”的场景,比如个人NAS、容器存储池,但它在某些高负载场景下出现过性能波动,生产环境要慎重。ZFS不是Linux内核原生支持,通常装在ZFS-on-Linux(OpenZFS)里,它功能最全,但架构上需要吃比较多内存,一般用在专门的文件服务器上。
| 文件系统 | 优点 | 典型适用场景 | 注意点 |
|---|---|---|---|
| ext4 | 兼容性最好、修复工具成熟 | 通用系统盘、/boot分区、不确定场景 | 单文件上限较大但仍小于XFS的顺手区间 |
| XFS | 大文件、并发读写强、在线扩容方便 | 数据库数据盘、虚拟化存储、大数据 | 缩容很麻烦,空间规划要一步到位 |
| Btrfs | 快照、透明压缩、自校验 | NAS、容器存储、个人折腾 | 高负载下性能波动需实测 |
| ZFS(OpenZFS) | 数据完整性最强、压缩率高 | 文件服务器、备份存储 | 吃内存,运维复杂度高 |
我自己的习惯是:根分区和/boot用ext4,数据盘尤其是数据库目录用XFS,特殊需求才考虑Btrfs或ZFS。格式化命令很直接:
$ mkfs.ext4 /dev/sdb1 $ mkfs.xfs /dev/sdc1等等,在格式化之前,我想提醒一句:mkfs会清空整个分区上的所有数据,执行前务必确认设备名没搞错。我见过不止一次,因为云控制台和系统内的设备名对应关系看反了,一条mkfs下去把数据盘格式化了。稳妥做法是先用lsblk -f或者blkid查出每个设备的UUID和已有文件系统,再三确认再动手。
2.2 挂载与/etc/fstab:千万别让开不了机
格式化之后,分区要挂载到某个目录才能用。手动挂载一条命令就搞定:
$ mount /dev/sdb1 /data但重启之后这个挂载就没了,所以要把挂载关系写进/etc/fstab,让系统开机时自动完成挂载。/etc/fstab每行有6个字段,依次是:设备标识、挂载点、文件系统类型、挂载选项、dump标记、fsck顺序。
这里要强调一个关键经验:设备标识一定要用UUID,不要用/dev/sdb1这种设备名。理由我在前面提过,设备名在重启后可能漂移。假设系统识别顺序变了,原来/dev/sdb1的设备变成了/dev/sdc1,fstab里还写着/dev/sdb1,开机时系统找不到设备,会进入emergency模式,搞不好连系统都起不来。而UUID是文件系统创建时生成的唯一标识,和设备名无关,能稳稳定位到正确的分区。
获取UUID的办法:
$ blkid或者在lsblk -f的输出里直接看。然后编辑/etc/fstab,比如把UUID为6c8f2c3a-xxxx-xxxx-xxxx的XFS分区挂载到/data:
UUID=6c8f2c3a-xxxx-xxxx-xxxx /data xfs defaults,noatime 0 2字段里defaults是最常见的挂载选项,默认包含rw、suid、dev、exec、auto等。生产环境我通常会在数据盘上加noatime和nodiratime。atime记录文件最后被访问的时间,每次读文件都要写一次这个时间戳,对读多写少的数据盘完全是浪费IO。加了noatime之后省略这个写操作,性能立竿见影。这算是我个人比较推荐的一个默认调优手段。
修改完fstab后,强烈建议先验证一下再重启,避免写错了直接开不了机。最简单的验证方式:
$ mount -a它会按fstab尝试挂载所有未挂载的条目,如果报错,立刻在开机能救命的系统盘适配之前解决掉。不过mount -a不会发现“挂载点目录不存在”这种错误水平的问题,所以更稳妥的是用findmnt --verify --verbose来验证fstab的完整性:
$ findmnt --verify --verbose这个命令会把fstab里的语法错误、目录不存在、文件系统类型不匹配等问题一次查出来,是上线前必跑的一道检查。
3. 弹性存储的关键:LVM逻辑卷管理实操
3.1 为什么生产环境离不开LVM
讲LVM之前,先说一个我亲历过的场景。早年在给一个业务系统加数据盘的时候,预估容量做了2TB,结果半年不到空间就吃紧了。物理分区遇上这种问题就非常尴尬:你要么把数据拷到新的大盘上重新挂载,要么在分区层面捣鼓缩容,风险极高。用了LVM之后,这个问题变成了一条命令的事。
LVM的核心理念,是把物理磁盘(PV,Physical Volume)聚合成一个资源池(VG,Volume Group),再从资源池里划分出逻辑卷(LV,Logical Volume),逻辑卷可以随时扩缩容。业务看到的是一个灵活的“虚拟磁盘”,不用关心底层物理盘怎么分布。数据库文件、日志目录、虚拟机镜像这种后期经常会膨胀的目录,特别适合放在LVM上做弹性管理。
另外LVM还支持快照。在做某些高风险操作之前,我可以对逻辑卷打一个快照,操作失败后可以秒级回滚。这个能力在升级数据库、批量修改文件时太重要了,靠备份重放或者rsync救援,恢复时间都是小时级别的,LVM快照只需要几分钟。
3.2 从零构建LVM:PV、VG、LV完整流程
把整块盘加入LVM是最常规的操作,完整流程我列出来,你跟着做就行。
第一步,创建物理卷。假设有一块已经分好区的盘/dev/sdb1:
$ pvcreate /dev/sdb1 $ pvspvs可以看到PV的基本信息,比如VG名称、PV大小、剩余空间。没有意外的话,PV Size就是你给分区的大小。
第二步,创建卷组。比如把这块PV归入名为vgdata的卷组:
$ vgcreate vgdata /dev/sdb1 $ vgsvgs输出里重点看Free列的剩余空间,这是你后续扩容逻辑卷的底气。
第三步,创建逻辑卷。比如在vgdata里创建一个名为lvdata、大小为1.5T的逻辑卷:
$ lvcreate -L 1.5T -n lvdata vgdata $ lvs第四步,在逻辑卷上创建文件系统并挂载。逻辑卷的设备路径是/dev/vgdata/lvdata,格式化之后挂载到/data:
$ mkfs.xfs /dev/vgdata/lvdata $ mount /dev/vgdata/lvdata /data注意,为了在重启后自动挂载,你还是要把这个逻辑卷的UUID写进/etc/fstab,方法同前。
3.3 在线扩容:容量不够时的一条龙操作
LVM最大的价值体现在“空间不够了”的时候。前提是VG里有剩余空间。如果vgs显示Free不够,你可能需要先添加一块新物理盘,然后pvcreate和vgextend把它并入现有VG,再用lvextend把空间分给逻辑卷。
假设VG里已经有剩余空间,现在把lvdata从1.5T扩到2T:
$ lvextend -L 2T /dev/vgdata/lvdata扩容逻辑卷之后,文件系统层面还得跟上。XFS和ext4的做法稍有不同:
# XFS $ xfs_growfs /data # ext4 $ resize2fs /dev/vgdata/lvdata这里有个非常容易踩的坑:只执行lvextend、不执行xfs_growfs或resize2fs,逻辑卷的大小变了,但文件系统不知道,df -h看到的还是旧容量,业务自然也不会感知任何变化。在我遇到过的“扩容没生效”工单里,有一半是这个问题。
缩容则要谨慎很多,尤其是XFS,它不支持在线缩容。如果确实需要缩容,传统做法是备份数据、删除逻辑卷、按新大小重建、再恢复数据。别在生产环境上硬来,这个钱省不得。
4. 容量与性能排查:数据都说没就没,都是哪里占的?
4.1 df和du的差异,以及藏在进程里的已删除文件
做运维的人都知道“磁盘满了”是高频事故。排查的第一步通常是:
$ df -h这个命令显示的是整个文件系统的使用情况,能快速定位哪个挂载点的空间快满了。但有时候你会遇到一个诡异现象:df -h显示挂载点使用率是99%,但进到对应目录里用du -sh *逐个统计,把所有可见文件加起来却只有一小半。这时候第一反应不要是系统坏了,而是“有已删除文件被进程占着”。
Linux的机制是这样的:进程打开一个文件后,删除了它,文件在目录里看不到了,但如果进程始终没关闭这个文件描述符,这个文件占用的空间就不会真正释放。常见于日志服务、数据库、消息队列这类长驻进程。排查方法:
$ lsof -nP | grep deleted找到PID和文件路径后,重启对应进程或者让应用重新打开日志文件,那些空间就会立刻释放。这个坑尤其在处理大日志的Java应用里很常见,直接删日志文件而不重启进程,磁盘永远“满着”。
另外一个容易忽略的是inode耗尽。df -i看的是inode使用率,inode是每个文件/目录在文件系统上的“档案卡”。如果小文件特别多(比如某个程序疯狂生成缓存文件),inode消耗完以后,即使磁盘还有大量剩余空间,系统同样报“No space left on device”。这时用df -i确认,再用find /data -type f | wc -l看看文件数量估算,该清理的清理、该调整inode数量的提前规划好。
4.2 IO瓶颈定位:iostat与iotop的使用思路
空间只是一方面,性能问题才是真的让人挠头。系统变慢、数据库超时、应用卡顿,很多时候是存储IO被拖垮了。我常用的排查组合是iostat和iotop。
$ iostat -x -m 5-x显示扩展信息,-m以MB为单位输出,5是每5秒刷新一次。重点看%util、await和svctm。%util接近100%说明设备基本被打满,await表示IO请求的平均等待时间,数值越高说明排队越严重。如果await高但svctm不高,通常意味着大量IO在排队而不是设备本身慢,这可能是队列深度配置不合理或请求太碎导致的。
确定磁盘有问题之后,用iotop定位到具体是哪个进程在生产IO:
$ iotop -o-o只显示正在进行IO的进程,不会刷出一屏无关内容。我发现过很多次性能事故的元凶就是某些后台进程在疯狂刷日志、或者备份脚本在对生产数据做全量扫描,这种平时根目录都没别的事的进程,用iotop一目了然抓到。
5. 实战排查心法:常见故障速查与事故复盘
5.1 一张表收好:存储故障速查思路
存储相关的故障,翻来覆去就那么几类,我把高发问题、现象和排查方向整理成一张表,你可以当速查手册。
| 故障现象 | 可能原因 | 快速排查命令 | 解决思路 |
|---|---|---|---|
| 磁盘剩余空间充足,但写文件报No space | inode耗尽 | df -i | 清理小文件、增大inode或迁移到新文件系统 |
| df显示99%,du却对不上 | 已删除文件被进程占用 | lsof -nP | grep deleted | 重启进程或让应用重新打开日志 |
| 挂载文件系统后目录是空的 | fstab挂载次序覆盖了目录 | findmnt /data | 调整挂载顺序,确保原数据所在文件系统最后挂载 |
| 系统启动进入emergency模式 | fstab设备名漂移或写错UUID | journalctl -xb | 修复fstab,改用UUID |
| 文件系统变成只读 | 内核IO错误,检测到文件系统异常 | dmesg | tail | 尝试remount ro-rw,不行就fsck |
| 扩容后df不涨 | 忘记扩展文件系统 | xfs_growfs /data或resize2fs | 执行对应文件系统的在线扩容命令 |
| 磁盘IO高但CPU低 | 存储子系统瓶颈 | iostat -x、iotop -o | 定位高IO进程,优化IO调度或升级存储 |
5.2 一次“把日志目录放进LVM”的生产事故复盘
讲一个印象很深的案例。某年给一套业务系统做存储扩容,新增应用模块需要一块1TB的数据盘,客户那边前期规划图把日志目录和数据目录放在同一个物理分区的习惯又延续下来了。我当时强烈建议拆开:日志目录放在LVM逻辑卷里,方便后续单独扩充;数据目录放在另一个逻辑卷,且文件系统用XFS以应对高并发写入。
后来某个晚上,日志量暴涨,整个文件系统直接被打满。因为两个目录在同一个逻辑卷上,日志把空间全占了,数据库开始报错写不进去。那一次事故,如果没有LVM的隔离,恢复时间会非常难看。当晚我先扩了日志卷的空间,再给数据卷加了容量,前后不到20分钟业务恢复。这个案例最值得记住的一点是:存储规划要按路径隔离,日志目录、数据目录、系统目录最好各放各的逻辑卷,不要让一个目录拖垮全局。
另一个细节是删日志文件不要用rm,让应用自己处理日志轮转(logrotate),否则就会出现我前面说的“空间不释放”问题。logrotate配合copytruncate选项,能让应用在不重启的情况下完成日志切换。
写在最后的一点实际经验
做了这么多年Linux的存储运维,我最深的体会是:存储管理最怕的不是技术上做不到,而是规划时图省事、出问题时靠猜。给目录做分区和LVM规划的时候,多花十分钟把设备清点清楚、fstab用UUID、能上LVM就上LVM、数据盘记得加noatime,这些都是成本极低但效果极高的习惯。等真正出了故障,你才知道当初这个十分钟有多值。
最后再分享一个小习惯:我每接管一台服务器,第一件事就是跑一遍lsblk -f输出设备清单,再核对一遍/etc/fstab里的每一项挂载是否合理,并把UUID对应关系记录到自己的运维笔记里。这个习惯帮我省下了很多“莫名其妙磁盘不见了”的半夜工单。存储这种事,讲究的就是一个“稳”,前期多梳理一次,后期就少折腾好几个晚上。