磁盘

磁盘文件

在分析和磁盘相关的问题时,通常是将其和文件系统同时考虑的,下面不再区分。和磁盘/文件系统相关的指标主要有以下几个,常用的观测工具为 iostat 和 pidstat,前者适用于整个系统,后者可观察具体进程的 I/O。

  • 磁盘 I/O 利用率:是指磁盘处理 I/O 的时间百分比;
  • 磁盘吞吐量:是指每秒的 I/O 请求大小,单位为 KB;
  • I/O 响应时间,是指 I/O 请求从发出到收到响应的间隔,包含在队列中的等待时间和实际处理时间;
  • IOPS(Input/Output Per Second):每秒的 I/O 请求数;
  • I/O 等待队列大小,指的是平均 I/O 队列长度,队列长度越短越好;

如何判断磁盘的指标出现了异常?

  • 当磁盘 I/O 利用率长时间超过 80%,或者响应时间过大(对于 SSD,从 0.0x 毫秒到 1.x 毫秒不等,机械磁盘一般为 5ms~10ms),通常意味着磁盘 I/O 存在性能瓶颈;

  • 如果 %util 很大,而 rkB/s 和 wkB/s 很小,一般是因为存在较多的磁盘随机读写,最好把随机读写优化成顺序读写,(可以通过 strace 或者 blktrace 观察 I/O 是否连续判断是否是顺序的读写行为,随机读写应可关注 IOPS 指标,顺序读写可关注吞吐量指标);

  • 如果 avgqu-sz 比较大,说明有很多 I/O 请求在队列中等待。一般来说,如果单块磁盘的队列长度持续超过 2,一般认为该磁盘存在 I/O 性能问题。

io 请求越大,需要消耗的时间就会越长。对于块设备而言,时间分成 2 个部分:寻道与读或写操作。

注意此处的寻道不能简单地理解成磁盘磁头旋转到指定位置,因为后备块设备可能是 RAID,可能是 SSD,我们理解写入前的准备动作。准备工作完成之后,写入 4K 和写入 128KB,明显写入 128KB 的工作量要更大一些,因此很容易理解随机写入 128KB 给块设备带来的负载要比随机写入 4K 给块设备带来的负载要高一些。

磁盘的性能评价

SAN(Storage Area Network, 存储区域网络)和 NAS 存储(Network Attached Storage,网络附加存储)一般都具备 2 个评价指标:IOPS 和带宽(throughput),两个指标互相独立又相互关联。体现存储系统性能的最主要指标是 IOPS。下面,将介绍一下这两个参数的含义。

IOPS (Input/Output Per Second)即每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一。IOPS 是指单位时间内系统能处理的 IO 请求数量,IO 请求通常为读或写数据操作请求。随机读写频繁的应用,如 OLTP(Online Transaction Processing),IOPS 是关键衡量指标。另一个重要指标是数据吞吐量(Throughput),指单位时间内可以成功传输的数据数量。对于大量顺序读写的应用,如 VOD(Video On Demand),则更关注吞吐量指标。

每秒 IO 吞吐量= IOPS* 平均 IO SIZE。从公式可以看出:IO SIZE 越大,IOPS 越高,那么每秒 IO 的吞吐量就越高。因此,我们会认为 IOPS 和吞吐量的数值越高越好。实际上,对于一个磁盘来讲,这两个参数均有其最大值,而且这两个参数也存在着一定的关系。

IOPS 可细分为如下几个指标:

  • Toatal IOPS,混合读写和顺序随机 IO 负载情况下的磁盘 IOPS,这个与实际 IO 情况最为相符,大多数应用关注此指标。
  • Random Read IOPS,100%随机读负载情况下的 IOPS。
  • Random Write IOPS,100%随机写负载情况下的 IOPS。
  • Sequential Read IOPS,100%顺序读负载情况下的 IOPS。
  • Sequential Write IOPS,100%顺序写负载情况下的 IOPS。

IOPS 计算公式

对于磁盘来说一个完整的 IO 操作是这样进行的:当控制器对磁盘发出一个 IO 操作命令的时候,磁盘的驱动臂(Actuator Arm)带读写磁头(Head)离开着陆区(Landing Zone,位于内圈没有数据的区域),移动到要操作的初始数据块所在的磁道(Track)的正上方,这个过程被称为寻址(Seeking),对应消耗的时间被称为寻址时间(Seek Time);但是找到对应磁道还不能马上读取数据,这时候磁头要等到磁盘盘片(Platter)旋转到初始数据块所在的扇区(Sector)落在读写磁头正上方的之后才能开始读取数据,在这个等待盘片旋转到可操作扇区的过程中消耗的时间称为旋转延时(Rotational Delay);接下来就随着盘片的旋转,磁头不断的读/写相应的数据块,直到完成这次 IO 所需要操作的全部数据,这个过程称为数据传送(Data Transfer),对应的时间称为传送时间(Transfer Time)。完成这三个步骤之后一次 IO 操作也就完成了。

在我们看硬盘厂商的宣传单的时候我们经常能看到 3 个参数,分别是平均寻址时间、盘片旋转速度以及最大传送速度,这三个参数就可以提供给我们计算上述三个步骤的时间。

第一个寻址时间,考虑到被读写的数据可能在磁盘的任意一个磁道,既有可能在磁盘的最内圈(寻址时间最短),也可能在磁盘的最外圈(寻址时间最长),所以在计算中我们只考虑平均寻址时间,也就是磁盘参数中标明的那个平均寻址时间,这里就采用当前最多的 10krmp 硬盘的 5ms。

第二个旋转延时,和寻址一样,当磁头定位到磁道之后有可能正好在要读写扇区之上,这时候是不需要额外额延时就可以立刻读写到数据,但是最坏的情况确实要磁盘旋转整整一圈之后磁头才能读取到数据,所以这里我们也考虑的是平均旋转延时,对于 10krpm 的磁盘就是(60s/10k)*(1/2) = 2ms。

第三个传送时间,磁盘参数提供我们的最大的传输速度,当然要达到这种速度是很有难度的,但是这个速度却是磁盘纯读写磁盘的速度,因此只要给定了单次 IO 的大小,我们就知道磁盘需要花费多少时间在数据传送上,这个时间就是 IO Chunk Size / Max Transfer Rate。

现在我们就可以得出这样的计算单次 IO 时间的公式。

IO Time = Seek Time + 60 sec/Rotational Speed/2 + IO Chunk Size/Transfer Rate

于是我们可以这样计算出 IOPS。

IOPS = 1/IO Time = 1/(Seek Time + 60 sec/Rotational Speed/2 + IO Chunk Size/Transfer Rate)

对于给定不同的 IO 大小我们可以得出下面的一系列的数据

4K (1/7.1 ms = 140 IOPS)    5ms + (60sec/15000RPM/2) + 4K/40MB = 5 + 2 + 0.1 = 7.1    8k (1/7.2 ms = 139 IOPS)    5ms + (60sec/15000RPM/2) + 8K/40MB = 5 + 2 + 0.2 = 7.2    16K (1/7.4 ms = 135 IOPS)    5ms + (60sec/15000RPM/2) + 16K/40MB = 5 + 2 + 0.4 = 7.4    32K (1/7.8 ms = 128 IOPS)    5ms + (60sec/15000RPM/2) + 32K/40MB = 5 + 2 + 0.8 = 7.8    64K (1/8.6 ms = 116 IOPS)    5ms + (60sec/15000RPM/2) + 64K/40MB = 5 + 2 + 1.6 = 8.6

从上面的数据可以看出,当单次 IO 越小的时候,单次 IO 所耗费的时间也越少,相应的 IOPS 也就越大。

上面我们的数据都是在一个比较理想的假设下得出来的,这里的理想的情况就是磁盘要花费平均大小的寻址时间和平均的旋转延时,这个假设其实是比较符合我们实际情况中的随机读写,在随机读写中,每次 IO 操作的寻址时间和旋转延时都不能忽略不计,有了这两个时间的存在也就限制了 IOPS 的大小。现在我们考虑一种相对极端的顺序读写操作,比如说在读取一个很大的存储连续分布在磁盘的的文件,因为文件的存储的分布是连续的,磁头在完成一个读 IO 操作之后,不需要从新的寻址,也不需要旋转延时,在这种情况下我们能到一个很大的 IOPS 值,如下。

4K (1/0.1 ms = 10000 IOPS)    0ms + 0ms + 4K/40MB = 0.1    8k (1/0.2 ms = 5000 IOPS)    0ms + 0ms + 8K/40MB = 0.2    16K (1/0.4 ms = 2500 IOPS)    0ms + 0ms + 16K/40MB = 0.4    32K (1/0.8 ms = 1250 IOPS)    0ms + 0ms + 32K/40MB = 0.8    64K (1/1.6 ms = 625 IOPS)    0ms + 0ms + 64K/40MB = 1.6

相比第一组数据来说差距是非常的大的,因此当我们要用 IOPS 来衡量一个 IO 系统的系能的时候我们一定要说清楚是在什么情况的 IOPS,也就是要说明读写的方式以及单次 IO 的大小,当然在实际当中,特别是在 OLTP 的系统的,随机的小 IO 的读写是最有说服力的。

另外,对于同一个磁盘(或者 LUN),随着每次 IO 读写数据的大小不通,IOPS 的数值也不是固定不变的。例如,每次 IO 写入或者读出的都是连续的大数据块,此时 IOPS 相对会低一些;在不频繁换道的情况下,每次写入或者读出的数据块小,相对来讲 IOPS 就会高一些。也就是说,IOPS 也取决与 IO 块的大小,采用不同 IO 块的大小测出的 IOPS 值是不同的。对一个具体的 IOPS, 可以了解它当时测试的 IO 块的尺寸。并且 IOPS 都具有极限值,表 1 列出了各种磁盘的 IOPS 极限值。

问题排查

  • 用工具输出磁盘相关的输出的指标,常用的有 %wa(iowait)、%util,根据输判断磁盘 I/O 是否存在异常,譬如 %util 这个指标较高,说明有较重的 I/O 行为;

  • 使用 pidstat 定位到具体进程,关注下读或写的数据大小和速率;

  • 使用 lsof + 进程号,可查看该异常进程打开的文件列表(含目录、块设备、动态库、网络套接字等),结合业务代码,一般可定位到 I/O 的来源,如果需要具体分析,还可以使用 perf 等工具进行 trace 定位 I/O 源头。

需要注意的是,%wa(iowait)的升高不代表一定意味着磁盘 I/O 存在瓶颈,这是数值代表 CPU 上 I/O 操作的时间占用的百分比,如果应用进程的在这段时间内的主要活动就是 I/O,那么也是正常的。

网络 I/O 存在瓶颈,可能的原因如下:

  • 一次传输的对象过大,可能会导致请求响应慢,同时 GC 频繁;

  • 网络 I/O 模型选择不合理,导致应用整体 QPS 较低,响应时间长;

  • RPC 调用的线程池设置不合理。可使用 jstack 统计线程数的分布,如果处于 TIMED_WAITING 或 WAITING 状态的线程较多,则需要重点关注。举例:数据库连接池不够用,体现在线程栈上就是很多线程在竞争一把连接池的锁;

  • RPC 调用超时时间设置不合理,造成请求失败较多;

Java 应用的线程堆栈快照非常有用,除了上面提到的用于排查线程池配置不合理的问题,其他的一些场景,如 CPU 飙高、应用响应较慢等,都可以先从线程堆栈入手。