
把 Linux 存储这摊事儿捋明白是我近几年带运维团队时最常做的事儿。新人入职我大概率会先扔给他一块坏硬盘或者一个说大不大说小不小的分区扩容需求让他自己去折腾。为什么因为存储这块儿太容易出“看似没问题实际埋大雷”的状况了。今天不聊虚的就围绕 mdadm 做的软 RAID以及配合 LVM 的逻辑卷管理把从规划到落地、再到救急的完整链路掰开揉碎讲一遍。这篇文章适合刚接手服务器运维的人也适合那些用过 LVM 扩容但一直没搞懂 RAID 底层原理的开发者和运维工程师。你会搞清楚 RAID 和 LVM 到底各管哪一段为什么要叠加着用以及真正遇到磁盘故障时怎么不慌不忙地把数据捞回来。1. 方案选型为什么是 mdadm LVM而不是二选一很多人刚接触服务器存储时会陷入一个非此即彼的误区用了 RAID 就不需要 LVM或者为了图省事直接用 LVM 而不做冗余。这两个工具压根不在一个层面上解决的问题也不一样。RAID 解决的是“盘坏了怎么办”和“单盘性能不够怎么办”的问题。通过把多块物理盘组合成一个逻辑设备实现数据冗余或者并行读写。mdadm 是 Linux 下做软件 RAID 的标准工具它不依赖昂贵的 RAID 卡直接用 CPU 和内存来完成数据条带化和校验计算非常适合中小型服务器和成本敏感的项目。LVM 解决的是“分区不够用怎么办”和“分区大小不合理怎么办”的问题。传统分区方案里你给 /home 分了 200G给 /var 分了 50G结果 /home 用了一半不到/var 却爆了。这时候想调整传统分区方案下非常痛苦甚至要备份重来。LVM 把物理分区抽象成物理卷PV再汇总成卷组VG最后从卷组里切割出任意大小的逻辑卷LV这一切都可以在系统运行状态下动态调整。这两者组合起来才是一个完整的存储解决方案。底层用 RAID 把多块物理盘变成一块可靠的大盘上层用 LVM 把这块大盘变成可以灵活切割和伸缩的空间池。我在实际项目里见过不少只用 LVM 不用 RAID 的案例一旦某块物理盘挂了整个卷组连带所有逻辑卷全部遭殃那场面真的惨烈。反过来只用 RAID 不做 LVM 的话将来扩容和数据迁移的灵活性会大打折扣。所以我的建议一直很明确物理层做 RAID逻辑层做 LVM各司其职。2. mdadm 软 RAID 核心实操解析2.1 先想清楚要哪种 RAID 级别mdadm 支持的 RAID 级别不少但日常用得最多的就四种RAID 0、RAID 1、RAID 5、RAID 10。很多人背过它们的区别但真的上手选型时还是会纠结。我直接给结论。RAID 0 是把数据均匀条带化到所有盘上读写性能最好磁盘利用率 100%但没有任何冗余任何一块盘挂了整个阵列数据全没。它只适合存缓存、临时数据、可重建的内容比如视频渲染的中间帧、日志聚合的临时索引。RAID 1 是镜像数据同时写到两块盘上冗余度最高坏一块盘数据不丢读性能有提升但写性能等于单盘磁盘利用率只有 50%。它适合放系统盘、数据库日志、配置类数据。RAID 5 是条带加分布式校验至少需要三块盘。校验信息分散存储在每块盘上允许坏一块盘而不丢数据磁盘利用率是 (N-1)/N。它兼顾了性能、容量、冗余是很多通用业务服务器的首选。但要注意RAID 5 在坏盘后的重建过程中如果另一块盘也出问题数据就悬了。所以有条件的场景我更推荐 RAID 10。RAID 10 是先镜像后条带需要偶数块盘至少四块。它结合了 RAID 1 的冗余和 RAID 0 的性能允许每组镜像中坏一块盘整体利用率 50%。数据库服务器、高负载业务系统我基本无脑选 RAID 10。我的选型逻辑很简单系统盘用 RAID 1业务数据盘用 RAID 10如果预算实在紧张且数据可重建才考虑 RAID 5。RAID 0 除非明确知道自己在干什么否则别碰。2.2 从格式化到组阵列的完整步骤规划确定后假设我们有四块空闲硬盘/dev/sdb、/dev/sdc、/dev/sdd、/dev/sde要做 RAID 10。我会先把每块盘的分区表清干净然后创建 RAID 分区。# 用 wipefs 擦除旧的文件系统签名避免干扰 wipefs -a /dev/sdb /dev/sdc /dev/sdd /dev/sde # 也可以用 fdisk 交互式操作但脚本化用 parted 更高效 parted /dev/sdb --script -- mklabel gpt parted /dev/sdb --script -- mkpart primary 0% 100% parted /dev/sdb --script -- set 1 raid on这里解释一下为什么要设置分区类型为 raid而不是直接把整块裸盘丢给 mdadm。虽然 mdadm 可以基于整块盘工作但用分区有两个好处一是可以在分区级别预留一定的空间或者做对齐二是在某些主板上带分区表标识的盘更容易被管理工具识别。实测下来直接用整块盘出问题的概率不大但养成用分区的习惯后续如果要做替换盘或者排查心理踏实得多。四块盘都准备好后创建 RAID 10 阵列# 创建 /dev/md0级别 raid104 块盘 mdadm --create /dev/md0 --levelraid10 --raid-devices4 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 # 查看阵列创建进度 cat /proc/mdstat # 等待同步完成后检查阵列详情 mdadm --detail /dev/md0创建过程会触发后台同步对于大容量盘来说可能需要几十分钟到几个小时。这段时间系统可以正常用但阵列的读写性能会有所下降。我遇到过有人以为卡死了直接 reboot结果阵列处于不一致状态反而更麻烦。所以创建前一定要耐心创建后要盯着 /proc/mdstat 看进度。2.3 让阵列配置永久生效阵列创建完成后如果不做额外操作重启后内核虽然能识别但 /dev/md0 的编号和组装过程可能不稳定。正确做法是把配置写入 mdadm.conf。# 生成配置 mdadm --detail --scan /etc/mdadm.conf # 更新 initramfs确保引导阶段能识别阵列 update-initramfs -u # Debian/Ubuntu # 或 dracut --force # RHEL/CentOS这一步很多人会漏掉然后在第一次重启后发现系统起不来或者阵列变成了 inactive 状态才回头补。initramfs 一定要更新否则内核加载时没有 mdadm 的配置根本不知道去组装 /dev/md0数据都在盘上但系统认不出来。3. 基于 RAID 阵列搭建 LVM 逻辑卷3.1 为什么 LVM 要放在 RAID 之上阵列建好后/dev/md0 相当于一块大硬盘。传统做法是直接在这个设备上分区、格式化、挂载但这样做之后如果将来空间不够要么重新组阵列要么用非常繁琐的方式调整分区。把 LVM 加在这层之上就是为了给这个“大硬盘”增加动态伸缩的能力。我的习惯是在 /dev/md0 上创建物理卷加入卷组然后按照业务需求切割逻辑卷。这样我面对的就不再是一块固定的磁盘而是一个可以随时调整的空间池。举个实际场景。某业务系统有三块数据盘组成的 RAID 5总容量约 8T。传统分区方案里我把 6T 分给了文件存储2T 分给了数据库目录。结果半年后文件存储那边已经用了 5.8T数据库目录才用了 800G。这种时候如果当初用的是 LVM直接从数据库目录的卷里缩出来 1T 给文件存储几分钟搞定在线操作不用停服务。而如果没有 LVM光是倒数据、重新规划分区、迁移就够折腾一个通宵。3.2 PV、VG、LV 三层结构的落地命令还是接着上面的 RAID 10 阵列/dev/md0 建好后初始化 LVM# 创建物理卷 pvcreate /dev/md0 # 创建卷组名称用 vg_data vgcreate vg_data /dev/md0 # 查看卷组信息 vgdisplay vg_data # 创建逻辑卷先划分一个 2T 的数据卷 lvcreate -L 2T -n lv_apps vg_data # 也可以直接用全部剩余空间建一个卷 # lvcreate -l 100%FREE -n lv_apps vg_data格式化并挂载# 根据文件系统需求格式化。xfs 和 ext4 在扩容能力上有区别后面专门说 mkfs.xfs /dev/vg_data/lv_apps # 挂载 mkdir -p /data/apps mount /dev/vg_data/lv_apps /data/apps # 写入 fstab 实现开机自动挂载用 UUID 更稳妥 blkid /dev/vg_data/lv_apps这里有个细节我要强调逻辑卷的路径有两种写法/dev/vg_data/lv_apps 是软链接形式实际设备名可能是 /dev/dm-x。写 fstab 时用 UUID 或完整逻辑卷路径都可以但千万别直接用它自动生成的 /dev/dm-x因为系统启动时设备编号可能变化。3.3 xfs 和 ext4 在 LVM 上的扩容差异这是新手最容易踩坑的地方。ext4 文件系统既可以扩大也可以缩小而 xfs 只能扩大不能缩小。如果你提前知道将来可能要缩容某个文件系统一开始就选 ext4。扩容逻辑卷时xfs 和 ext4 的命令也不同。xfs 要先扩容 LV再执行 xfs_growfsext4 是先扩容 LV再执行 resize2fs。# 假设 lv_apps 要从 2T 扩到 3T lvextend -L 1T /dev/vg_data/lv_apps # xfs 在线扩容 xfs_growfs /data/apps # ext4 在线扩容 resize2fs /dev/vg_data/lv_appsxfs_growfs 后面跟的是挂载点resize2fs 后面跟的是设备路径这个区别我见过太多人搞混。还有一点如果你在 fstab 里用了 xfs 的 pquota 特性扩容时要确保挂载选项里带上对应的参数否则扩容后 quota 信息异常。4. 从零到一的完整实操流程4.1 环境规划与磁盘准备最近一次帮一个业务团队部署新的存储节点需求是跑容器化应用需要 12T 裸容量要求能容忍至少一块盘故障并且未来半年可能扩展到 20T。我选择了四块 4T 的 SAS 盘做 RAID 10总容量 8T上层 LVM 划分逻辑卷。为什么不直接上 RAID 6因为四块盘做 RAID 6 只剩一半容量而且 RAID 6 的重建性能开销更大对于容器化应用这种随机读写偏多的场景RAID 10 的延迟表现更稳定。磁盘规划如下设备用途分区类型/dev/sdbRAID 10 成员盘fd (Linux raid autodetect)/dev/sdcRAID 10 成员盘fd/dev/sddRAID 10 成员盘fd/dev/sdeRAID 10 成员盘fd/dev/md0RAID 10 阵列LVM PVvg_data卷组-lv_docker容器数据卷xfslv_backup备份数据卷ext44.2 完整命令序列实录清理磁盘并创建分区for disk in sdb sdc sdd sde; do wipefs -a /dev/$disk parted /dev/$disk --script -- mklabel gpt parted /dev/$disk --script -- mkpart primary 0% 100% parted /dev/$disk --script -- set 1 raid on done创建 RAID 10mdadm --create /dev/md0 --levelraid10 --raid-devices4 \ /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 # 查看同步进度等待完成 watch -n 5 cat /proc/mdstat同步完成后搭建 LVMpvcreate /dev/md0 vgcreate vg_data /dev/md0 # 划分逻辑卷lvm 元数据默认存放在卷组内lvcreate 时可以指定 --metadatasize但默认值足够用 lvcreate -L 6T -n lv_docker vg_data lvcreate -l 100%FREE -n lv_backup vg_data # 格式化 mkfs.xfs /dev/vg_data/lv_docker mkfs.ext4 /dev/vg_data/lv_backup # 挂载 mkdir -p /var/lib/docker /opt/backup mount /dev/vg_data/lv_docker /var/lib/docker mount /dev/vg_data/lv_backup /opt/backup # 写入 fstab用 UUID echo UUID$(blkid -s UUID -o value /dev/vg_data/lv_docker) /var/lib/docker xfs defaults 0 0 /etc/fstab echo UUID$(blkid -s UUID -o value /dev/vg_data/lv_backup) /opt/backup ext4 defaults 0 0 /etc/fstab整个过程下来系统层面完全在线操作没有停机窗口。容器服务重新配置数据根目录后正常启动读写延迟符合 RAID 10 的预期。4.3 性能验证与监控配置阵列建好后不要急着上业务先跑一轮性能基准测试。我习惯用 fio 做简单的读写压测# 顺序读 fio --nameseqread --rwread --bs1M --size4G --numjobs4 --runtime60 --group_reporting # 随机写 fio --namerandwrite --rwrandwrite --bs4K --size4G --numjobs4 --runtime60 --group_reportingRAID 10 的随机写性能应该远高于单盘如果测试结果和单盘差不多就要检查阵列是否还在同步、控制器是否有瓶颈、盘本身是否故障。监控方面我会在 crontab 里加一个定期检查 mdstat 和 LVM 状态的脚本一旦阵列降级或卷组空间不足立刻告警# 每天凌晨检查一次 RAID 状态 0 2 * * * /root/scripts/check_raid.sh脚本内容很简单就是抓取 /proc/mdstat 中是否有 [U_] 或 [_U] 这类降级标记以及 vgdisplay 里 Free PE 的数量是否低于阈值。5. 故障模拟、扩容实战与高频坑位记录5.1 磁盘故障模拟与阵列恢复全过程我每次帮团队搭完存储都会建议做一次故障演练。因为只有在真实模拟过坏盘的情况下你才知道自己的监控告警、备件流程、恢复步骤是否靠谱。这里给出完整的故障恢复流程。假设 /dev/sdc 出现故障可以用 mdadm 手动模拟# 模拟故障把 sdc1 标记为 failed mdadm /dev/md0 --fail /dev/sdc1 # 查看阵列状态此时应该能看到 [2/3] 或者类似降级标记 cat /proc/mdstat mdadm --detail /dev/md0此时系统还能正常工作读写走的是剩下三块盘RAID 10 的镜像组里坏了一块数据无损。接下来移除故障盘mdadm /dev/md0 --remove /dev/sdc1物理更换新盘后重新分区并加入阵列# 新盘可能是 /dev/sdc也可能因为槽位变化变成其他设备名 parted /dev/sdc --script -- mklabel gpt parted /dev/sdc --script -- mkpart primary 0% 100% parted /dev/sdc --script -- set 1 raid on # 加入阵列 mdadm /dev/md0 --add /dev/sdc1 # 此时阵列会自动开始重建观察进度 watch -n 5 cat /proc/mdstat重建过程中阵列性能会下降这是正常现象。如果是热插拔环境务必确认新盘在系统里已经被正确识别不要因为设备名错位把一块好盘给 format 了。5.2 容量不够时如何在线扩容LVM 最大的卖点就是在线扩容。当 vg_data 空间不足时有两条路。如果你有空的物理盘可以直接扩到卷组里# 新盘 /dev/sdf先做成 RAID 1 镜像或者直接作为 PV 加入 # 这里假设加了一块单盘做 PV注意单盘没有冗余只适合放可重建数据 pvcreate /dev/sdf1 vgextend vg_data /dev/sdf1 # 然后把空间分配给逻辑卷 lvextend -L 2T /dev/vg_data/lv_backup resize2fs /dev/vg_data/lv_backup如果卷组内有空闲未分配的 PE那就更简单了直接扩展逻辑卷即可。唯一要注意的是扩展 xfs 的逻辑卷后需要执行 xfs_growfs 更新文件系统否则 df 里看到的还是旧容量。我在生产环境里遇到过这样一个问题lvextend 成功了resize2fs 也成功但 df 显示容量没变。排查了很久发现是 fstab 挂载选项里指定了 usrquota 和 grpquota导致 resize2fs 在重算 quota 信息时没有更新到超块。解决办法是先卸载或用 remount 清掉 quota 选项再执行扩容。这个问题比较小众但值得记一笔。5.3 还原那些年踩过的坑mdadm.conf 与设备名变化做运维这些年存储相关的故障我处理过不少以下这些问题出现频率最高也是面试时我常拿来考人的。第一个坑重启后阵列变成 inactive。原因基本就是 /etc/mdadm.conf 没有正确配置或者 initramfs 没有更新。还有一个隐蔽的原因是多块盘同时掉线导致内核在引导时无法自动组装阵列。这时候不要慌手动使用 mdadm --assemble --scan 尝试恢复如果有完整元数据一般能找回来。第二个坑fstab 里用了 /dev/md0但重启后系统识别不到。因为 md0 的编号不是固定的如果有多套阵列系统可能按扫描顺序给 md1、md2。正确的做法是在 mdadm.conf 里用 UUID 或设备路径定义阵列同时在 fstab 里用 UUID 或 LVM 逻辑卷名而不是直接用 /dev/mdX。第三个坑LVM 卷组显示 incomplete。这通常发生在多块盘组成 VG、其中一块盘故障的时候。系统启动时发现缺少 PV卷组就无法激活。解决方法是先把故障盘的 PV 从 VG 中移除重新挂载其他正常的 PV然后激活卷组。# 查看 PV 状态 pvdisplay # 移除故障 PV注意这是破坏性操作仅在其他副本可用时执行 vgreduce vg_data /dev/sdc1 # 激活卷组 vgchange -ay vg_data第四个坑误删了 LVM 元数据。虽然可以用 vgcfgrestore 恢复但前提是你备份过 VG 元数据。养成习惯在每完成一次 LVM 配置变更后执行vgcfgbackup -f /backup/lvm/vg_data_backup_$(date %F).vg这个命令极其便宜但关键时刻能救命。我见过有同事折腾了三天最后靠这份备份恢复了整个卷组。5.4 深度问答看着像“面试题”但都是实战须知问RAID 5 坏一块盘后为什么建议尽快换盘重建而不是拖到业务低峰答RAID 5 只允许坏一块盘。在重建完成前整阵列处于脆弱状态。这期间如果再来一块盘故障数据几乎无法找回。而且重建期间所有盘都在高强度读写故障概率反而会上升。所以坏盘后要立刻处理别赌运气。问LVM 的 PE 大小影响什么答PE 是 LVM 分配空间的最小单位默认 4MB。PE 越小空间分配越灵活但元数据会膨胀PE 越大适合大容量卷后续缩容时的粒度更粗。如果你的场景需要频繁调整逻辑卷大小建议在 vgcreate 时指定较小的 PE比如 4MB 或 8MB否则最小分配单位太大空间会浪费。问RAID 阵列的分区对齐有什么用答现代硬盘和 SSD 的逻辑块大小是 4K分区起始扇区如果不是 4K 的整数倍IO 就会产生读改写放大性能会有明显下降。Linux 的 parted 分区工具默认对齐已经是 1MiB基本不用手动干预。但如果你用 fdisk 老式交互创建分区要注意扇区起始位置通常设置成 2048 是安全的。问如何判断一块盘是不是快坏了答SMART 信息是首要参考。重点关注 Reallocated_Sector_Ct重映射扇区数、Current_Pending_Sector待重映射扇区数、UDMA_CRC_Error_Count传输错误计数。这三个值只要持续增长哪怕阵列还没提示降级都要提前做替换规划。我一般会在巡检脚本里加入对这三个 SMART 属性的监控。问有没有必要在 LVM 之上再叠加一层文件系统加密或压缩答如果安全要求高可以在 LV 层用 dm-crypt 做加密但这会带来额外的 CPU 开销和复杂度。压缩文件系统比如 ZFS 或 btrfs 有自己的压缩特性但和 RAID LVM 的组合方案不太兼容。我的建议是如果没有明确的合规要求别引入不必要的复杂度存储方案越简单越可靠。6. 实测tips让这套组合在长期运行中更稳最后分享几个平时不会写在文档里、但实际运行中非常有用的经验。第一RAID 阵列的同步和重建速度可以通过调整 mdadm 的 sync speed 来限制。生产环境中重建会让业务 IO 变慢如果业务优先可以临时把速度限制调低echo 20000 /proc/sys/dev/raid/speed_limit_min echo 50000 /proc/sys/dev/raid/speed_limit_max等到夜深人静业务低峰再放开限制让它加速跑。第二LVM 的 snapshot 功能很多人只用来做备份前的快照其实也可以用来做临时环境。在快照上跑一些有风险的数据变更确认没问题后合并出问题就直接删除快照回滚。但要注意快照会占用卷组空间如果快照空间满了快照会自动失效做好监控别让它默默爆掉。第三如果你用的是 SSD 做 RAID要考虑耐磨和性能问题。mdadm 本身没有 SSD 感知能力所以尽量别把不同品牌、不同寿命状态的 SSD 混在一个阵列里。同时想追求单盘性能的话可以在 mdadm.conf 里为阵列设置 read-ahead 和 write-behind 参数比如 --write-behind256 配合 --bitmapinternal可以在断电后加快重建速度同时提升一定的写性能。不过这些参数需要你根据自己的硬件和业务特点做一次压测对比不能盲目抄。第四fstab 挂载时我强烈建议在挂载选项里加上 noatime。对存储服务器来说atime 更新产生的额外写入完全没意义关掉后能减少大量不必要的 IO。这套 mdadm LVM 的组合方案看起来每一步单独拎出来都不复杂但把它组合成一个能长期稳定运行的系统需要你对底层原理、常见故障、恢复路径有足够清晰的认识。我自己也是踩了无数次坑从数据丢失的边缘爬回来之后才真正把这些知识点串成了一条线。希望这篇文章能让你在规划自己的服务器存储时少走一些弯路至少不要在深夜两点对着一个 inactive 的阵列发呆。