聊聊磁盘 IO
常见的磁盘类型
按存储原理的不同,可以把磁盘分为这么几种
- HDD 盘:没啥说的,就是平时最常见的机械盘。
- SSD 盘:用电信号来记录存储数据,而不是磁片。显然进行 I/O 时,这要比机械盘的物理寻址方式快的多。
- HHD 盘:HDD + SSD 的组合形式。
衡量磁盘性能的指标有哪些?
有时我们发现运行在 Linux 服务器上的某个应用响应很慢,你也不知道是什么原因,但是就是想看看磁盘 I/O 是不是有问题。除了 I/O 还有没有其他的指标参考呢? 答案是 有!
- 吞吐量: 指的是磁盘每秒处理 I/O 请求总的数据大小。用 TPS 表示,例如 32M/s。
- IOPS: 每秒处理 I/O 请求的次数。一般我们在说 IOPS 的时候说的就是磁盘的随机读写性能,这个指标在购买云服务器的时候,厂商会明确的告诉你该数据,用来区分不同云存属性的性能。
- 使用率: 磁盘处理 I/O 的时间百分比。过高的使用率(80%+)意味着磁盘的 I/O 存在性能瓶颈。
- 响应时间: 通过系统调用内核发出 I/O 请求,到内核收到 I/O 响应花费的时间,包括 I/O 请求处于 I/O 队列中等待的时间。
在查找磁盘瓶颈时,这些指标是我们需要特别关注的。那么,这些指标怎么看?下面我用一个案例说下。
环境准备
4G , 4c 虚拟机两台,安装了 docker 环境。
S: 10.10.3.56
C: 10.10.3.55
S: # 下载 redis 官方镜像 [root@a ~]# docker pull redis # 这里我本地 /data 目录下有一个自定义的 redis.conf 配置文件,启动时mount 到了容器的 /data 目录 [root@a ~]# docker run -itd --name redis -p 6379:6379 -v /data:/data docker.io/redis redis-server /data/redis.conf
在另一台虚拟机写脚本程序向 redis 插入数据。
C: [root@huiyoujia ~]# ls # txt 是我构造的 redis 数据文件,sh 脚本读取并插入到对端 Redis 实例 redis_data.txt request.sh [root@huiyoujia ~]# sh redis_data.txt >>/dev/null 启动脚本模拟应用程序 I/O 请求
排查问题的时候,我们把模拟环境的信息忘掉。所有都是未知的,然后一步步进行排查。
一. 从应用的角度解决磁盘 I/O 瓶颈
1. 先 top 一下看看进程信息
[root@a ~]# top top - 17:55:01 up 36 days, 6:34, 1 user, load average: 2.55, 0.13, 0.05 Tasks: 104 total, 1 running, 53 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.0 us, 0.7 sy, 0.0 ni, 15.3 id, 83.7 wa, 0.0 hi, 0.3 si, 0.0 st %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 1.3 us, 2.3 sy, 0.0 ni, 91.7 id, 0.0 wa, 0.0 hi, 4.7 si, 0.0 st KiB Mem : 4039612 total, 3391044 free, 307880 used, 340688 buff/cache KiB Swap: 2097148 total, 2088688 free, 8460 used. 3422408 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 5385 systemd+ 20 0 207616 18496 3344 D 13.3 0.5 0:14.30 redis-server 1 root 20 0 46192 7184 4600 S 0.0 0.2 0:30.39 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.44 kthreadd 3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp 4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_par_gp # top 之后按快捷键 1 查看所有 cpu 的使用情况。 top 输出的信息很多,排查 I/O 问题时这些需要重点关注: 第一行的 load average 平均负载 第三行的 %Cpu(s) us 用户态:该值高的时候要排查用户态的进程; sy 内核态: 该值高的时候,排查内核态的进程活动,cpu 上下文切换等的情况。 id cpu空闲时间百分比:该值很高,说明你的 cpu 没有什么计算任务,没事干。 wa cpu等待时间百分比:该值很高,说明 cpu 在等待进程准备就绪花了很多时间,大量进程如果在等待IO,则该值会很高。 第六行 S: 进程的状态,关注 r 状态的进程 %CPU %MEM 等标签
查看 top 输出信息,看到 Cpu0 编号的 wa 值 比较高,达到了 83.7%, 而其他 cpu 则正常,有点可疑。 再接着看 进程的 cpu 时间,发现 除了 pid 是 5385 的进程 cpu 时间有点高之外,13.3 好像也不是很高,其他进程都很正常。这里先做个标记, pid 是 5385 的进程 使用的是 redis-server 命令,有可能是 redis-server 引起的 wa 值升高。
(记住我们的初衷是:就是想看看磁盘 I/O 是不是有问题。)
现在既然怀疑是 redis-server 引起的问题,那么就看看系统磁盘的性能状态。
2. iostat 查看磁盘的状态
[root@a ~]# iostat -d -x 1 Linux 4.18.4-1.el7.elrepo.x86_64 (a.huiyoujia.com) 08/15/2019 _x86_64_ (4 CPU) Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util scd0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 sda 0.00 28.00 0.00 84.00 0.00 444.00 10.57 0.99 10.01 0.00 10.01 11.81 99.20 # -d 查看 磁盘信息; # -x 列出详细的输出信息; # 每隔 1s 刷新一次 iostat 工具除了能查看磁盘状态,还能查看 cpu 等的信息。man iostat 查看详细的使用方法。 回想一下前面提到的衡量磁盘性能的指标。 输出信息中心我们应该重点关注这些: 1. rrqm/s: 每秒合并读的请求数 2. wrqm/s: 每秒合并写的请求数 3. r/s: 每秒读请求的次数 4. w/s: 每秒写请求的次数 5. rkB/s: 每秒读的数据大小 6. wkB/s: 每秒写的数据大小 7. r_await: 完成读请求等待的时间 8. w_wait: 完成写请求等到的时间 9. %util: 磁盘的使用率 10. avgqu-sz: 磁盘 i/o 队列的长度 # 1 & 2 在排查 mysql 数据库时可以参考,因为 mysql 合并读写的情况很常见。 # 3 & 4 指的是当前磁盘的 IOPS。并不是 最大 iops。 # 5 & 6 指的就是磁盘的吞吐量 # 7 & 8 指的是响应时间 # 9 使用率
现在我们已经有了磁盘的性能指标的数据。
通过观察发现 sda 盘的磁盘使用率已经达到了 99.2%,说明 sda 盘确实有 I/O 瓶颈。看来我们的磁盘的确有瓶颈。那么是什么原因或者是系统执行的什么任务导致的 磁盘使用率升高呢? 需要 进一步分析系统进程 使用 IO 的 情况。
3. pidstat 查看进程使用系统资源的详细信息
[root@a ~]# pidstat -d 2 5 Linux 4.18.4-1.el7.elrepo.x86_64 (a.huiyoujia.com) 08/15/2019 _x86_64_ (4 CPU) 06:59:11 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 06:59:15 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 06:59:17 PM 999 5385 0.00 118.00 0.00 redis-server 06:59:13 PM 1000 32038 0.00 1.99 0.00 mysqld Average: 0 341 0.00 1.00 0.00 jbd2/sda2-8 06:59:17 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command 06:59:19 PM 999 5385 0.00 520.00 0.00 redis-server 06:59:13 PM 1000 32038 0.00 1.99 0.00 mysqld Average: UID PID kB_rd/s kB_wr/s kB_ccwr/s Command Average: 999 5385 0.00 518.35 0.00 redis-server # -d 查看进程的 I/O 情况 # 2 5 没两秒刷新一次,刷新五次退出 pidstat 不仅能看进程的 I/O情况, cpu ,上下文切换,内存等的信息也能查看。
pidstat 输出的信息中看到,pid 5383 的进程,每秒写 500 KB 的数据,其他进程则没有什么 I/O 请求。pid 5383 的进程 Command 是 redis-server。 这印证了我们前面的猜测,说明我们的猜测是正确的。
好的,现在我们已经知道了是 redis-server 导致的 磁盘 I/O 瓶颈。问题结束!
但是问题并没有解决,你的应用仍然响应很慢。还要接着查,为什么 redis-server 会导致磁盘使用率升到呢?redis 在做什么?
pidstat 输出的信息中显示 redis-server 有大量的写磁盘操作,读却很少。那么,redis 应用 在写什么?
redis 运行在用户态,根据操作系统的原理我们知道用户态的进程发起 I/O 请求,需要经过系统调用,由内核程序完成 I/O 调度操作,那么我们就要查看 redis 应用的系统调用情况。
4. strace 查看进程的系统调用信息
[root@a ~]# strace -f -p 5385 [pid 5385] epoll_wait(5, [{EPOLLIN, {u32=7, u64=7}}], 10128, 42) = 1 [pid 5385] accept(7, {sa_family=AF_INET, sin_port=htons(50014), sin_addr=inet_addr("10.10.3.55")}, [16]) = 9 [pid 5385] fcntl(9, F_GETFL) = 0x2 (flags O_RDWR) [pid 5385] fcntl(9, F_SETFL, O_RDWR|O_NONBLOCK) = 0 [pid 5385] setsockopt(9, SOL_TCP, TCP_NODELAY, [1], 4) = 0 [pid 5385] epoll_ctl(5, EPOLL_CTL_ADD, 9, {EPOLLIN, {u32=9, u64=9}}) = 0 [pid 5385] accept(7, 0x7fff29fbec00, 0x7fff29fbebfc) = -1 EAGAIN (Resource temporarily unavailable) [pid 5385] read(3, 0x7fff29fbecbf, 1) = -1 EAGAIN (Resource temporarily unavailable) [pid 5385] epoll_wait(5, [{EPOLLIN, {u32=9, u64=9}}], 10128, 40) = 1 [pid 5385] read(9, "*1\r\n$7\r\nCOMMAND\r\n", 16384) = 17 [pid 5385] read(3, 0x7fff29fbecbf, 1) = -1 EAGAIN (Resource temporarily unavailable) [pid 5385] write(8, "*200\r\n*6\r\n$9\r\npexpireat\r\n:3\r\n", 29) = 29 [pid 5385] write(8, "*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*"..., 50) = 50 [pid 5385] write(8, "*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r"..., 62) = 62 [pid 5385] write(8, "*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:"..., 55) = 55 # -f 查看字进程 或者 子线程的信息 # -p 指定 pid。这里我们已经知道是 5385 的进程了。 观察一会他这个输出基本是循环的,这里截取其中一段。(案例中是循环输出,其他情况不一定,需要多观察输出信息) epoll_wait, read, write, setsockopt 等这些都是系统函数,5383 的 进程因为 一直在写东西, 所以这里显示有很多 write() 函数调用的情况,括号里的第一个数字指的 FD,就是文件描述符为 9,在后面那一串看起来是换行权限什么的,没什么用。只要找到文件描述符就可以了。
知道了redis-server 在写 FD 号是 8 的文件 我们能干什么? 能查到具体的文件。
关于文件描述符:
Linux 一切皆文件,内核利用文件描述符来访问文件。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
1.进程级的文件描述符表;
2.系统级的打开文件描述符表;
3.文件系统的i-node表。
i-node 即 index-name ,inode 号是唯一的,切只能一个文件或者目录拥有。操作系统并不清楚文件名和目录名,而是操作 inode 号码,文件名和目录名只是友好显示, 更改文件名对操作系统无感,因为更改文件名 inode 号也不会变。如果你知道inode号,则可以用 inode 号管理这个文件。
5. lsof 查看进程打开的文件
[root@a ~]# lsof -p 5385 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME redis-ser 5385 systemd-bus-proxy cwd DIR 8,2 4096 5505025 /data redis-ser 5385 systemd-bus-proxy rtd DIR 0,43 4096 5640717 / redis-ser 5385 systemd-bus-proxy txt REG 0,43 10407928 5640636 /usr/local/bin/redis-server redis-ser 5385 systemd-bus-proxy mem REG 8,2 5640636 /usr/local/bin/redis-server (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636195 /lib/x86_64-linux-gnu/libc-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636250 /lib/x86_64-linux-gnu/libpthread-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636254 /lib/x86_64-linux-gnu/librt-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636203 /lib/x86_64-linux-gnu/libdl-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636218 /lib/x86_64-linux-gnu/libm-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy mem REG 8,2 5636181 /lib/x86_64-linux-gnu/ld-2.28.so (stat: No such file or directory) redis-ser 5385 systemd-bus-proxy 0u CHR 136,2 0t0 5 /2 redis-ser 5385 systemd-bus-proxy 1u CHR 136,2 0t0 5 /2 redis-ser 5385 systemd-bus-proxy 2u CHR 136,2 0t0 5 /2 redis-ser 5385 systemd-bus-proxy 3r FIFO 0,12 0t0 4723129 pipe redis-ser 5385 systemd-bus-proxy 4w FIFO 0,12 0t0 4723129 pipe redis-ser 5385 systemd-bus-proxy 5u a_inode 0,13 0 9538 [eventpoll] redis-ser 5385 systemd-bus-proxy 6u sock 0,9 0t0 4723130 protocol: TCPv6 redis-ser 5385 systemd-bus-proxy 7u sock 0,9 0t0 4723131 protocol: TCP redis-ser 5385 systemd-bus-proxy 8w REG 8,2 6551861 5505296 /data/appendonly.aof redis-ser 5385 systemd-bus-proxy 9u sock 0,9 0t0 4874906 protocol: TCP 第四列最后二行 8w 是 redis-server 进程操作的 FD,与之前 strace 追踪到的对应。8w,w 是写的意思,REG 常规文件,正在写的文件是 /data/appendonly.aof。 至此,我们找到了 redis 在写文件 /data/appendonly.aof,导致了磁盘 I/O 瓶颈。 ## 以上输出的信息系统 man 手册都能看到解释,忘记了可以进行查看。常用的熟悉就可以。
appendonly.aof 是 redis 的快照数据,有可能是 redis 快照策略配置的不合理,这个时候就要检查 redis 的配置了。(案例中故意配置了不合理的快照策略)
排查系统性能问题耗时费力,案例只是提供了 排查 I/O 性能的一种思路。也有其他的方法其他工具,比如 sar 可以替换 iostat 。(不过,这个方式差不多就可以搞定绝大部分 i/o 问题了)
工具链:top-->iostat-->pidstat-->strace-->lsof
这些工具使用非常灵活,参数非常多,没有必要都记住,忘记了可以查看 man 手册。
二. 从物理角度解决磁盘 I/O 瓶颈
如果不是应用程序的问题导致磁盘 I/O 瓶颈,确实是业务量的需求,那么需要物理上提高磁盘的 I/O 性能。
常用的方式:
做 Raid 磁盘阵列,不同的 Raid 级别存储效率差异较大。常见的有这些:
- Raid 0:条带模式,是一种水平扩展方式,n 快盘连接起来,看起来像一块盘,容量是 n 快盘的总和。存储时把 数据打散,分散的存到道不同的盘上,由于是 多快盘同时进行 I/O,所以 Raid 0 的 IOPS 是线性的提高。但是其中一块盘坏掉,数据就会丢失,数据冗余较差。
- Raid 1:镜像模式,至少需要两块盘才能做 Raid 1。每次把写两份数据,一份存到镜像盘上。
容量和IOPS 是线性下降。但是数据安全性很高,主盘快掉,镜像盘可以进行替换。 - Raid 10: 是 Raid 1 和 Raid 0 的组合方式,至少需要 4 快磁盘,每两块先做出成 Raid 1,然后再把 Raid 1 做成 Raid 0 。及解决了 Raid 0 数据不安全的问题,又解决了 Raid 1 IOPS 性能的问题。
- Raid 5:奇偶校验存储,至少需要三块磁盘。数据存储用奇偶校验的方式存到不同的盘上,当有磁盘损坏是,用奇偶存储推算出丢失的数据进行恢复。是Raid 0 和 Raid 1 的这种方案,IOPS 介于两者之间。
不同级别可以任意组合,像 Raid10 + Raid10 再组合成 Raid 0
IOPS: Raid 0 > Raid 10 ≈ Raid 5 > 单块磁盘 > Raid 1
mysql 数据库服务器,通常会使用 Raid 10 方式。日志服务器 Raid 0。
磁盘阵列是由 Raid 控制卡(硬件)完成的,Raid 控制卡包含了一块512M的内存,有读写缓存的能力,但是写缓存默认是未开启的,开启后能有效的提高磁盘的 IOPS 性能。
使用 SSD 盘
IOPS 作为衡量磁盘 I/O 性能的重要指标,是我们需要重点关注的。
说 ssd 盘速度快,一般指的是 IOPS 性能的提升。
如何采购硬盘?
1. 机械盘:
机械盘要关注磁盘转速参数,转速 10k, 表示 每分钟磁盘转 1w 圈。由机械盘的存储原理可知,转速越快意味着旋转延迟越小,磁针能更快的到达指定扇区。
服务器磁盘转速 10k, 15k 的比较常见。PC 机磁盘 5400, 7200。
用转速估算磁盘的 IOPS:
IOPS = 1s/(寻道时间 + 旋转延迟)ms
寻道时间:磁盘上有不同半径的磁道,磁道上分布着各个扇区,扇区是磁盘读写数据的最小单元。读写磁盘上的数据,靠的是磁针在不同的磁道上来回移动,磁道的半径不同, 磁针移动到不同磁道上的时间也就不同。目前的机械盘,寻道时间在 3-15ms(平均数) 旋转延迟:磁针到达指定半径的磁道之后,还不能立刻读写扇区上的数据,因为同半径的磁道上有很多扇区,磁针要等待磁盘旋转到达指定的扇区才行。有可能磁针刚好停留的位置 就是需要的扇区,也有可能需要磁盘旋转一周。旋转延通常迟取半圈旋转的时间。 以 15k 转速为例: 旋转时间 = 1/(15000/60s/1000) * 1/2 = 2ms,7200 是 4.2ms 这就是你 PC 慢的原因。 15k iops估算: 1s/(3 + 2)ms = 200 (最大值) 7.2k iops: 160
实际上磁盘使用不同的接口方式也能提高 IOPS 性能,机械盘的接口类型有,SCSI,SAS,SATA 这几种,15k 的SATA 接口盘比 SCSI 接口的盘 IOPS 性能要好。
2. SSD 盘:
SSD 盘没有旋转的概念,采购的时候需要问清楚厂商 IOPS 的数据。
SSD 盘按接口类型可分为 STAT ssd 和 PCIE ssd。
普通 SSD:(STAT 接口)
三星。
官网 IOPS 信息
4KB 随机读取(QD32):最大 96,000 IOPS
4KB 随机写入(QD1):最大 42,000 IOPS
IOPS 测试要跟相同的 文件快大小比较。4kb 文件块和 16kb 文件快 测试的数据没有可比性,不是一个概念。 文件快越小,才能测出 IOPS 的最佳性能。
ssd 盘的 读写是不对称的,不想机械盘一样,这根 ssd 盘的存储原理有关。
上面的 IOPS 大致能换算成随机 MB/s 的读写。
比如 随机写 : (42000 * 4kb) /1024 = 164 Mb/s
对比 HDD 盘的 IOPS ,SSD 盘 IOPS 的提升不是线性提高,而是指数级别的提高。对于 数据库服务器来讲,使用 SSD 存储,可以对 DB 速度有一个质的提升。
PCIE SSD:
Intel。
厂商 IOPS 信息
512G 容量:
4KB 随机读取:最大 340000 IOPS
4KB 随机写入:最大 275000 IOPS
PCIE ssd 在 普通 ssd 的基础上 把 IOPS 性能 又提升了 3-5 倍。
IOPS 指的是随机 I/O,能有效的衡量磁盘的 I/O 性能。磁盘有预读页或者文件块的机制,顺序 I/O 比 随机 I/O 要快的多,顺序 I/O 通常不是我们关注的对象。 一般我们对磁盘的操作随机 I/O 的 请求比例更多,如果在应用层把随机 I/O 优化成 顺序 I/O(或者是逻辑上的 顺序 I/O), 那么,应用的响应速度会快很多。 Mysql innodb 存储引擎的 很多优化思想 就是这个逻辑,像索引,undo,redo 日志记录 等,尽可能的把 随机 I/O 变得有序。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统