HOME

麒麟 V10 LVM 磁盘繁忙率异常复现:从 807% 到内核修复

文章目录21 节

一位同事在麒麟 V10 上遇到过一个很反直觉的现象:磁盘实际流量只有几十 KiB/s,sar%util 却会周期性跳到几千甚至几万;直接分区使用的盘看起来正常,做成 LVM 的盘更容易暴露异常。关闭应用和服务后,现象仍会复现。

我在 Termark 资产“临时测试麒麟”上搭建了 direct 分区盘与 LVM 盘两条路径,对齐同事的麒麟构建、内核和 sysstat 版本,复现出 sar 807.70%atop 895%,再用 /proc/diskstats 的最小 4 KiB 写入实验定位到 io_ticks 异常。最后只升级麒麟维护内核,在磁盘、LVM、XFS、sysstat、atop 和 fio 均不变的条件下完成了 A/B 验证。

这次实验的结论不是“LVM 无法被 atop 监控”,也不是“sar 算法有问题”。真正异常的是旧内核块层提供的设备繁忙时间计数;经本次实测有效的解决方案,是升级到包含相应修复的后续麒麟维护内核。

一、先纠正截图中的工具:这是 sar,不是 atop

同事提供的异常画面具有 DEVtpsrkB/swkB/saqu-szawait%util 等列,因此这是 sysstat 套件中的 sar -d 输出,不是 atop 界面。

sar 中单盘 %util 突然达到 29368.50% 的现场截图

截图里相邻样本的间隔约为 2 秒,sdb 只有约 0.75 KiB/s 读取、2 tps,但 %util 突然达到 29368.50%。如果按 sysstat 的换算关系反推:

29368.50% × 2000 ms ÷ 100 ≈ 587370 ms

也就是说,内核计数在一个 2 秒样本里一次性增加了约 587 秒,接近 10 分钟。这与同事描述的“每 10 分钟出现一次”高度吻合:磁盘并没有真的同时繁忙 293 倍,而是一次 I/O 或 flush 触发了旧时间差的集中补记。

需要保留一个证据边界:这张图来自同事现场,只能证明现场存在极端 %util 跳变;本文后续的 807.70%895%io_ticks +86999 ms 和内核升级结果,来自我在独立麒麟实验机上的复现。

二、先把版本对齐,否则复现结果没有可比性

同事补充的版本截图显示:

同事环境的麒麟 V10 SP3、52.46 内核和 sysstat 12.2.1 版本截图(主机名已遮盖)

项目同事环境我的复现阶段修复验证阶段
操作系统Kylin Linux Advanced Server V10 SP3 Build23相同相同
构建日期2023-03-24相同相同
内核4.19.90-52.46.v2207.ky10.x86_64完全相同4.19.90-52.65.v2207.ky10.x86_64
sysstat12.2.1-1.p00.ky10.x86_64完全相同保持不变
atop未提供2.7.1保持同一二进制
fio未提供3.7保持不变

下面这组命令只读取系统、内核和工具版本,适合在目标麒麟主机与复现机上分别执行。/etc/.kyinfo 可能包含构建信息,公开日志前应检查是否需要脱敏。

风险等级:INFO(只读)

作用: 对齐发行版构建、当前运行内核、sysstat、fio、iotop、LVM 和 atop 版本。 执行对象: 麒麟 Linux 主机的 shell;读取版本不要求停止业务。 完成验证: 复现阶段至少应确认发行版构建、内核和 sysstat 与问题主机一致;否则只能作为相似环境测试,不能称为同版本复现。

cat /etc/.kyinfo
uname -r
rpm -q sysstat fio iotop lvm2 device-mapper
/usr/local/sbin/atop -V 2>/dev/null || atop -V

我的复现环境最终得到:

Kylin Linux Advanced Server V10 SP3 Build23,2023-03-24
4.19.90-52.46.v2207.ky10.x86_64
sysstat-12.2.1-1.p00.ky10.x86_64
fio-3.7-6.p01.ky10.x86_64
iotop-0.6-20.p01.ky10.noarch
atop 2.7.1

这也回答了“版本不一样会不会影响实验”的问题:会。%util 的原始数据来自运行内核,内核版本是这次实验最关键的变量;sysstat 版本会影响展示和设备命名,但无法修复内核已经记错的 io_ticks

三、实验拓扑与安全边界

系统盘 /dev/sda 已经使用 LVM,实验没有修改它。我只操作两块新挂载的 10 GiB QEMU 测试盘:

/dev/sdb
└─ /dev/sdb1, XFS, label=ATOP_DIRECT
   └─ /mnt/atop-direct

/dev/sdc
└─ /dev/sdc1, GPT Linux LVM
   └─ PV /dev/sdc1
      └─ VG vg_atop_lab
         └─ LV lv_lvm_test, dm-2, XFS, label=ATOP_LVM
            └─ /mnt/atop-lvm

1. 操作前只读确认磁盘身份

下面的检查用于确认候选盘的容量、序列号、文件系统签名、挂载、LVM 归属、只读标志和 holders。命令本身不修改磁盘,但不能只按 /dev/sdX 名称或容量猜设备;生产环境还应核对 WWN、阵列 LUN ID 或虚拟化平台映射。

风险等级:INFO(只读)

作用: 在任何分区和格式化之前,证明 /dev/sdb/dev/sdc 是本次专用空盘,并排除系统盘、已挂载盘和现有 LVM 成员。 执行对象: 本次实验机;其他环境必须替换设备名,并使用持久化设备身份重新核对。 完成验证: 两块目标盘无挂载、无现有签名、不是 PV,holders 为空且只读标志为 0;任何一项不符合都应停止。

lsblk -o NAME,KNAME,MAJ:MIN,TYPE,SIZE,FSTYPE,LABEL,MOUNTPOINTS,RO,SERIAL,WWN
findmnt
wipefs -n /dev/sdb
wipefs -n /dev/sdc
pvs -o pv_name,pv_uuid,pv_size,pv_free,vg_name
udevadm info --query=property --name=/dev/sdb | grep -E '^(ID_SERIAL|ID_WWN)='
udevadm info --query=property --name=/dev/sdc | grep -E '^(ID_SERIAL|ID_WWN)='
ls -la /sys/class/block/sdb/holders /sys/class/block/sdc/holders
cat /sys/class/block/sdb/ro /sys/class/block/sdc/ro

我核对到的实验盘序列号分别是 QEMU_HARDDISK_QM00005QEMU_HARDDISK_QM00007。这两个值只用于记录本次虚拟机磁盘身份,不应作为其他环境的匹配条件。

2. 创建 direct 分区盘

下面是本次实验实际执行的 direct 路径创建命令。它会重写 /dev/sdb 的分区表并格式化 /dev/sdb1,对错误设备执行会造成数据丢失。

风险等级:DANGER(重分区并格式化整块目标盘)

作用: 将已确认无数据的专用测试盘 /dev/sdb 创建为 GPT 单分区 XFS,作为不经过 LVM 的对照组。 执行对象: 本次实验唯一确认过的 /dev/sdb,序列号为 QEMU_HARDDISK_QM00005;其他环境严禁照抄设备名。 执行前检查: 必须先完成上一节的 lsblkfindmntwipefs -n、序列号、PV 和 holders 核对,并有独立备份或确认该盘无数据。 不可恢复点: sfdisk 写入分区表和 mkfs.xfs -f 创建文件系统会覆盖原有磁盘结构;没有备份时不能通过简单撤销恢复。 恢复与验证: 若命令中途失败,先停止后续写入并从备份恢复;完成后用 lsblk -fblkidfindmnt 核对来源、标签与挂载点。

printf 'label: gpt\n,8G,L\n' | sfdisk /dev/sdb
partprobe /dev/sdb
udevadm settle
mkfs.xfs -f -L ATOP_DIRECT /dev/sdb1
mkdir -p /mnt/atop-direct
mount /dev/sdb1 /mnt/atop-direct

lsblk -f /dev/sdb
blkid /dev/sdb1
findmnt /mnt/atop-direct

3. 创建 LVM 测试盘

本机 util-linux 2.35.2 的 `sfdisk` 不接受 `8e00` 这种 GPT 类型缩写,因此我使用了 Linux LVM 的完整类型 GUID。下面命令会重写 `/dev/sdc`、创建 PV/VG/LV,并格式化逻辑卷。

风险等级:DANGER(重分区、创建 LVM 并格式化目标盘)

作用: 将已确认无数据的 /dev/sdc 建成 sdc1 → PV → vg_atop_lab → lv_lvm_test → XFS,作为 LVM 实验组。 执行对象: 本次实验唯一确认过的 /dev/sdc,序列号为 QEMU_HARDDISK_QM00007;VG/LV 名称也必须确认不会与现有环境冲突。 执行前检查: 除磁盘身份检查外,还要用 vgslvs 确认 vg_atop_lablv_lvm_test 尚不存在,并确保 /dev/sdc1 不属于任何现有卷组。 不可恢复点: sfdiskpvcreatemkfs.xfs -f 会覆盖目标上的原有分区、LVM 元数据和文件系统;没有备份时无法可靠撤销。 恢复与验证: 中途失败时不要对其他盘尝试同名命令;完成后用 pvsvgslvsdmsetuplsblkfindmnt 验证完整映射。

printf 'label: gpt\n,8G,E6D6D379-F507-44C2-A23C-238F2A3DF928\n' \
  | sfdisk /dev/sdc
partprobe /dev/sdc
udevadm settle

pvcreate -y /dev/sdc1
vgcreate vg_atop_lab /dev/sdc1
lvcreate -y -n lv_lvm_test -l 100%FREE vg_atop_lab
mkfs.xfs -f -L ATOP_LVM /dev/vg_atop_lab/lv_lvm_test

mkdir -p /mnt/atop-lvm
mount /dev/vg_atop_lab/lv_lvm_test /mnt/atop-lvm

创建完成后,我用下面的只读命令核对 device-mapper 映射。预期可以看到 dm-2 最终落到 /dev/sdc1

风险等级:INFO(只读)

作用: 证明 fio 写入的逻辑卷、device-mapper 设备、分区与底层整盘之间的对应关系。 执行对象: 已完成创建的测试机。 完成验证: dmsetup table 中应出现 linear 8:33 2048,即本次 dm-2 → sdc1 → sdc;实际主次设备号可能随环境变化。

lsblk -o NAME,KNAME,MAJ:MIN,TYPE,SIZE,FSTYPE,LABEL,MOUNTPOINTS
pvs -o pv_name,pv_uuid,pv_size,pv_free,vg_name
vgs -o vg_name,vg_uuid,vg_size,vg_free,pv_count,lv_count
lvs -o lv_name,vg_name,lv_uuid,lv_size,segtype,devices,kernel_major,kernel_minor
dmsetup ls --tree
dmsetup table vg_atop_lab-lv_lvm_test
findmnt /mnt/atop-direct
findmnt /mnt/atop-lvm

本次实测映射为:

vg_atop_lab-lv_lvm_test (253:2)
 └─ (8:33)

0 16769024 linear 8:33 2048

实验没有向 /etc/fstab 写入测试挂载,避免未确认的实验盘在下次启动时自动挂载。

四、安装工具,并正确理解 atop 的 DSK 与 LVM

麒麟仓库中没有提供本次需要的 atop 包,我安装了 fio、iotop 和编译依赖,再从 atop 官方站点下载 2.7.1 源码构建。安装软件和写入 /usr/local 会持久修改测试机,但不要求重启。

风险等级:CAUTION(安装软件并写入系统目录)

作用: 准备 fio、iotop 和 atop 2.7.1,使复现机能同时采集进程流量、设备繁忙率与压测数据。 执行对象: 隔离测试机;需要 root 权限和可用的软件源、HTTPS 访问。 需要核对: 上游版本和 SHA-256 可能随下载来源变化;本文记录的是本次实测文件,不应跳过来源与哈希验证。 影响与恢复: 会安装 RPM,并在 /usr/local/src/atop-lab/usr/local/sbin 写文件;不再需要时可删除自行构建文件,并通过包管理器评估卸载依赖。 完成验证: atop -V 应显示 2.7.1;源码包和二进制哈希应与本次记录一致,或与读者自行确认的可信来源一致。

dnf install -y fio iotop ncurses-devel

mkdir -p /usr/local/src/atop-lab
cd /usr/local/src/atop-lab
curl -fL -o atop-2.7.1.tar.gz \
  https://www.atoptool.nl/download/atop-2.7.1.tar.gz
sha256sum atop-2.7.1.tar.gz

tar -xzf atop-2.7.1.tar.gz
cd atop-2.7.1
make -j1
install -m 0755 atop /usr/local/sbin/atop-2.7.1
ln -sfn /usr/local/sbin/atop-2.7.1 /usr/local/sbin/atop

/usr/local/sbin/atop -V
sha256sum /usr/local/sbin/atop-2.7.1

本次哈希记录为:

atop-2.7.1.tar.gz
ca48d2f17e071deead5e6e9cc9e388bf6a3270d695e61976b3794d4d927b5c4e

/usr/local/sbin/atop-2.7.1
39e97987f2a9eb67bd23c35acd8bc832bba6a4a29d3d47038aae5666b887045d

atop 支持 LVM,但它把不同层级分成不同记录类型:

atop 类型本次对象含义
DSKsdbsdc物理整盘或底层块设备
LVMdm-2 / lv_lvm_testdevice-mapper 逻辑卷

下面的命令只采集设备统计,不修改磁盘。若只使用 -P DSK,逻辑卷不会出现;要同时观察底层整盘与 LVM,应显式选择 DSK,LVM

风险等级:INFO(只读监控)

作用: 每秒同时输出物理盘与 LVM 逻辑卷的繁忙率,避免因过滤参数造成“atop 不显示 LVM”的误判。 执行对象: 目标 Linux 主机;1 30 表示每 1 秒采样一次,共 30 次。 完成验证: 输出中应同时出现 DSK | sdcLVM | lv_lvm_test 一类记录。

atop -P DSK,LVM 1 30

因此,“LVM 盘在 atop 中没有繁忙率”可能包含两种完全不同的问题:一是只筛选了 DSK,导致 LVM 行没被输出;二是本文复现的旧内核 io_ticks 异常,导致底层盘 busy 出现离谱跳变。两者不能混为一谈。

五、使用相同 fio 参数做 direct 与 LVM 对照

我让两轮 fio 只改变目标文件路径,其他参数完全一致:4 KiB 随机写、libaio、队列深度 32、direct I/O、持续 20 秒、2 GiB 测试文件。

采集端的命令只读取系统统计。建议分别在多个终端或由同一采集脚本并行启动,确保 sar、iostat、atop、pidstat、iotop 与 /proc/diskstats 覆盖同一时间窗口。

风险等级:INFO(只读监控,但会产生一定采样开销)

作用: 从设备层、逻辑卷层和进程层同时观察同一次 fio 负载。 执行对象: 测试机;-p ALL 用于让 iostat 显示整盘、分区和 device-mapper,-P DSK,LVM 用于让 atop 同时显示底层盘与 LVM。 注意事项: 1 秒高频采样会增加少量 CPU 与日志开销;生产故障取证时应评估持续时长和输出目录空间。 完成验证: 各工具的采样时间应覆盖 fio 起止时间,且设备名能映射到同一测试路径。

atop -P DSK,LVM 1 30
sar -d -p 1 30
iostat -dxm -p ALL 1 30
pidstat -d 1 30
iotop -b -o -P -k -d 1 -n 30

fio 会在目标文件系统持续写入测试文件,不能在生产业务目录、容量不足的文件系统或未经确认的挂载点上执行。下面是本次 direct 对照组的实测命令。

风险等级:CAUTION(持续写入 2 GiB 测试文件并制造 I/O 压力)

作用: 在 direct 分区盘上生成可控的 4 KiB 随机写负载,作为 LVM 路径的基线。 执行对象: 已确认挂载到 /mnt/atop-direct 的专用测试文件系统;需要预留至少 2 GiB 空间。 影响与恢复: 测试会占用磁盘带宽并创建或覆盖 fio-test.bin;停止 fio 可终止压力,验证后可删除该测试文件回收空间。 完成验证: fio 正常运行约 20 秒并输出带宽、IOPS;同时确认监控工具记录的是 sdb/sdb1

fio \
  --name=direct_randwrite \
  --filename=/mnt/atop-direct/fio-test.bin \
  --size=2G \
  --rw=randwrite \
  --bs=4k \
  --ioengine=libaio \
  --iodepth=32 \
  --direct=1 \
  --runtime=20 \
  --time_based=1 \
  --group_reporting \
  --output-format=json

第二轮保持所有参数不变,只把目标切换到 LVM 文件系统。

风险等级:CAUTION(持续写入 2 GiB 测试文件并制造 I/O 压力)

作用: 在 LVM 逻辑卷上重复同一负载,与 direct 组比较底层盘、分区和逻辑卷的统计差异。 执行对象: 已确认挂载到 /mnt/atop-lvm 的专用测试文件系统。 影响与恢复: 与 direct 轮相同;测试会创建或覆盖 LVM 挂载点内的 fio-test.bin,不应指向真实业务文件。 完成验证: fio 正常结束,监控中同时出现底层 sdclv_lvm_test/dm-2,并保留原始输出用于交叉核对。

fio \
  --name=lvm_randwrite \
  --filename=/mnt/atop-lvm/fio-test.bin \
  --size=2G \
  --rw=randwrite \
  --bs=4k \
  --ioengine=libaio \
  --iodepth=32 \
  --direct=1 \
  --runtime=20 \
  --time_based=1 \
  --group_reporting \
  --output-format=json

在旧内核 52.46 上,我得到的完整负载对照是:

测试fio 带宽fio IOPSsar 整盘峰值sar 整盘平均atop 整盘峰值atop LVM 峰值
direct /mnt/atop-direct24.43 MiB/s6253.64sdb 78.60%45.42%sdb 85%不活动
LVM /mnt/atop-lvm24.23 MiB/s6203.29sdc 807.70%100.27%sdc 895%98%

LVM 轮第一个活动样本的实测输出如下:

sar:
17:20:28 sdc                       ... %util 807.70
17:20:28 vg_atop_lab-lv_lvm_test   ... %util  20.80

atop:
LVM | -lv_lvm_test | busy  24% | write 5655 | avio 38.6 us
DSK |          sdc | busy 895% | write 2826 | avio 2.86 ms

两轮 fio 吞吐量和 IOPS 几乎相同,LVM 逻辑卷自身也只有约 20% 到 24%,但底层 sdc 突然报告 8 到 9 倍繁忙。仅靠真实业务流量无法解释这个差异。

六、最小触发:只写 4 KiB,直接观察 io_ticks 跳变

完整 fio 复现证明了现象,但还不能排除工具计算、应用行为或采样边界。为了缩小变量,我让 LVM 路径空闲 35 秒,然后只向现有测试文件写入 4 KiB,并在写入前后记录 /proc/diskstats

下面的辅助函数只读取指定设备的原始计数。$13 是设备执行 I/O 的累计毫秒数,即本文关注的 io_ticks$14 是加权 I/O 毫秒数。不同内核导出的字段数可能不同,解析前应先核对当前内核文档和原始行。

风险等级:INFO(只读)

作用: 以原始内核计数为准,绕开 sar、iostat 和 atop 的格式化与换算层。 执行对象: 本次测试机;设备列表需按实际拓扑修改。 完成验证: 能稳定输出 sdbsdb1sdcsdc1dm-2 的请求数、inflight、io_ticks 和 weighted 值。

awk '
  $3=="sdb" || $3=="sdb1" || $3=="sdc" || $3=="sdc1" || $3=="dm-2" {
    printf "%-5s reads=%s writes=%s inflight=%s io_ticks=%s weighted=%s\n", \
      $3, $4, $8, $12, $13, $14
  }
' /proc/diskstats

下面是最小触发序列。它会在 /var/tmp 保存三次快照,并覆盖 LVM 测试文件偏移 4096 字节处的 4 KiB 数据;oflag=direct,dsync 用于绕过页缓存并要求同步写入。该命令只能针对可丢弃的实验文件。

风险等级:CAUTION(覆盖测试文件中的 4 KiB 数据)

作用: 在长时间空闲后用一次极小的同步 direct 写触发块层完成路径,观察旧内核是否把异常时间差补入 io_ticks执行对象: 已确认可覆盖的 /mnt/atop-lvm/fio-test.bin;不能替换为数据库文件、虚拟磁盘或其他业务数据。 执行前检查:findmnt /mnt/atop-lvm 确认挂载来源,用 ls -lh 确认测试文件存在且可丢弃。 影响与恢复: 只覆盖测试文件中的 4 KiB;删除并重新创建 fio 测试文件即可恢复实验状态。 完成验证: 比较 after-idleafter-write,请求数只应增加少量;若 io_ticks 增加数万或数十万毫秒,而 weighted 只增加几毫秒,即复现计数异常。

findmnt /mnt/atop-lvm
ls -lh /mnt/atop-lvm/fio-test.bin

awk '$3=="sdb" || $3=="sdb1" || $3=="sdc" || $3=="sdc1" || $3=="dm-2" {print}' \
  /proc/diskstats > /var/tmp/diskstats-before-idle.txt

sleep 35

awk '$3=="sdb" || $3=="sdb1" || $3=="sdc" || $3=="sdc1" || $3=="dm-2" {print}' \
  /proc/diskstats > /var/tmp/diskstats-after-idle.txt

dd if=/dev/zero \
  of=/mnt/atop-lvm/fio-test.bin \
  bs=4096 count=1 seek=1 conv=notrunc \
  oflag=direct,dsync status=none

awk '$3=="sdb" || $3=="sdb1" || $3=="sdc" || $3=="sdc1" || $3=="dm-2" {print}' \
  /proc/diskstats > /var/tmp/diskstats-after-write.txt

旧内核 52.46 的差分结果是:

设备空闲 35 秒 Δio_ticks触发写请求增量触发 Δweighted触发 Δio_ticks
sdc0 ms21 ms86999 ms
sdc10 ms11 ms86998 ms
dm-20 ms43 ms3 ms
direct sdb0 ms21 ms2 ms
direct sdb10 ms11 ms328147 ms

这组数字是整篇实验最关键的证据:LVM 轮只增加了 2 个底层写请求,weighted I/O time 只增加 1 ms,但 sdc io_ticks 一次增加了 86999 ms。因此异常在内核导出的原始设备统计中已经存在,sar、iostat、atop 只是读取并换算,不是它们凭空制造了 807% 或 895%。

它还推翻了“直接分区的盘完全正常”这一假设。direct 的整盘 sdb 只增加 2 ms,但分区 sdb1 曾跳增 328147 ms。atop 默认的 DSK 行和本机 sar -d -p 通常不会把普通分区作为独立可见对象,而 LVM 路径的底层整盘 sdc 恰好发生跳变,所以界面上才形成“direct 正常、只有 LVM 异常”的错觉。

七、为什么 iotop 流量很小,%util 却可以达到几千

这些工具观察的是不同指标:

工具或数据源主要观察对象
iotop、pidstat进程提交或完成的 I/O 字节、任务 I/O 行为
fio测试任务自身的 IOPS、带宽、时延
/proc/diskstats内核按块设备累计的请求数与时间计数
sar、iostat、atop读取设备计数并按采样间隔换算 %util 或 busy

设备繁忙率可以简化理解为:

%util = Δio_ticks(ms) ÷ 采样间隔(ms) × 100

如果旧内核在一次 flush 或 passthrough 请求完成时,把长时间空闲形成的时间戳差一次性加入 io_ticks,应用实际只写了几 KiB,进程工具看到的流量仍然很小;但设备级 %util 会按错误的数万或数十万毫秒计算,因此轻易超过 100%。

Linux 上游块层历史中有多项与该机制相关的修复:

这些提交用于解释上游机制,不代表我已经逐项证明麒麟 52.65 精确回移了哪一个提交。本文的最终结论来自麒麟内核 A/B 实测:52.46 会出现原始 io_ticks 大幅跳增,52.65 在同条件下不再跳增。

八、解决方案:升级维护内核,并保持其他变量不变复测

本次验证通过的目标内核是:

4.19.90-52.65.v2207.ky10.x86_64

这不是要求所有麒麟 V10 主机都无条件安装同一版本。生产实施前必须确认发行版分支、仓库来源、HBA、多路径、备份软件、杀毒、内核模块和第三方驱动兼容性,并保留当前可启动内核和带外控制台。

1. 升级前只读检查

下面命令检查当前内核、可用版本、/boot 空间、默认启动项和存储拓扑。dnf --showduplicates 只查询仓库元数据,不安装软件。

风险等级:INFO(只读)

作用: 在安装新内核之前,确认主机分支、目标版本可用性、启动空间、当前默认内核和 LVM 根文件系统关系。 执行对象: 待升级的麒麟 V10 SP3 主机。 完成验证: 仓库能提供目标维护内核,/boot 空间足够,旧内核仍可识别,存储拓扑和第三方模块清单已留档。

cat /etc/.kyinfo
uname -r
rpm -q sysstat
rpm -qa 'kernel*' | sort
dnf --showduplicates list kernel
df -h /boot /
grubby --default-kernel
grubby --info=ALL
lsblk -o NAME,KNAME,MAJ:MIN,TYPE,SIZE,FSTYPE,MOUNTPOINTS
pvs
vgs
lvs -a -o lv_name,vg_name,segtype,devices

2. 安装新内核,但先不删除旧内核

安装新内核会写入 /boot 和 RPM 数据库,但当前正在运行的内核不会立即切换。target 必须替换为本机仓库中已确认兼容的完整版本。

风险等级:CAUTION(安装新的系统内核包)

作用: 把目标维护内核安装到系统,同时保留当前旧内核作为回退入口。 执行对象: 本次实验的目标为 4.19.90-52.65.v2207.ky10.x86_64;其他麒麟分支不能直接套用。 影响与恢复: 会占用 /boot 和根文件系统空间;安装失败时不要删除旧内核,可根据包管理器日志修复仓库或依赖后重试。 完成验证: rpm -q 能查到对应的 kernel、core、modules 和 modules-extra,且内核与 initramfs 文件存在。

target=4.19.90-52.65.v2207.ky10.x86_64

dnf install -y "kernel-$target"

rpm -q \
  "kernel-$target" \
  "kernel-core-$target" \
  "kernel-modules-$target" \
  "kernel-modules-extra-$target"

test -s "/boot/vmlinuz-$target"
test -s "/boot/initramfs-$target.img"

下面的检查只读取新 initramfs 中的模块列表,用来确认 LVM 根文件系统所需的 device-mapper 等基础模块没有明显缺失。

风险等级:INFO(只读)

作用: 在改变默认启动项前,验证目标内核文件与 initramfs 可读,并检查常见 LVM/块设备模块。 执行对象: 已安装目标内核的主机。 完成验证: vmlinuzinitramfs 非空,lsinitrd 能正常读取;实际所需模块应结合根盘、HBA、多路径和虚拟化驱动确认。

target=4.19.90-52.65.v2207.ky10.x86_64

ls -lh "/boot/vmlinuz-$target" "/boot/initramfs-$target.img"
lsinitrd -m "/boot/initramfs-$target.img" \
  | grep -E '^(dm|lvm|qemu|rootfs-block)$'

3. 设置默认内核并重启

改变默认启动内核并重启会中断主机服务;若目标内核或第三方驱动不兼容,还可能导致系统无法正常启动。生产环境必须有维护窗口、最新备份、业务停机确认和可操作的带外/虚拟控制台。

风险等级:DANGER(改变启动内核并造成整机中断)

作用: 把已验证文件完整的 52.65 设置为默认启动项,并通过重启切换运行内核。 执行对象: 本次实验机;执行前必须确认旧内核仍保留在 GRUB 菜单中。 执行前检查: 核对 grubby --info=ALL、备份、控制台、第三方模块兼容性和维护窗口;未完成这些条件时不要重启生产主机。 不可恢复点: systemctl reboot 会立即中断当前业务与 SSH 会话;错误内核可能无法启动。 回滚路径: 通过 GRUB/虚拟控制台选择原内核启动,再用 grubby --set-default /boot/vmlinuz-<OLD_KERNEL> 恢复默认项;新内核稳定前不要删除旧内核。 完成验证: 重启后 uname -r 必须显示目标版本,文件系统、LVM、网络、业务和内核日志均通过验收。

target=4.19.90-52.65.v2207.ky10.x86_64

grubby --set-default "/boot/vmlinuz-$target"
grubby --default-kernel
grubby --info=ALL

systemctl reboot

4. 重启后的系统与存储验收

下面的命令在系统重新上线后执行,主要检查运行内核、失败服务、块设备、LVM、挂载和新的 I/O/XFS/device-mapper 错误。读取 dmesg 通常需要 root 权限。

风险等级:INFO(只读验收)

作用: 证明主机确实运行在目标内核上,并确认升级没有破坏存储映射或产生新的块层错误。 执行对象: 重启后的麒麟主机。 完成验证: uname -r 为目标版本,测试文件系统与业务文件系统均正常挂载,LVM 结构完整,内核日志没有新 I/O、XFS 或 device-mapper 错误。

uname -r
systemctl --failed
lsblk
pvs
vgs
lvs -a -o lv_name,vg_name,segtype,devices
findmnt
dmesg -T | grep -Ei \
  'I/O error|blk_update_request|Buffer I/O|XFS.*(error|corrupt)|device-mapper.*error' \
  || true

本次实验机的 systemctl is-system-running 仍为 degraded,原因是实验前已经存在的 lm_sensors.service 失败;内核升级前后都存在,因此没有把它归因于本次存储实验。

九、52.65 内核的同条件 A/B 结果

升级后,我没有重建磁盘、LVM、XFS 或测试文件,也没有升级 sysstat、atop、fio 和 iotop。唯一核心变量是运行内核从 52.46 变为 52.65

先重复同样的“空闲 35 秒后写 4 KiB”测试:

设备空闲 35 秒 Δio_ticks写请求增量flush 增量ΔweightedΔio_ticks
sdc0 ms322 ms4 ms
sdc10 ms202 ms4 ms
dm-20 ms304 ms4 ms

旧内核的 +86999 ms 已经消失,所有层级都回到毫秒级。

再重复完整 LVM fio:

fio: 31.27 MiB/s, 8004.60 IOPS
sar sdc %util peak: 94.30%
sar sdc %util average: 56.86%
sar LVM %util average: 65.96%
sar sdc/LVM samples over 110%: 0

atop 的 LVM 行偶尔出现 100%105% 的 1 秒近似值,这与不同设备层级、采样边界和取整有关;关键是底层盘不再出现 895% 或几千百分比的跳变。实际性能判断仍应结合 IOPS、吞吐量、awaitaqu-sz、业务延迟和底层硬件指标,而不是只看单一 %util

十、生产解决方案与维护窗口前的临时口径

已验证的解决方案

对与本实验同分支、同类问题的麒麟 V10 SP3 主机,优先升级到厂商支持的后续维护内核。本次验证通过的是 4.19.90-52.65.v2207.ky10.x86_64。是否使用这一具体版本,应以目标主机的软件仓库、麒麟支持策略和第三方驱动兼容性为准。

下面这些做法不能修复根因:

  • 关闭应用或服务:异常在空闲后的单次 4 KiB 写入中仍能触发。
  • 只看 iotop:它能说明进程流量很小,但不能修正设备时间计数。
  • 更换 sar、iostat 或 atop:三者最终都依赖内核设备统计。
  • 单独升级 sysstat:可能改善展示和设备匹配,但不会修复 /proc/diskstats 原始 io_ticks
  • %util > 100% 截断成 100%:这会掩盖内核记账异常,而不是解决问题。

维护窗口前的临时监控口径

在尚未升级内核时,我建议把极端 %util 样本标记为“待核对的设备计数异常”,同时保存这些证据:

  1. iostat -dxm -p ALL,同时覆盖整盘、分区与 dm-*
  2. atop -P DSK,LVM,同时覆盖物理盘与逻辑卷。
  3. 异常样本前后的 /proc/diskstats 原始值。
  4. 同期 IOPS、字节数、weighted I/O time、awaitaqu-sz、应用延迟和底层存储指标。
  5. 发生时间与上一次 I/O 的间隔,判断是否存在周期性空闲后 flush 触发。

判断依据不是“只要超过 100% 就忽略”,而是检查极高 %util 是否同时具备:I/O 字节很低、请求数只增加少量、weighted time 很低、io_ticks 却突然增加数万毫秒。满足这一组合时,才有充分理由把它与真实磁盘过载区分开。

十一、实验后的回滚与保留状态

本次实验结束时:

当前及默认内核:4.19.90-52.65.v2207.ky10.x86_64
保留回退内核:4.19.90-52.46.v2207.ky10.x86_64
保留原始内核:4.19.90-52.22.v2207.ky10.x86_64
sysstat:12.2.1-1.p00.ky10.x86_64
测试挂载:/mnt/atop-direct、/mnt/atop-lvm

我没有清理测试数据,也没有写 /etc/fstab,以便继续复查原始证据。下面只给出未来清理的边界,本文实验中没有执行这些删除命令。

如果确认不再需要复现证据,先做只读核对。设备名、序列号、挂载来源和 VG/LV 必须全部与本次实验一致。

风险等级:INFO(只读清理前检查)

作用: 在删除实验 LVM 与卸载文件系统之前,再次确认所有目标只属于本次实验。 执行对象: 实验机;任何输出不符合预期都应停止。 完成验证: 两个挂载来源、两块磁盘序列号、VG/LV 名称和 device-mapper 映射均与本文实验拓扑一致。

findmnt /mnt/atop-direct
findmnt /mnt/atop-lvm
udevadm info --query=property --name=/dev/sdb | grep '^ID_SERIAL='
udevadm info --query=property --name=/dev/sdc | grep '^ID_SERIAL='
pvs
vgs
lvs -a -o lv_name,vg_name,devices
dmsetup ls --tree

下面的清理模板会卸载 LVM 测试文件系统并删除 LV、VG 和 PV 元数据,也会卸载 direct 测试盘。它不会自动清除 /dev/sdb/dev/sdc 的分区表,避免把更大范围的擦除隐藏在一段命令里。

风险等级:DANGER(删除实验 LVM 元数据并使测试挂载离线)

作用: 在证据已备份且明确不再需要复现环境后,删除 lv_lvm_testvg_atop_lab/dev/sdc1 上的 PV,并卸载 direct 测试盘。 执行对象: 仅限本文实验创建的 /mnt/atop-lvmvg_atop_lab/lv_lvm_test/dev/sdc1/mnt/atop-direct执行前检查: 完成上面的挂载、序列号和 LVM 核对,确认没有进程使用挂载点,并已备份所有需要保留的原始证据。 不可恢复点: lvremovevgremovepvremove 会删除 LVM 元数据;没有备份时测试卷数据无法通过简单撤销恢复。 恢复与验证: 需要保留数据时不要执行;误删后的恢复依赖独立备份或专业数据恢复。完成后用 findmntpvsvgslvs 确认对象已消失。

umount /mnt/atop-lvm
lvremove -y /dev/vg_atop_lab/lv_lvm_test
vgremove -y vg_atop_lab
pvremove -y /dev/sdc1

umount /mnt/atop-direct

findmnt /mnt/atop-lvm || true
findmnt /mnt/atop-direct || true
pvs
vgs
lvs

十二、最终结论

这次复现把几个容易混淆的问题拆开了:

  1. 同事截图是 sar -d,不是 atop;现场的 29368.50% 与约 10 分钟周期可以由一次性补入约 587 秒 io_ticks 解释。
  2. atop 支持 LVM。DSKLVM 是不同记录类型,只使用 atop -P DSK 看不到逻辑卷,应使用 atop -P DSK,LVM
  3. “direct 正常、LVM 异常”部分来自默认展示层级造成的错觉;旧内核中 direct 分区 sdb1 也出现过异常,只是默认界面通常不显示这一层。
  4. 旧内核 52.46 上,一次 4 KiB 写入让 sdc io_ticks 增加 86999 ms,而 weighted time 只有 1 ms,证明异常来自内核块设备时间记账。
  5. 保持 sysstat、atop、fio、磁盘、LVM 和 XFS 不变,只升级到 52.65 后,同一最小写入的 sdc io_ticks 只增加 4 ms,完整负载也不再出现数百或数千百分比。

所以,生产解决方向应是评估并升级麒麟维护内核,同时保留旧内核、控制台和完整回滚路径。关闭应用、更换监控工具或截断 %util 都只能改变观察方式,不能修复错误的内核原始计数。

殷工每次都提供非常有趣的实验课题,亦师亦友跟着学习受益匪浅。

麒麟 Linux Linux LVM 性能监控 atop sysstat 故障排查