Linux iowait过高的常见根因与排查路径

描述

问题背景

线上服务器监控报警,CPU us 不高,但 iowait 高达 40%、50%,磁盘 util 100%。这时候工程师的第一反应往往是"磁盘坏了",但实际情况远比这复杂。iowait 高只是现象,背后可能藏着 MySQL 慢查询、Docker 日志风暴、Nginx 写 access log、文件系统碎片、Swap 滥用、甚至是内核调度问题。

这篇文章从 Linux I/O 栈的全貌出发,讲清楚 iowait 到底是什么、不是什么、怎么一步步定位根因,最后给出常见的故障复盘案例。

Linux I/O 栈全貌:从应用到磁盘

Linux 的 I/O 路径是一层一层串起来的,每一层都有自己的队列、缓冲和调度逻辑。理解这个栈,是排查 I/O 问题的前置条件。

第 1 层:应用层

应用通过系统调用发起 I/O 请求,最常见的是 read() 和 write()。应用程序本身不直接跟磁盘打交道,它只管写文件描述符。具体怎么写、写到哪块磁盘、由内核决定。

常见的 I/O 发起方:

MySQL:InnoDB 的脏页刷新、binlog 写入、redo log 刷盘

Nginx/Apache:access log、error log 写入

Docker:容器日志、存储层写

Python/Java 进程:业务日志、文件缓存

系统守护进程:rsyslog、auditd

第 2 层:VFS(虚拟文件系统层)

VFS 是 Linux 内核提供的一层抽象,它统一了不同文件系统的接口。无论你用的是 ext4、XFS、NFS 还是 tmpfs,在应用层看来都是统一的 open()、read()、write() 接口。

VFS 的核心数据结构:

struct file:已打开文件的抽象

struct dentry:目录项缓存

struct inode:文件元数据

struct super_block:文件系统超级块

VFS 层还有一个关键机制:页缓存(Page Cache)。所有文件的读写都会经过页缓存。写操作默认是"写回"(write-back)模式:数据先写入页缓存,之后由内核线程异步刷到磁盘。这意味着 write() 系统调用通常会立即返回,但数据还没真正落盘。

第 3 层:具体文件系统(ext4/XFS/btrfs)

文件系统层负责把文件操作翻译成对底层块设备的请求。它要管理:

inode 和 block 的分配

元数据的组织(目录结构、文件大小、时间戳)

块寻址和扩展

文件系统的日志(ext4 的 journal)

不同文件系统在 I/O 调度策略、日志模式、空间分配方式上有显著差异,这会直接影响 I/O 性能表现。

第 4 层:通用块设备层(Block Layer)

这是 I/O 栈中最复杂的一层。Block Layer 接收来自文件系统的块请求(bio),并负责:

I/O 调度:把多个相邻扇区的请求合并,减少磁盘寻道次数

请求排队:不同进程的 I/O 请求进入同一个队列

调度算法选择:CFQ、Deadline、NOOP、MQ-Deadline(blk-mq)

Linux 4.13 之后默认使用 mq-deadline,之前默认是 CFQ。调度算法的选择对 I/O 延迟影响很大。

关键数据结构:

struct bio:代表一个 I/O 请求

struct request:经过调度器合并后的磁盘请求

struct request_queue:请求队列

第 5 层:设备驱动层

设备驱动把块请求翻译成针对具体硬件的操作指令。机械硬盘(HDD)走 SCSI/SATA 协议,SSD 走 NVMe 协议,虚拟化环境走 virtio-blk 或 NVMe 模拟。

设备驱动的性能差异:

HDD:受寻道时间和转速限制,顺序读尚可,随机 I/O 极差

SATA SSD:受 SATA 通道带宽限制,顺序读约 550MB/s

NVMe SSD:走 PCIe 通道,延迟低、并发强,顺序读可达数 GB/s

虚拟磁盘:受宿主机 I/O 队列和物理磁盘双重影响

第 6 层:物理磁盘

最终的存储介质。机械硬盘有磁头寻道、盘片旋转的物理限制;SSD 有读写放大、NAND 颗粒寿命的问题。

iowait 到底是什么

iowait 是 top 和 vmstat 输出中的一个指标,全称是 "I/O wait"。它表示 CPU 处于空闲状态,但有未完成的 I/O 请求正处于不可中断的等待状态。

理解 iowait 有几个关键点:

iowait 不等于磁盘 I/O 繁忙

iowait 高不等于磁盘 util 高。如果只有 1 个 CPU 核心的 iowait 高,而其他核心忙碌,整体 iowait 会被均摊,看起来不高但磁盘已经很繁忙。反过来,iowait 高也不一定是磁盘慢——可能是 NFS、tmpfs、内存分配等待等场景。

iowait 是 CPU 级别的指标

iowait 是从 CPU 视角看的指标。具体来说:

 

iowait = CPU 在非空闲状态(user/nice/system)之外,剩余时间中花在 I/O 等待上的比例

 

如果你有 8 核 CPU,其中 1 核 100% iowait,剩下 7 核 0%,top 显示的整体 iowait 大约是 12.5%。所以看到整体 iowait 20% 时,实际上可能是 1 核 100% iowait(4 核机器)或者 2 核全满(8 核机器)。

iowait 高的本质是 CPU 饿着

iowait 高的意思是:CPU 想干活,但活被 I/O 卡住了,只能等。CPU 本身没有损坏,但利用率上不去,系统吞吐量下降。

iowait 和 CPU idle 的区别

CPU idle:CPU 没事干,没有任何工作

iowait:CPU 没事干,但有 I/O 请求正在等待

两者在 top 里都显示为 idle,但含义完全不同。一个是系统负载极低,一个是系统被 I/O 阻塞了。

排查工具链:从宏观到微观

排查 iowait 问题的工具有好几个,每个工具关注不同的层级。

1. vmstat:看整体趋势

 

# vmstat 1:每秒采样一次
vmstat 1 10

 

输出示例:

 

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  3      0 812340 123456 4567890    0    0  1200   500  3000  4500  5  3  0 92  0

 

关键列:

r:运行队列长度,待运行的进程数

b:不可中断睡眠状态的进程数(通常是被 I/O 阻塞)

bi(blocks in):从磁盘读入的块数/秒

bo(blocks out):写入磁盘的块数/秒

wa(wait I/O):iowait 占用的 CPU 百分比

重点关注:若 b 列长期大于 0,说明有进程被阻塞在 I/O 上。若 wa 长期高于 30%,说明 iowait 是主要瓶颈。

2. iostat:看磁盘 I/O 细节

 

# 安装 sysstat 包
# CentOS: yum install sysstat
# Ubuntu: apt install sysstat

# 基本用法:-x 显示扩展信息,-k 以 KB 为单位,1 表示每秒
iostat -xzk 1 5

 

输出示例:

 

Linux 5.4.0-xxxx (hostname)     05/19/2026     _x86_64_        (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          3.21    0.00    1.45   45.23    0.00   50.11

Device:   rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s  avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda         0.00     12.00    0.00   120.00     0.00  16384.00   272.73    45.00  375.00    0.00  375.00   8.26  99.20

 

重点字段解读:

%util:设备利用率,接近 100% 说明磁盘已经饱和。这是判断磁盘是否是瓶颈的最直接指标。

await:平均 I/O 响应时间(毫秒),包括排队时间和实际服务时间。

avgqu-sz:平均队列深度,磁盘请求的排队长度。如果持续高于 4(机械硬盘)或者高于 32(SSD),说明 I/O 超过了磁盘处理能力。

r_await / w_await:读写分离的平均响应时间。

svctm(service time):平均服务时间,已经废弃(不可靠),仅做参考。

rrqm/s / wrqm/s:每秒合并的读写请求数。合并越多,效率越高。

实战判断:

%util 接近 100% 且 await 很高 → 磁盘本身是瓶颈

%util 接近 100% 但 await 很低 → 磁盘性能很好但请求太多,块层排队严重

%util 不高但 iowait 高 → 可能是 NFS、内存压力、或 CPU 层面的等待

3. iotop:定位具体进程

 

# 需要 root 权限
iotop -o -b -n 2 -d 5

 

参数说明:

-o:只显示有 I/O 活动的进程

-b:批处理模式(非交互)

-n 2:刷新 2 次

-d 5:每次间隔 5 秒

输出示例:

 

Total DISK READ:        0.00 B/s | Total DISK WRITE:      16.39 G/s
Actual DISK READ:        0.00 B/s | Actual DISK WRITE:      16.39 G/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO    COMMAND
18123 be/4 mysql       0.00 B/s   16.39 G/s    0.00 %  99.99 % mysqld
18234 be/3 root         0.00 B/s    0.00 B/s    0.00 %   0.00 % kworker/u256:2

 

重点:IO 列显示该进程的 I/O 占用百分比。如果 MySQL 的 IO 列接近 100%,基本可以确定是数据库的 I/O 导致的问题。

4. pidstat:进程级别 I/O 统计

 

# 查看每个进程的 I/O 统计,每秒一次
pidstat -d 1 5

 

输出示例:

 

Linux 5.4.0-xxxx (hostname)     05/19/2026     _x86_64_        (8 CPU)

0310 PM     UID       PID     kB_rd/s     kB_wr/s    iodelay  command
0311 PM       0     18123      0.00  16384000.00         0  mysqld
0311 PM       0     18234      0.00       120.00         0  rsyslogd

 

kB_rd/s:每秒读取 KB 数

kB_wr/s:每秒写入 KB 数

iodelay:I/O 延迟(以时钟周期计),反映进程等待 I/O 的时间

command:进程名

iodelay 越大,说明进程花在等待 I/O 上的时间越多。

5. /proc/diskstats:原始磁盘统计

 

cat /proc/diskstats

 

这是 iostat 数据的来源。如果需要自定义监控或者写脚本采集,可以用这个接口。

输出字段(按顺序):

设备号(major:minor)

设备名

读完成数

读合并数

读扇区数

读花费时间(毫秒)

写完成数

写合并数

写扇区数

写花费时间(毫秒)

I/O 当前进度(正在进行中的 I/O)

I/O 花费时间(毫秒,累计值)

Weighted I/O time(累计 I/O 时间,含排队)

计算平均 I/O 响应时间:

 

(总 I/O 时间 / (读完成数 + 写完成数)) = 平均每次 I/O 的毫秒数

 

6. blktrace:追踪每个 I/O 请求

如果普通工具不够用,需要看每个 I/O 请求的细节,用 blktrace:

 

# 安装:yum install blktrace 或 apt install blktrace
# 对特定设备追踪 10 秒
blktrace -d /dev/sda -o /tmp/blktrace -w 10

# 分析结果
blkparse -i /tmp/blktrace -d | head -50

 

blktrace 可以看到每个 I/O 请求从应用发出、到 VFS、到块设备层的完整耗时分布。这个工具一般用于深度性能分析,生产环境慎用,会产生大量数据并影响性能。

7. free 和 /proc/meminfo:看内存压力

 

free -h
cat /proc/meminfo | grep -E "^(MemTotal|MemFree|MemAvailable|Cached|Buffers|SwapTotal|SwapFree|SwapCached)"

 

内存压力会导致两个 I/O 相关问题:

Swap 使用:如果 SwapFree 持续下降,说明系统在使用 Swap,而 Swap 在磁盘上,会产生大量 I/O

Page Cache 回收:内存紧张时,内核会回收 Page Cache,导致原本可以通过缓存满足的读 I/O 变成磁盘直接读

检查 Swap 是否在大量使用:

 

# 看 Swap 使用情况
vmstat 1 10 | awk '{print $2,$3,$4}'

# 如果 si(swap in)和 so(swap out)长期不是 0,说明在换页

 

常见根因与排查路径

根因一:MySQL InnoDB 脏页刷盘

MySQL InnoDB 有自己的缓存池(Buffer Pool),数据页在内存中修改后变成脏页,由后台线程定期刷到磁盘。如果脏页积累过多或者磁盘写入速度跟不上, InnoDB 的 page_cleaner 线程会产生大量 I/O。

排查步骤:

看 MySQL 的 I/O 写入量:

 

pidstat -d 1 5 -p $(pgrep -x mysqld)

 

看 InnoDB 脏页状态:

 

-- 登录 MySQL
mysql -u root -p

-- 查看脏页比例和刷新状态
SHOW ENGINE INNODB STATUSG

-- 关注以下指标:
-- Pages made flushd:累计页刷新数
-- InnoDBBufferPool 的脏页比例

 

看 innodb 相关配置:

 

-- 查看关键参数
SHOW VARIABLES LIKE '%innodb%flush%';
SHOW VARIABLES LIKE 'innodb_max_dirty_pages_pct';
SHOW VARIABLES LIKE 'innodb_buffer_pool_pages_dirty';

 

关键参数说明:

innodb_max_dirty_pages_pct:脏页比例上限,默认 75(MySQL 5.6),超过这个比例会强制刷新

innodb_io_capacity:InnoDB 能承受的 I/O 吞吐量上限,默认 200(机械硬盘),SSD 应该设置更高

innodb_flush_method:刷新方式,O_DIRECT(绕过 OS 页缓存)或 fsync(通过 OS 页缓存)

修复方案:

 

# 方法1:调整 innodb_io_capacity(如果是 SSD)
# 在 my.cnf 中添加或修改:
# innodb_io_capacity = 2000    # SSD 推荐 2000-10000
# innodb_io_capacity_max = 4000

# 方法2:增加 Buffer Pool 大小,减少磁盘访问
# innodb_buffer_pool_size = 16G   # 建议设置为可用内存的 60-80%

# 方法3:调整脏页刷新策略
# innodb_max_dirty_pages_pct = 50  # 降低阈值,更频繁刷新

 

风险提醒:修改 innodb_flush_method 或增大 innodb_io_capacity 可能导致 I/O 峰值更高,短期更卡。生产环境建议在低峰期操作,并提前备份配置。

根因二:Nginx/Apache 日志写入

每个 HTTP 请求都会写入 access log,如果 QPS 很高(几千甚至几万),日志写入 I/O 会非常频繁。

排查步骤:

看 nginx 进程的写 I/O:

 

pidstat -d 1 -p $(pgrep -x nginx | tr '
' ',') 2 5

 

看日志文件的写入速度:

 

# -c 只显示变化的字节,-a 显示属性变化,-f 持续监控
tail -f /var/log/nginx/access.log | pv -rate > /dev/null

 

看 nginx 配置中的日志设置:

 

# nginx.conf 中查看
access_log /var/log/nginx/access.log combined buffer=16k flush=5s;
error_log /var/log/nginx/error.log warn;

 

修复方案:

 

# 方案1:关闭 access log(不推荐生产环境,但临时救火有效)
access_log off;

# 方案2:降低日志级别,只记录 error
access_log /var/log/nginx/access.log error;

# 方案3:开启日志缓冲,减少系统调用
access_log /var/log/nginx/access.log combined buffer=64k gzip=4;

# 方案4:日志写入 tmpfs(内存文件系统),避免磁盘 I/O
# 在 /etc/fstab 中添加:
# tmpfs /var/log/nginx tmpfs defaults,size=512m 0 0
# 注意:重启后日志会丢失,需要定期同步到磁盘

# 方案5:使用 syslog 协议将日志发送到远程日志服务器
access_log syslog:server=192.168.1.100:514,facility=local7,tag=nginx,severity=info combined;

 

验证方式:

 

# 重载 nginx 配置
nginx -s reload

# 确认配置生效
nginx -t

# 之后观察 iostat,看 %util 是否下降

 

根因三:Docker 容器日志

Docker 容器的日志默认由 dockerd 接收并写入 /var/lib/docker/containers//-json.log。如果容器 stdout 输出很多,日志文件会快速膨胀,撑爆磁盘并产生大量 I/O。

排查步骤:

看 Docker 日志文件大小:

 

find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} ; | sort -k5 -h | tail -20

 

看 dockerd 进程的 I/O:

 

pidstat -d 1 -p $(pgrep -x dockerd) 2 5

 

看具体容器的日志量:

 

# 看容器最近 100 行日志的行数增长速率
watch "docker logs --tail 100  2>&1 | wc -l"

 

修复方案:

 

# 方案1:限制容器日志大小(在 docker-compose.yml 中)
# docker-compose.yml
logging:
  driver: "json-file"
  options:
    max-size: "50m"      # 单个日志文件最大 50MB
    max-file: "5"       # 最多保留 5 个文件

# 方案2:限制容器日志直接写入系统日志(降低 dockerd 压力)
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
    "max-size": "50m",
    "max-file": "5"
  },
"storage-driver": "overlay2"
}

# 修改后重启 dockerd
systemctl restart dockerd

# 方案3:手动清理历史日志(高风险,需确认容器正常运行)
# 先停止容器日志写入
# truncate -s 0 /var/lib/docker/containers//*-json.log

# 方案4:改用 journald 日志驱动
# /etc/docker/daemon.json
{
"log-driver": "journald",
"log-opts": {}
}

# 之后重启 dockerd
systemctl restart dockerd

 

风险提醒:修改 dockerd 配置会重启 Docker 服务,导致所有容器停止。生产环境需要在维护窗口操作,提前通知用户,并确认容器支持重启后自动拉起(使用 restartpolicy)。

根因四:文件系统碎片与日志模式

ext4 文件系统在频繁的小文件写入后会产生碎片,导致文件读取时磁头移动次数增加,I/O 延迟上升。

排查步骤:

查看文件系统的碎片情况:

 

# 安装 e2fsprogs 包
yum install e2fsprogs -y

# 查看 ext4 文件系统碎片
e2fsck -n /dev/sda1 2>&1 | grep -i fragment

# 或者用 debugfs 查看
debugfs -R "frag /" /dev/sda1 2>/dev/null

 

查看文件系统日志模式:

 

dumpe2fs /dev/sda1 | grep -i "journal mode"
tune2fs -l /dev/sda1 | grep "Journal"

 

ext4 的日志模式:

journal:所有数据写入前先写日志,最安全但最慢

ordered(默认):只记录元数据日志,数据写入在元数据提交之后

writeback:不记录数据,只记录元数据,最快但不安全

修复方案:

 

# 方法1:在线调整 ext4 日志模式为 writeback(提升写入性能)
tune2fs -o journal_data_writeback /dev/sda1
tune2fs -O "^has_journal" /dev/sda1
tune2fs -O "has_journal" /dev/sda1

# 注意:调整为 writeback 后,如果突然断电可能丢失数据
# 确保有 UPS 和硬件 RAID

# 方法2:对于 XFS 文件系统,看是否可以优化
# XFS 日志默认在外置设备上,性能更好

# 方法3:定期碎片整理(需要卸载文件系统或者单用户模式)
# CentOS 7 以后:
umount /data
xfs_frags /dev/sda2   # 检查碎片
xfs_fsr /dev/sda2      # 碎片整理,可能需要数小时
mount /data

 

风险提醒:修改文件系统参数和碎片整理都需要谨慎操作。生产环境建议先在测试环境验证,并确保有完整备份。碎片整理期间性能会严重下降。

根因五:Swap 使用

当物理内存耗尽,系统会把不活跃的内存页换出到 Swap 空间。如果 Swap 所在的磁盘是机械硬盘,大量的换入换出会导致严重的 I/O 风暴。

排查步骤:

查看 Swap 使用情况:

 

# 确认哪个设备是 Swap
swapon -s

# 确认 Swap 使用量和换入换出速率
vmstat 1 10

# 看具体哪些进程在换入换出
cat /proc/$(pgrep -x mysqld)/status | grep -i swap

 

确认 Swap 是否在持续增长:

 

# 每秒采样一次,监控 si(swap in)和 so(swap out)列
vmstat 1 | awk '{print $3,$4,$7,$8}'

 

看内存分配情况:

 

# 看哪些进程占用的内存最多
ps aux --sort=-%mem | head -20

 

修复方案:

 

# 方案1:临时关闭 Swap(仅限内存充足时)
swapoff -a    # 关闭所有 Swap
swapon -a     # 重新开启

# 方案2:降低 Swap 优先级(让系统尽量用物理内存)
# 在 /etc/sysctl.conf 中添加:
vm.swappiness = 10    # 默认 60,值越低越少使用 Swap

# 立即生效:
sysctl -p

# 方案3:把 Swap 放到 SSD 上
# 创建一个 SSD 上的 Swap 文件
fallocate -l 8G /mnt/ssd/swapfile
chmod 600 /mnt/ssd/swapfile
mkswap /mnt/ssd/swapfile
swapon /mnt/ssd/swapfile

# 在 /etc/fstab 中添加:
# /mnt/ssd/swapfile none swap sw 0 0

# 方案4:确认 MySQL 的内存配置是否合理
# MySQL 5.7: 确保 innodb_buffer_pool_size <= 物理内存 * 0.8
# 避免所有进程内存之和超过物理内存

 

根因六:批量写入任务

运维过程中常见的定时任务:备份脚本、rsync 同步、日志切割(logrotate)、数据库全量导出、大文件压缩等。这些任务往往在半夜或高峰期跑,产生大量 I/O 把正常业务拖垮。

排查步骤:

看谁在写磁盘:

 

# 高频观察
while true; do echo "=== $(date) ==="; ps aux --sort=-%mem | awk '{print $2,$3,$4,$11}' | head -15; sleep 3; done

 

看定时任务:

 

# 看 crontab
crontab -l
cat /etc/crontab
ls -la /etc/cron.d/

 

看最近修改过的文件:

 

# 找出最近 1 分钟内写入超过 100MB 的文件
find / -type f -mmin -1 -size +100M 2>/dev/null

 

修复方案:

 

# 方案1:使用 ionice 限制 I/O 优先级
# cron 任务中使用 ionice 限制
0 2 * * * ionice -c 3 -n 7 /backup/backup.sh

# ionice 参数说明:
# -c 3:空闲类(idle),只有磁盘空闲时才执行
# -c 2:最佳努力类(best effort),可以设置 -n 优先级(0-7,越低越优先)
# -c 1:实时类(real time),最高优先级,生产环境慎用

# 方案2:使用 cgroups 限制 I/O
# 创建 cgroup 限制写入带宽
mkdir /sys/fs/cgroup/blkio/limited
echo"8:0 1048576" > /sys/fs/cgroup/blkio/limited/blkio.throttle.write_bps_device
echo $(pgrep -f backup.sh) > /sys/fs/cgroup/blkio/limited/tasks

# 方案3:使用 rsync 的限速参数
rsync -avz --bwlimit=10240 /source/ /dest/

# 方案4:调整 logrotate 时间
# /etc/logrotate.conf 中把 daily 改成 weekly 或 monthly
# 减少日志切换频率

# 方案5:备份任务安排到低峰期
# 安排在凌晨 3-5 点,业务最空闲的时段
0 3 * * * ionice -c 3 /backup/backup.sh >> /var/log/backup.log 2>&1

 

实战案例:从 iowait 高到定位 MySQL 脏页刷新

案例背景

某台 16 核 64GB 内存的物理机,运行 MySQL 5.7.30,数据库大小约 300GB。最近一周监控显示:

CPU iowait 从平时的 5% 上升到 35-45%

iostat 显示 sda 的 %util 持续在 95% 以上

await 从 5ms 上升到 400ms 以上

数据库查询延迟明显上升,从 10ms 上升到 500ms+

第 1 步:初步判断

先用 top 看整体 CPU:

 

top -b -n 1
%Cpu(s):  3.2 us,  1.5 sy,  0.0 ni, 58.2 id, 37.1 wa,  0.0 hi,  0.0 si,  0.0 st

 

37% 的 iowait 确认了问题。再用 iostat 看磁盘:

 

iostat -xzk 1 5
Device:  rrqm/s  wrqm/s   r/s   w/s    rkB/s    wkB/s  avgrq-sz avgqu-sz   await r_await w_await  svctm %util
sda         0.00   12.00  0.00 120.00     0.00  16384.00   272.73   45.00  375.00    0.00  375.00  8.26 99.20

 

%util 99.20%,w/s 120 次/秒,写入 16MB/s,avgqu-sz 45,说明队列严重积压,写 I/O 响应时间高达 375ms。

第 2 步:定位进程

用 iotop 找元凶:

 

iotop -o -b -n 3 -d 3
Total DISK READ:        0.00 B/s | Total DISK WRITE:      16.39 G/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO    COMMAND
18123 be/4 mysql       0.00 B/s   16.39 G/s    0.00 %  99.99 % mysqld

 

mysqld 进程占了 99.99% 的 I/O 带宽,写入速度 16.39 G/s(这个数字异常大,说明单位可能是块设备报的累计值,实际写入速率需要用 pidstat 确认)。

第 3 步:分析 MySQL I/O 来源

登录 MySQL 查看状态:

 

mysql> SHOW ENGINE INNODB STATUSG

 

找到关键段落:

 

---
LOG
---
Log sequence number          28495678912
Log flushed up to            28495456789
Pages flushed up to          28494000000
Last checkpoint at           28493000000
100 pending log writes, 200 pending chkp writes

 

100 个待写入的日志写操作,200 个待检查点的脏页刷新。这就是 I/O 压力的来源。

再看 InnoDB 配置:

 

mysql> SHOW VARIABLESLIKE'innodb_%flush%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| innodb_flush_log_at_trx_commit | 1       |
| innodb_flush_method             | O_DIRECT |
+---------------+-------+

mysql> SHOWVARIABLESLIKE'innodb_io_capacity%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_io_capacity     | 200   |
| innodb_io_capacity_max | 2000  |
+------------------------+-------+

mysql> SHOWSTATUSLIKE'Innodb_buffer_pool_pages_dirty';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| Innodb_buffer_pool_pages_dirty | 20480 |
+------------------------------+-------+

 

发现问题:

innodb_io_capacity 只有 200,这个值是给机械硬盘设计的,对于 NVMe SSD 来说严重偏低

Innodb_buffer_pool_pages_dirty 有 20480 个脏页,每个 16KB,总共约 320MB 脏页等待刷新

innodb_flush_log_at_trx_commit = 1,每次事务提交都会刷 redo log,I/O 压力会比较大

第 4 步:确认根因

综合以上信息,根因分析:

innodb_io_capacity = 200 严重偏低,后台刷新线程每次只能处理很少的脏页

脏页积累速度(业务写入) > 脏页刷新速度(innodb_io_capacity 限制)

脏页堆积到 innodb_max_dirty_pages_pct 阈值后,InnoDB 被迫强制刷新,阻塞前台查询

大量写 I/O 积压在块层队列中,avgqu-sz 达到 45,await 高达 375ms

第 5 步:修复方案

风险评估:修改 innodb_io_capacity 是在线参数,可以动态调整,不需要重启数据库,风险可控。但调整后短时间内刷新速度加快,磁盘 I/O 会更集中。

操作步骤:

 

-- 先备份当前配置
mysqld --help --verbose | grep my.cnf

-- 备份 my.cnf
cp /etc/my.cnf /etc/my.cnf.bak.$(date +%Y%m%d)

-- 动态调整参数(当前 session 生效)
SETGLOBAL innodb_io_capacity = 2000;
SETGLOBAL innodb_io_capacity_max = 4000;

-- 确认生效
SHOWVARIABLESLIKE'innodb_io_capacity%';

-- 动态调整 innodb_max_dirty_pages_pct(MySQL 5.7 可在线调整)
SETGLOBAL innodb_max_dirty_pages_pct = 60;

-- 写进配置文件,永久生效
-- 在 [mysqld] 段添加:
-- innodb_io_capacity = 2000
-- innodb_io_capacity_max = 4000
# 编辑 my.cnf
vim /etc/my.cnf

# 在 [mysqld] 段添加或修改
[mysqld]
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_max_dirty_pages_pct = 60

 

第 6 步:验证效果

修改后观察:

 

# 1. 监控 iowait 是否下降
vmstat 1 30

# 2. 监控磁盘 util 是否下降
iostat -xzk 1 30

# 3. 监控脏页是否在正常下降
mysql -u root -p -e "SHOW STATUS LIKE 'Innodb_buffer_pool_pages_dirty';" every 10s

 

预期效果(5-10 分钟后):

iowait 从 35-45% 下降到 5-10%

%util 从 95% 下降到 30-50%

await 从 400ms 下降到 10-30ms

avgqu-sz 从 45 下降到 5 以下

Innodb_buffer_pool_pages_dirty 稳定在 2000 以下

第 7 步:回滚方案

如果调整后出现其他问题(比如 I/O 峰值把磁盘带宽占满影响其他服务),立即回滚:

 

SET GLOBAL innodb_io_capacity = 200;
SET GLOBAL innodb_io_capacity_max = 2000;

 

同时还原 my.cnf:

 

cp /etc/my.cnf.bak.$(date +%Y%m%d) /etc/my.cnf
systemctl restart mysqld  # 需要在维护窗口操作

 

诊断流程图:iowait 排查 7 步法

 

┌─────────────────────────────────────────────┐
│ 1. vmstat 1 10                               │
│    发现 b 列 > 0 或 wa 列持续 > 20%          │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 2. iostat -xzk 1 5                          │
│    判断 %util 是否 > 80%                     │
│    判断 avgqu-sz 是否过高                    │
│    判断是读还是写为主                        │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 3. iotop -o -b -n 3                         │
│    找出具体是哪个进程在产生 I/O               │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 4. pidstat -d 1 -p                     │
│    确认进程的 I/O 读写速率和延迟              │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 5. 进一步分析:                              │
│    - MySQL: SHOW ENGINE INNODB STATUS        │
│    - Nginx: access log 配置检查              │
│    - Docker: docker logs / 容器日志大小      │
│    - Swap: free + vmstat 看 si/so           │
│    - 定时任务: crontab -l                   │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 6. 确认根因后,制定修复方案                   │
│    - 调整参数(在线 or 维护窗口)             │
│    - 关闭/限流/迁移 I/O 来源                 │
│    - 升级硬件(SSD)                         │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│ 7. 验证效果                                  │
│    - 监控 iostat / vmstat 趋势              │
│    - 业务延迟是否恢复正常                    │
│    - 准备回滚方案                            │
└─────────────────────────────────────────────┘

 

高风险操作汇总

操作 风险等级 风险描述 缓解措施
修改 innodb_flush_method 切换刷盘策略,可能导致数据丢失或 I/O 暴增 确保 UPS、RAID、有备份
调整 innodb_io_capacity 调高后刷新更快但短期 I/O 更集中 先在从库测试,低峰期操作
关闭 Swap 内存不足时无兜底,可能 OOM 先确认物理内存充足
修改 dockerd 日志配置 重启 Docker,所有容器中断 确认容器有 restartpolicy
在线碎片整理 整理期间性能严重下降 单用户模式,低峰期操作
truncate 容器日志 日志丢失,排查问题困难 先 docker logs 导出重要日志
ionice 限制 I/O 可能导致任务执行时间变长 确保 cron 有合理的超时设置

内核参数调优:控制 I/O 行为

Linux 内核提供了大量可调参数(sysctl),可以控制 I/O 行为。合理调整这些参数,可以从系统层面改善 I/O 性能或限制 I/O 滥用。

1. 调整内核 I/O 调度器

调度器决定了请求如何排序和合并。不同调度器适合不同场景:

 

# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# noop [deadline] cfq  输出中 [] 包围的是当前值

# deadline 调度器:适合数据库、SSD、随机读写场景
# cfq 调度器:适合桌面系统和通用 Linux,对实时性要求高的场景表现差
# noop 调度器:适合 SSD、虚拟机、RAID 卡带缓存的场景
# mq-deadline 调度器:blk-mq 版本的 deadline,更好的并发支持

# 临时修改(重启后失效)
echo deadline > /sys/block/sda/queue/scheduler

# 永久修改(在 udev 规则或启动脚本中)
# /etc/udev/rules.d/60-io-scheduler.rules
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="deadline"

 

调度器选择建议:

机械硬盘(HDD):deadline 或 cfq

SATA SSD:deadline 或 noop

NVMe SSD:noop 或 mq-deadline

虚拟化环境:noop(virtio-blk 或 pvscsi 本身有调度)

数据库服务器:deadline(避免 cfq 的"公平"调度导致的延迟不稳定)

2. 调整块设备队列深度

 

# 查看队列深度
cat /sys/block/sda/queue/nr_requests
# 默认 128,适合机械硬盘
# SSD 和 RAID 卡可以设置为 512-1024

# 临时调整
echo 512 > /sys/block/sda/queue/nr_requests

# 永久修改(在 /etc/rc.local 或 systemd service 中)
# echo 512 > /sys/block/sda/queue/nr_requests

 

队列深度过小会导致高并发下请求排队不足,队列深度过大会增加延迟。生产环境建议根据磁盘数量和并发连接数调整。

3. 调整 read_ahead_kb(预读)

 

# 查看预读大小(单位 KB)
cat /sys/block/sda/queue/read_ahead_kb
# 默认 128KB

# 临时调整(适合顺序读多的场景,如数据仓库)
echo 256 > /sys/block/sda/queue/read_ahead_kb

# 永久修改
# echo 256 > /sys/block/sda/queue/read_ahead_kb

# 适合随机读多的场景(如数据库)可以降低预读
echo 16 > /sys/block/sda/queue/read_ahead_kb

 

预读的作用是:当内核读取一个扇区时,提前把相邻的后续扇区也读入缓存。如果业务是大量顺序读(如备份、ETL),增大预读能显著提升吞吐。如果是随机读(如数据库),增大预读只会浪费 I/O 带宽。

4. 调整内核脏页刷新参数

内核的内存管理子系统会定期把脏页(已修改但未写回磁盘的页)刷新到磁盘。相关参数:

 

# 查看当前值
cat /proc/sys/vm/dirty_background_ratio   # 脏页占可用内存的比例,默认 10
cat /proc/sys/vm/dirty_ratio             # 强制同步的脏页比例,默认 20
cat /proc/sys/vm/dirty_expire_centisecs  # 脏页被认为是"可刷新"的时间,单位是 0.01 秒,默认 3000(30 秒)
cat /proc/sys/vm/dirty_writeback_centisecs # 后台刷新线程运行间隔,默认 500(5 秒)

# 临时调整(降低脏页比例,适合数据库服务器)
echo 5 > /proc/sys/vm/dirty_background_ratio
echo 10 > /proc/sys/vm/dirty_ratio

# 永久修改(在 /etc/sysctl.conf 中)
# vm.dirty_background_ratio = 5
# vm.dirty_ratio = 10
# vm.dirty_expire_centisecs = 3000
# vm.dirty_writeback_centisecs = 500

 

重要:这些参数直接影响数据库的 I/O 模式。如果 dirty_background_ratio 太高,后台刷新线程会在磁盘已经很繁忙时继续往磁盘写数据,导致 I/O 拥塞。如果 dirty_ratio 太高,当进程写入大量数据时,可能在最糟糕的时机触发强制刷盘(此时进程被阻塞,用户请求积压)。

数据库服务器的推荐配置(MySQL/ PostgreSQL):

dirty_background_ratio = 5(更积极的后台刷新)

dirty_ratio = 10-15(更低的强制刷盘阈值)

dirty_expire_centisecs = 500(脏页 5 秒后就可被刷新,更快响应)

5. 开启 I/O 统计并实时监控

 

# 开启 I/O 延迟统计(需要内核支持 blk_mq)
echo 1 > /sys/block/sda/queue/iotail_latency

# 实时监控 I/O 延迟分布
# 安装 bpftrace(高级工具,需要 root)
# bpftrace -e 'kprobe:blk_account_io_start { @ = lhist(args->bytes, 0, 4096, 512); }'

# 或者用 iostat 持续监控并记录
iostat -xzk 1 >> /var/log/iostat.log &
# 注意:这个日志会持续增长,需要 logrotate 处理

 

6. 限制单进程的 I/O 带宽

如果某个进程(如备份脚本)占用了过多 I/O,影响正常业务,可以用 cgroups v2 限制:

 

# 创建 IO 限制组(cgroups v2)
mkdir -p /sys/fs/cgroup/io/limited

# 限制 /dev/sda 的写入带宽为 50MB/s
echo"8:0 wbps=52428800" > /sys/fs/cgroup/io/limited/max.bps.write

# 限制 IOPS 为 1000
echo"8:0 wiops=1000" > /sys/fs/cgroup/io/limited/max.iops.write

# 把备份进程加入限制组
echo $(pgrep -f backup.sh) > /sys/fs/cgroup/io/limited/tasks

# 监控限制效果
cat /sys/fs/cgroup/io/limited/io.stat

 

实战案例 2:Nginx 日志写入导致的 iowait 飙升

案例背景

某台 4 核 8GB 内存的 Web 服务器,运行 Nginx,每天 QPS 约 2000 次。最近发现 iowait 经常在业务高峰期(上午 10-12 点)飙升到 30-40%,CPU idle 掉到 20%,导致部分 HTTP 请求超时。

第 1 步:初步排查

 

# 1. 看 CPU 状态
top -b -n 1 | head -20
# us 40%, sy 10%, wa 35%, id 15%

# 2. 看磁盘 I/O
iostat -xzk 1 3
# %util 接近 100%,w/s 200+,写为主

# 3. 找进程
iotop -o -b -n 5 -d 2

 

输出显示:Nginx 主进程的 IO 占比 80% 以上。

第 2 步:分析日志配置

 

# 看 Nginx 日志配置
nginx -T | grep -A5 "access_log"

 

配置是:

 

access_log /var/log/nginx/access.log combined;
error_log /var/log/nginx/error.log warn;

 

没有缓冲、没有压缩、没有限时刷新。每次请求都同步写日志。

第 3 步:修复方案

 

# 修改 nginx.conf
http {
    # 开启日志缓冲,减少系统调用
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    # access log 加缓冲,16KB 缓冲区,5 秒刷新一次
    access_log /var/log/nginx/access.log main buffer=16k flush=5s;

    # error log 用 info 级别,减少写入量
    error_log /var/log/nginx/error.log info;

    # 开启异步日志写入(需要配置缓冲)
    # 注意:异步日志在进程崩溃时可能丢失最后几秒的日志
}

 

第 4 步:验证

 

# 重载配置
nginx -s reload

# 观察 iostat
iostat -xzk 1

 

重载后观察:

%util 从 100% 降到 30-40%

w/s 从 200+ 降到 40-60

iowait 从 35% 降到 5-10%

HTTP 请求延迟明显改善,P99 从 800ms 降到 150ms

第 5 步:更激进的方案(可选)

如果业务能接受日志丢失(短暂的服务中断不丢即可),可以写入 tmpfs:

 

# 在 /etc/fstab 中添加
tmpfs /var/log/nginx tmpfs defaults,size=256m 0 0

# 或者直接挂载
mount -t tmpfs -o size=256m tmpfs /var/log/nginx

# 将现有日志迁移
cp /var/log/nginx/access.log /tmp/backup_access.log
> /var/log/nginx/access.log   # 清空现有日志

# 定期同步到磁盘(每分钟)
# /etc/cron.d/sync-nginx-logs
* * * * * root rsync -av /var/log/nginx/ /backup/nginx_logs/ >> /var/log/sync-nginx.log 2>&1

 

风险:tmpfs 中的数据在重启后会丢失。需要确保有定期同步机制,且在日志同步的窗口期内能接受最多 1 分钟的日志丢失。

实战案例 3:系统升级 SSD 后的 I/O 优化

背景

一台运行 3 年的物理服务器,从机械硬盘(7200 转 SATA)升级到 NVMe SSD。升级后运维团队期望 I/O 问题彻底消失,但实际观察发现 iowait 仍然存在,%util 仍然偏高。

问题分析

升级 SSD 后出现新问题,说明瓶颈从"磁盘物理速度"转移到了"其他层级"。可能的原因:

调度器仍然是 cfq:cfq 的设计基于"磁盘有寻道时间"的假设,SSD 没有寻道延迟,cfq 的公平调度策略反而增加了不必要的上下文切换和延迟。

队列深度不够:机械硬盘的队列深度通常很低,SSD 支持更高的队列深度,但内核默认值可能没有充分利用。

文件系统日志模式不合适:ext4 默认的 ordered 模式每次写数据前要先写日志,对于 SSD 这种快速设备,日志写入产生的额外 I/O 仍然占比不小。

诊断步骤

 

# 1. 确认 SSD 是否被识别为旋转设备
cat /sys/block/sda/queue/rotational
# 1 = 机械硬盘,0 = SSD

# 2. 查看当前调度器
cat /sys/block/sda/queue/scheduler
# 如果是 cfq,SSD 也可能变慢

# 3. 查看队列深度
cat /sys/block/sda/queue/nr_requests
# 默认 128,SSD 可以开到 512

# 4. 查看 I/O 合并情况
cat /sys/block/sda/queue/nr_requests
iostat -x 1 | grep sda
# 如果 %util 仍然高但 r/s + w/s 不高,说明瓶颈在别处

 

优化方案

 

# 1. 切换到 noop 调度器(NVMe SSD 最适合)
echo noop > /sys/block/sda/queue/scheduler

# 2. 增大队列深度
echo 512 > /sys/block/sda/queue/nr_requests

# 3. 调整脏页刷新参数(对所有类型磁盘都有效)
echo 5 > /proc/sys/vm/dirty_background_ratio
echo 10 > /proc/sys/vm/dirty_ratio
echo 1000 > /proc/sys/vm/dirty_writeback_centisecs

# 4. 如果用 ext4,可以考虑切换到 XFS
# XFS 在高并发写入时性能更好,日志管理更高效
# 但迁移文件系统需要备份数据、重新格式化
# 永久化这些配置:/etc/rc.local 或 systemd service
cat > /etc/systemd/system/tune-ssd.service <<'EOF'
[Unit]
Description=Tune SSD I/O settings
After=local-fs.target

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo noop > /sys/block/sda/queue/scheduler'
ExecStart=/bin/bash -c 'echo 512 > /sys/block/sda/queue/nr_requests'
ExecStart=/bin/bash -c 'echo 5 > /proc/sys/vm/dirty_background_ratio'
ExecStart=/bin/bash -c 'echo 10 > /proc/sys/vm/dirty_ratio'

[Install]
WantedBy=multi-user.target
EOF

systemctl enable tune-ssd.service
systemctl start tune-ssd.service

 

验证优化效果

 

# 用 fio 做基准测试
yum install fio -y   # CentOS
# apt install fio    # Ubuntu

# 顺序写测试(64KB 块,4 线程,1GB 数据)
fio --name=seqwrite --filename=/tmp/fio_test --size=1G 
    --rw=write --bs=64k --numjobs=4 --iodepth=32 
    --ioengine=libaio --direct=1 --runtime=30 --time_based=1

# 随机读测试(4KB 块,16 线程,1GB 数据)
fio --name=randread --filename=/tmp/fio_test --size=1G 
    --rw=randread --bs=4k --numjobs=16 --iodepth=64 
    --ioengine=libaio --direct=1 --runtime=30 --time_based=1

# 查看结果中的 iops 和 lat 指标
# NVMe SSD 在优化后,随机读 iops 应该达到 10 万以上
# 延迟(clat)P99 应该低于 1ms

 

优化后实际效果:

%util 从 80-90% 降到 20-30%(消除了不必要的调度开销)

延迟稳定性提升,P99 延迟从 5ms 降到 0.8ms

吞吐量提升约 40%(队列深度增加后并发能力增强)

监控体系:从被动告警到主动预防

建立 I/O 基线

在系统正常时建立 I/O 基线,便于在异常时快速对比判断。

 

# 建立基线脚本(每日定时执行)
#!/bin/bash
# /usr/local/bin/io-baseline.sh

DATE=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="/var/log/io-baseline"
mkdir -p $OUTPUT_DIR

# 采集当前 I/O 状态
{
    echo"=== $(date) ==="
    echo"--- vmstat ---"
    vmstat 1 5
    echo"--- iostat ---"
    iostat -x 1 5
    echo"--- disk usage ---"
    df -h
    echo"--- inodes ---"
    df -i
    echo"--- mount ---"
    mount | grep "^/dev"
    echo"--- loadavg ---"
    uptime
} > $OUTPUT_DIR/baseline_$DATE.txt

# 只保留最近 30 天
find $OUTPUT_DIR -name "baseline_*.txt" -mtime +30 -delete

 

Prometheus + node_exporter 监控指标

在 Prometheus 中配置 node_exporter,可以采集以下 I/O 相关指标:

 

# prometheus.yml 中添加 node_exporter
scrape_configs:
  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

 

关键查询:

 

# 平均 I/O 等待时间百分比(每台机器)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 磁盘使用率
node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_free_bytes{mountpoint="/"}

# 每台机器的 I/O util(需要 node_exporter 0.18+)
rate(node_disk_io_time_seconds_total[5m]) * 100

# 每台机器的写入吞吐量
rate(node_disk_written_bytes_total[5m])

 

Grafana 面板配置

推荐的 Grafana 面板布局(从左到右,从上到下):

第一行:CPU 使用率分解(user、system、iowait、idle)第二行:磁盘 util(%util)和队列深度(avgqu-sz)第三行:读写吞吐量(r/s、w/s)和带宽(rkB/s、wkB/s)第四行:I/O 响应时间(await、r_await、w_await)第五行:内存使用率和 Swap 换入换出速率

告警规则配置:

 

# alertmanager.rules
groups:
-name:io_alerts
    rules:
      # iowait 持续 5 分钟高于 30%
      -alert:HighIOWait
        expr:100-(avgby(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>30
        for:5m
        labels:
          severity:warning
        annotations:
          summary:"High iowait on {{ $labels.instance }}"
          description:"iowait is {{ $value }}% for more than 5 minutes"

      # 磁盘 util 持续 5 分钟高于 90%
      -alert:DiskUtilHigh
        expr:rate(node_disk_io_time_seconds_total[5m])*100>90
        for:5m
        labels:
          severity:critical
        annotations:
          summary:"Disk {{ $labels.device }} utilization is critical"

 

总结

iowait 只是一个症状指标,它告诉你"CPU 在等 I/O",但不会告诉你"谁在产生 I/O"。排查 iowait 的核心思路是:

先确认是否真的是磁盘问题:用 iostat 看 %util,如果磁盘利用率很低但 iowait 高,可能是 NFS、网络 I/O 或 CPU 层面的等待。

找到 I/O 产生者:用 iotop 和 pidstat 定位具体进程,不要凭感觉猜测。

分析 I/O 类型:是读还是写,是顺序还是随机,是元数据还是数据,这决定了修复方向。

理解每层 I/O 栈的特性:VFS 的页缓存、文件系统的日志模式、块层的调度算法、设备的物理限制,每一层都可能成为瓶颈。

修复时考虑全局:调高 InnoDB 的 I/O 容量可能解决了 MySQL 问题,但如果这台机器还有其他服务,可能把整台机器的 I/O 带宽占满,反而更糟。

永远准备回滚方案:改配置之前先备份,改完之后监控效果,如果变差立即回滚。

建立监控基线:在系统正常时建立 I/O 基线,异常时快速对比,缩短故障定位时间。

内核参数不是万能药:调度器、队列深度、脏页参数等系统级调优能提升性能,但无法替代应用层的 I/O 优化(如缓存、异步写入、批量操作)。

iowait 排查没有银弹,每一次都需要结合业务场景、硬件配置、负载特征综合判断。工具只是手段,工程师的分析思路才是核心。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分