1. 项目背景与核心需求拆解
1.1 为什么单路径存储会成为生产环境的隐患
很多运维同行第一次接触多路径存储,往往是在一次故障之后。服务器上挂载了一块来自磁盘阵列的LUN,系统识别为/dev/sdb,业务跑得好好的,某天机房网络抖动或者HBA卡固件升级,重启之后发现设备名变成了/dev/sdc,挂载脚本直接报错,数据库起不来。更麻烦的是,有些场景下同一条链路被操作系统识别成两个甚至四个块设备,应用层看到的是"多块盘",实际上指向的是同一份数据,一旦同时写入,文件系统直接损坏。
这个问题的根源在于:服务器到存储之间通常存在多条物理链路。以典型的双控存储为例,每台服务器配两块HBA卡,分别接到两台光纤交换机,存储的两个控制器各出一个端口,这样服务器到同一块LUN之间就有 2×2=4 条可达路径。操作系统默认的块设备层并不理解"这些路径其实通向同一个后端卷",它只会老老实实把每条路径都枚举成一个SCSI设备。于是就有了上面说的设备名漂移和重复识别问题。
Multipath(多路径)要解决的就是这件事:把这些物理上分离、逻辑上同源的路径聚合成一个虚拟块设备,向上层屏蔽底层链路的复杂性,同时提供故障切换和负载均衡能力。CentOS 7 自带的device-mapper-multipath就是干这个的,它工作在块设备层,位于 SCSI 层和文件系统层之间,对上层应用完全透明。
1.2 这套方案适合谁、解决什么问题
这篇文章面向的是手里有真实存储环境、需要在 CentOS 7 上把多路径跑起来并且跑好的运维工程师和系统管理员。如果你只是用虚拟机做实验,也能跟着走一遍流程,但很多参数调优的体感只有在真实负载下才明显。
具体来说,Multipath 在 CentOS 7 上要达成的目标有这么几个:
- 设备聚合:把多条路径合并成
/dev/mapper/mpathX这样的单一设备,业务只认这一个名字,重启、换卡、换交换机端口都不影响。 - 故障切换:某条链路断了,I/O 自动切到其他可用路径,业务基本无感知。
- 负载均衡:多条健康路径同时分担 I/O,提升吞吐。
- 路径管理:提供命令行工具查看每条路径的状态,方便排障。
需要提前说清楚的是,Multipath 本身不产生冗余,冗余是存储和网络层面已经做好的,Multipath 只是把这份冗余"用起来"。如果后端只有一条物理链路,配了 Multipath 也没有意义。
1.3 核心组件与工作模型
理解 Multipath 的工作模型,关键要分清三个层次:
| 层次 | 名称 | 作用 |
|---|---|---|
| 物理层 | 路径(path) | 每条 HBA 到存储端口的链路,对应一个/dev/sdX |
| 聚合层 | 路径组(path group) | 优先级相同的路径集合,同一时刻只有一个组处于 active |
| 设备层 | 多路径设备(mpath) | 对上层暴露的虚拟块设备,如/dev/mapper/mpatha |
内核里的dm-multipath模块负责实际的 I/O 转发,用户态的multipathd守护进程负责监控路径状态、执行切换策略、响应配置变更。两者通过 device-mapper 的 ioctl 接口通信。multipath命令是一次性的配置和查询工具,multipathd是常驻服务,实际生产里真正干活的是后者。
提示:很多人配完 Multipath 发现不生效,八成是
multipathd没起来,或者配置文件语法有问题导致守护进程加载了默认配置。排查时先看systemctl status multipathd。
2. 环境准备与安装配置全流程
2.1 安装前的环境确认清单
动手之前,先把下面这些信息确认清楚,能省掉后面大量的返工:
- 存储侧:确认 LUN 已经映射给这台服务器,并且存储管理员告诉你这块 LUN 的 WWID(World Wide Identifier)。这个 ID 是 Multipath 聚合路径的唯一依据,必须准确。
- 链路侧:
lspci | grep -i fibre确认 HBA 卡被识别;cat /sys/class/fc_host/host*/port_name查看每块 HBA 的 WWPN。 - 系统侧:
uname -r看内核版本,CentOS 7 建议 3.10.0-957 以上;rpm -qa | grep device-mapper确认基础包在。 - 网络侧:如果是 iSCSI,确认
iscsiadm能发现目标;如果是 FC,确认fdisk -l能看到多块同名容量的盘。
一个很实用的判断方法:如果lsblk或者fdisk -l里出现了多块容量完全一致、型号也一致的盘,而你又只申请了一块 LUN,那基本可以确定是多路径没配导致的重复识别。
2.2 安装 device-mapper-multipath
CentOS 7 的安装很直接,但有几个细节要注意:
# 确认仓库可用 yum repolist # 安装核心包和工具 yum install -y device-mapper-multipath device-mapper-multipath-libs # 确认版本 rpm -qa | grep multipath如果生产环境不能连外网,需要提前把 rpm 包和依赖下载好。依赖关系其实不复杂,主要是device-mapper、device-mapper-libs、kmod这几类。离线安装时用yum install --downloadonly --downloaddir=/tmp/mp在一台能上网的同版本机器上把包拉全,再拷到目标机yum localinstall *.rpm。
安装完成后不要急着启动,先把配置文件准备好。CentOS 7 默认会生成一个/etc/multipath.conf,但内容基本是注释,实际生效的是内置默认值。我习惯的做法是保留一份原始备份,然后自己写一份精简的配置。
2.3 生成并理解默认配置
# 备份原始配置 cp /etc/multipath.conf /etc/multipath.conf.bak # 生成一份带默认值的配置参考 mpathconf --enable --with_multipathd ympathconf这个工具会帮你把服务设成开机启动,并生成基础配置。执行完之后/etc/multipath.conf里会有defaults、blacklist、multipaths等段落。这里有个坑:mpathconf生成的配置里user_friendly_names默认是yes,意味着设备名会是mpatha、mpathb这种,而不是基于 WWID 的3600...长串。两种方式各有取舍,后面会专门讲。
2.4 启动服务并验证基础状态
# 启动并设置开机自启 systemctl enable multipathd systemctl start multipathd # 查看服务状态 systemctl status multipathd # 查看当前多路径设备 multipath -ll如果multipath -ll输出为空,说明还没有设备被聚合。这时候先别怀疑配置,先确认底层是不是真的有多条路径。用multipath -v3可以看到详细的探测过程,它会告诉你哪些设备被扫描到、哪些被黑名单过滤、哪些因为 WWID 不合法被跳过。这个-v3的输出信息量很大,是排障的第一手资料。
3. multipath.conf 核心参数深度解析
3.1 defaults 段:全局行为的基调
defaults段决定了所有多路径设备的默认行为,是配置里最需要花心思的部分。下面这份是我在多个生产环境里验证过的配置,逐项说明为什么这么设:
defaults { user_friendly_names no polling_interval 10 path_selector "service-time 0" path_grouping_policy multibus failback immediate no_path_retry 5 rr_min_io_rq 1 max_sectors_kb 1024 dev_loss_tmo 60 fast_io_fail_tmo 5 find_multipaths yes }user_friendly_names设成no是我强烈建议的。设成yes时,设备名mpatha是绑定到/etc/multipath/bindings文件的,这个文件一旦丢失或者换机器,名字就全乱了。而用 WWID 命名,设备名是3600508b400105e210000900000490000这种,虽然长,但它跟存储卷一一对应,任何机器上看到这个名字都知道是哪块盘。脚本里引用也更安全。
polling_interval是multipathd检查路径状态的间隔,单位秒。默认 5 秒,我一般设 10 秒。设太小会增加系统开销,设太大故障发现不及时。10 秒是个平衡点,对大多数业务够用。
path_selector决定多条健康路径之间怎么分发 I/O。service-time 0是基于每条路径的预估服务时间动态选择,比老式的round-robin 0更智能。round-robin是无脑轮询,不管路径快慢,在链路性能不一致时会导致慢路径拖后腿。service-time会把 I/O 优先发给当前响应最快的路径,实测在混合链路环境下吞吐提升明显。
path_grouping_policy设成multibus表示所有路径放在一个组里,全部参与负载均衡。另一种常见值是failover,同一时刻只有一条路径 active,其余待命。multibus适合存储两个控制器都能同时服务的场景(比如 ALUA 的 active/active 模式),failover适合 active/passive 存储。选错了不会报错,但性能会差一大截。
failback控制路径恢复后是否切回。immediate表示路径一恢复就立刻切回,适合主备链路性能差异大的场景。如果两条链路对等,设成manual或一个较大的延迟值(如failback 30)可以避免频繁切换带来的抖动。
no_path_retry是路径全断后 I/O 的行为。设成数字表示重试多少次后失败,设成queue表示一直排队等待路径恢复。生产环境里我倾向设一个具体数字,比如 5,避免 I/O 无限挂起导致应用假死。但如果是数据库这种对 I/O 中断极度敏感的场景,queue配合合理的dev_loss_tmo更稳妥。
max_sectors_kb限制单次 I/O 的最大扇区数。默认值在不同内核版本上不一致,显式设成 1024 可以保证行为一致。设太大在某些存储上会触发超时,设太小又增加 I/O 次数。1024 是个保守稳妥的值。
dev_loss_tmo和fast_io_fail_tmo是配合使用的。fast_io_fail_tmo设成 5 秒,意味着链路出问题后 5 秒内快速失败,把 I/O 切到其他路径;dev_loss_tmo设成 60 秒,是设备彻底从系统移除前的等待时间。这两个值要保证fast_io_fail_tmo < dev_loss_tmo,否则快速失败没意义。
3.2 blacklist 段:把不该管的设备排除掉
黑名单非常重要,配不好会把本地盘也卷进多路径,导致系统盘无法启动。CentOS 7 上至少要排除本地 SATA/SAS 盘和虚拟设备:
blacklist { devnode "^sd[a-z]$" devnode "^hd[a-z]" devnode "^vd[a-z]" devnode "^cciss!c[0-9]d[0-9]*" wwid "3600508b400105e210000900000490000" }devnode "^sd[a-z]$"这条会把所有单字母的 sd 设备排除,但多路径设备本身是/dev/dm-X,不受影响。注意这个正则只匹配sda到sdz,如果服务器本地盘超过 26 块,需要调整正则。更稳妥的做法是用wwid精确排除,但前提是你知道本地盘的 WWID。
blacklist_exceptions段可以给黑名单开例外,比如某些设备虽然匹配了 devnode 规则,但你确实想让它走多路径:
blacklist_exceptions { wwid "3600d0230000000000e0d0a0000d3a1b2" }3.3 multipaths 段:针对特定 LUN 的精细控制
全局配置管的是默认行为,但不同存储、不同业务对多路径的要求可能不一样。multipaths段允许你针对单个 WWID 做覆盖:
multipaths { multipath { wwid 3600d0230000000000e0d0a0000d3a1b2 alias data_lun01 path_grouping_policy multibus path_selector "service-time 0" failback immediate no_path_retry 10 } multipath { wwid 3600d0230000000000e0d0a0000d3a1b3 alias log_lun01 path_grouping_policy failover failback 30 } }alias是给设备起个人性化名字,比mpatha更可控,比 WWID 更好记。设了 alias 之后,设备会出现在/dev/mapper/data_lun01。注意 alias 不能和已有设备名冲突,也不能包含斜杠。
这里有个经验:数据库数据盘和日志盘对 I/O 的要求不同,数据盘随机读写多,适合service-time选择器;日志盘顺序写多,round-robin反而可能更稳定。分 LUN 配置能把这些差异照顾到。
3.4 devices 段:存储厂商特定的参数
不同存储对 SCSI 命令的响应行为不一样,devices段可以针对厂商和产品型号设置参数:
devices { device { vendor "NETAPP" product "LUN" path_grouping_policy group_by_prio prio "alua" path_checker tur failback immediate no_path_retry 5 } device { vendor "HITACHI" product "DF.*" path_grouping_policy multibus path_checker readsector0 prio "alua" } }path_checker决定用什么方式检测路径是否健康。tur(Test Unit Ready)是最通用的,发一个不产生实际 I/O 的命令探测;readsector0是读第一个扇区,对某些老存储更可靠但会产生实际读操作。选错了可能导致路径误判,比如某些存储对 TUR 响应慢,会被误认为路径故障。
prio是路径优先级算法。alua是 SCSI 标准里的非对称逻辑单元访问,存储会告诉主机哪些路径是优化的、哪些是非优化的。用alua配合group_by_prio策略,可以让 I/O 优先走优化路径,这是现代存储的最佳实践。
4. 实操验证与性能调优
4.1 配置生效与设备识别验证
改完配置后,标准操作流程是:
# 重新加载配置 systemctl reload multipathd # 或者完全重启 systemctl restart multipathd # 查看聚合结果 multipath -llmultipath -ll的输出需要仔细读。一个健康的多路径设备长这样:
data_lun01 (3600d0230000000000e0d0a0000d3a1b2) dm-3 NETAPP,LUN size=500G features='1 queue_if_no_path' hwhandler='0' wp=rw `-+- policy='service-time 0' prio=50 status=active |- 2:0:0:1 sdb 8:16 active ready running |- 3:0:0:1 sdc 8:32 active ready running |- 2:0:1:1 sdd 8:48 active ready running `- 3:0:1:1 sde 8:64 active ready running关键信息:dm-3是内核设备号,size=500G是容量,policy是选择器,prio=50是优先级,四条路径都是active ready running。如果某条路径显示failed faulty,说明那条链路有问题,需要查 HBA、光纤、交换机端口。
4.2 故障切换实测
配置完不测故障切换,等于没配。测试方法很简单,但要在业务低峰期做:
# 找到一条路径对应的 HBA 端口 ls -l /sys/block/sdb/device/scsi_device/ # 模拟链路故障(以 2:0:0:1 为例) echo 1 > /sys/block/sdb/device/delete执行后立刻multipath -ll观察,应该看到sdb消失,其余三条路径仍然active,设备dm-3依然可用。同时开一个dd或者fio在/dev/mapper/data_lun01上跑,I/O 不应该中断。
恢复测试用:
# 重新扫描 SCSI 总线 echo "- - -" > /sys/class/scsi_host/host2/scan路径回来后,根据failback设置决定是否自动切回。immediate会立刻切回,manual需要手动执行multipathd reconfigure或者multipath -r。
注意:
echo 1 > /sys/block/sdb/device/delete这个操作在有些内核版本上会直接移除设备,有些只是标记。测试前确认业务能承受,最好在测试环境先演练一遍。
4.3 性能调优的关键参数
多路径的性能调优,核心是让 I/O 尽可能均匀且高效地分布在健康路径上。除了前面说的path_selector,还有几个参数值得调:
rr_min_io_rq是轮询模式下每条路径连续处理的 I/O 数。设成 1 表示每个 I/O 都换路径,设成 1000 表示一条路径处理 1000 个 I/O 再换。设太小会导致路径切换开销大,设太大又失去负载均衡意义。用service-time选择器时这个参数基本不起作用,但用round-robin时很关键。一般设 1 到 100 之间,SSD 存储可以设小一点,机械盘设大一点。
queue_if_no_path特性配合no_path_retry使用。当所有路径都断时,I/O 是排队还是直接失败。可以在features里显式指定:
features "1 queue_if_no_path"数字 1 表示启用。这个特性在存储短暂抖动时能保住业务,但如果存储长时间不可用,I/O 会一直挂起,需要配合应用层的超时机制。
max_sectors_kb的调整需要结合存储的条带大小。如果存储条带是 256KB,把max_sectors_kb设成 256 的整数倍能让 I/O 对齐,减少读改写。这个值可以通过cat /sys/block/dm-3/queue/max_sectors_kb查看当前生效值。
4.4 用 fio 做基准测试
调优前后要有数据对比,fio是最常用的工具:
# 顺序读测试 fio --name=seqread --filename=/dev/mapper/data_lun01 \ --direct=1 --rw=read --bs=1M --size=10G \ --numjobs=4 --iodepth=32 --runtime=60 --group_reporting # 随机写测试 fio --name=randwrite --filename=/dev/mapper/data_lun01 \ --direct=1 --rw=randwrite --bs=4k --size=10G \ --numjobs=4 --iodepth=64 --runtime=60 --group_reporting对比单路径和多路径的 IOPS、带宽、延迟。正常情况下,四条路径的聚合带宽应该接近单路径的四倍(受限于存储控制器和交换机背板)。如果提升不明显,检查是不是path_grouping_policy设成了failover,或者存储侧只允许一条路径 active。
提示:
fio测试会破坏设备上的数据,务必在空 LUN 或者测试环境做。生产环境可以用--readonly参数只做读测试。
5. 常见问题排查与避坑经验
5.1 设备重复识别与黑名单失效
最常见的现象是multipath -ll里同一个 WWID 出现多次,或者本地盘被错误聚合。排查步骤:
multipath -v3 2>&1 | grep -i "blacklist"看黑名单规则有没有匹配上。- 检查
blacklist里的正则是不是写错了,比如^sd[a-z]$匹配不了sdaa。 - 确认
find_multipaths的值。设成yes时,只有满足特定条件的设备才会被聚合,能减少误判;设成no时所有非黑名单设备都会被尝试聚合。
如果本地盘已经被聚合了,千万不要直接删/dev/mapper下的设备,可能导致系统盘不可用。正确做法是先把该 WWID 加入黑名单,然后multipathd reconfigure,再multipath -f刷新。
5.2 路径状态异常排查表
| 现象 | 可能原因 | 排查命令 | 处理方式 |
|---|---|---|---|
| 路径显示 failed faulty | 光纤断、HBA 故障、交换机端口 down | cat /sys/class/fc_host/host*/port_state | 检查物理链路,换端口 |
| 路径显示 active ghost | 存储 ALUA 非优化路径 | multipathd show paths format "%w %i %p %t" | 正常现象,I/O 会优先走优化路径 |
| 路径频繁 up/down | 链路抖动、SFP 光模块老化 | `dmesg | grep -i "fc|scsi"` |
| 所有路径消失 | 存储控制器重启、交换机故障 | multipath -ll无输出 | 检查存储和交换机状态 |
| 设备容量不对 | LUN 映射错误、WWID 冲突 | multipath -ll对比存储侧 | 重新映射 LUN |
5.3 服务启动顺序与依赖问题
CentOS 7 用 systemd 管理服务,multipathd的启动时机很关键。如果它启动太晚,可能在根文件系统挂载之后才运行,导致根分区无法使用多路径。检查/usr/lib/systemd/system/multipathd.service里的Before和After设置,确保它在local-fs-pre.target之前启动。
另一个坑是multipathd和iscsid的依赖关系。iSCSI 场景下,iscsid必须先启动并发现目标,multipathd才能看到设备。如果顺序反了,multipathd启动时没有设备可聚合,后面也不会自动补上。解决办法是在multipathd.service里加After=iscsid.service,或者手动systemctl restart multipathd。
5.4 配置文件语法错误的隐蔽性
multipath.conf的语法检查不算严格,有些错误不会导致服务启动失败,但配置不生效。比如:
- 段落名拼错:
default写成defaults才对。 - 参数值没加引号:
path_selector service-time 0会被解析成两个 token,必须写成path_selector "service-time 0"。 - 大括号不匹配:少一个
}会导致后面所有配置被忽略。
改完配置后,用multipathd -k"show config"可以查看实际生效的配置,对比你写的文件,能发现哪些没被加载。这个命令比重启服务再猜要高效得多。
5.5 udev 规则与设备权限
多路径设备默认属主是root:disk,权限brw-rw----。如果业务以非 root 用户运行,需要调整 udev 规则:
# /etc/udev/rules.d/99-multipath-permissions.rules KERNEL=="dm-*", SUBSYSTEM=="block", PROGRAM="/sbin/multipath -c /dev/$name", RESULT=="*", OWNER="oracle", GROUP="oinstall", MODE="0660"改完udevadm control --reload-rules && udevadm trigger生效。注意规则里的PROGRAM调用multipath -c判断是不是多路径设备,避免影响其他 dm 设备。
6. 生产环境落地建议
6.1 配置版本化管理
multipath.conf是核心配置文件,必须纳入版本管理。我习惯在文件头部加注释记录变更:
# multipath.conf # 2024-01-15 初始配置,适配 NETAPP E系列 # 2024-03-02 调整 no_path_retry 从 queue 改为 5,解决存储维护时 I/O 挂起 # 2024-06-10 新增 log_lun01 的 failover 配置配合 Git 或者内部配置管理平台,每次变更都有记录,出问题能快速回滚。
6.2 监控与告警
multipathd本身不提供监控接口,但可以通过multipathd show paths的输出做监控。关键指标:
- 路径状态:任何一条路径非
active ready就告警。 - 路径数量:少于预期数量告警。
- 设备状态:
multipath -ll里设备不是rw状态告警。
可以用 Zabbix 或者 Prometheus 的自定义脚本采集,也可以写个简单的 shell 脚本定时检查:
#!/bin/bash # check_multipath.sh failed=$(multipathd show paths | grep -v "active ready" | grep -c "^") if [ "$failed" -gt 0 ]; then echo "WARNING: $failed paths not ready" multipathd show paths | grep -v "active ready" exit 1 fi exit 06.3 变更窗口与回滚预案
多路径配置变更涉及存储链路,必须在变更窗口做。回滚预案要提前准备好:
- 备份当前
multipath.conf。 - 记录当前
multipath -ll输出。 - 如果变更后业务异常,立即恢复配置文件并
systemctl restart multipathd。 - 如果设备名变了导致挂载失败,用 WWID 重新挂载。
最稳妥的做法是在测试环境先验证一遍完整流程,包括故障切换和恢复。
6.4 与集群软件的配合
如果服务器上跑的是 Oracle RAC、Pacemaker 这类集群软件,多路径设备的命名必须稳定。用 WWID 命名或者固定的 alias 是关键。集群软件通常有自己的存储检查机制,要确保它检查的是/dev/mapper/下的设备,而不是底层的/dev/sdX。
另外,集群软件可能会自己管理设备挂载和卸载,要确认它的操作不会和多路径的failback冲突。比如集群在路径切换时执行了multipath -f,可能导致设备被移除,影响其他节点。
6.5 内核参数配合调优
多路径的性能还受一些内核参数影响:
# /etc/sysctl.d/99-multipath.conf # SCSI 层命令超时,默认 30 秒,可适当调大 kernel.sysrq = 0 # 提高 SCSI 设备队列深度 # 通过 /sys/block/dm-X/queue/nr_requests 调整nr_requests是块设备层的请求队列深度,默认 128。高并发场景可以调到 256 或 512,但要注意内存占用。调整方法:
echo 256 > /sys/block/dm-3/queue/nr_requests这个值重启后会恢复,需要写进 udev 规则或者 systemd 服务里持久化。
6.6 存储侧配合的注意事项
多路径不是主机单方面的事,存储侧的配置同样关键:
- ALUA 配置:确认存储的 ALUA 状态是 active/active 还是 active/passive,主机侧的
path_grouping_policy要匹配。 - LUN masking:确认 LUN 只映射给需要的服务器,避免多台主机同时看到同一 LUN 导致数据损坏。
- 控制器固件:保持存储控制器固件和 HBA 固件版本兼容,厂商兼容性列表要查。
- 交换机 zoning:FC 交换机的 zoning 配置要确保每条路径独立,避免单点故障。
我在实际项目中遇到过存储侧 ALUA 配置错误,导致主机侧所有路径都被标记为优化路径,group_by_prio策略失效,I/O 全部涌向一个控制器。后来在存储侧修正 ALUA 状态,主机侧multipath -ll的prio值才正常区分。这个案例说明多路径调优必须主机和存储两边一起看,单看一边容易误判。
最后分享一个我常用的快速检查脚本,登录服务器后跑一遍,基本能判断多路径状态是否健康:
#!/bin/bash echo "=== multipathd 服务状态 ===" systemctl is-active multipathd echo "=== 多路径设备列表 ===" multipath -ll echo "=== 路径状态统计 ===" multipathd show paths | awk '{print $NF}' | sort | uniq -c echo "=== 黑名单检查 ===" multipathd show blacklist echo "=== 最近内核 SCSI 错误 ===" dmesg | grep -i "scsi\|fc" | tail -20这个脚本输出信息量足够,日常巡检和故障初判都能用。多路径这东西,配好之后平时不用管,但一旦出问题就是存储链路级别的事故,所以前期的配置严谨度和监控覆盖度,直接决定了后面是省心还是救火。