iostat之背锅侠util

简介: 即使在空载的情况下也会偶尔出现明明没什么IO但是看到的%util已经接近100%了,而传统意义上大多数同学会拿util的值来判断这个磁盘是否已经达到了性能瓶颈,这种情况真的是磁盘达到瓶颈了吗?



网络异常,图片无法展示
|



开局先上一张图,某用户反馈即使在空载的情况下也会偶尔出现上图所示,明明没什么IO但是看到的%util已经接近100%了,而传统意义上大多数同学会拿util的值来判断这个磁盘是否已经达到了性能瓶颈。所以经常会有客户怀疑是我们云盘有性能问题。


但是实际上只要压测一下云盘就可以知道性能是达标的,那为什么会出现这个问题呢,util到底是什么?可以代表什么呢?从iostat的源码中摘出如下逻辑:



网络异常,图片无法展示
|





网络异常,图片无法展示
|





我们iostat计算util时的tot_ticks其实就是这里的io_ticks,所以计算方法可以总结为:

util=[Δio_ticks/Δt]

所以下一步我们要搞清楚的是这个io_tick是怎么来的才能理清楚这个公式是什么意思。

从代码中可以看到iostat计算的所有数据都来自 /proc/diskstats , 所以我们应该先搞清楚这里面都有些什么东西:



网络异常,图片无法展示
|



以下解释来自( https://www.kernel.org/doc/Documentation/iostats.txt):

  1. (rd_ios)读操作的次数。
  2. (rd_merges)合并读操作的次数。如果两个读操作读取相邻的数据块时,可以被合并成一个,以提高效率。合并的操作通常是I/O scheduler(也叫elevator)负责的。
  3. (rd_sectors)读取的扇区数量。
  4. (rd_ticks)读操作消耗的时间(以毫秒为单位)。每个读操作从__make_request()开始计时,到end_that_request_last()为止,包括了在队列中等待的时间。
  5. (wr_ios)写操作的次数。
  6. (wr_merges)合并写操作的次数。
  7. (wr_sectors)写入的扇区数量。
  8. (wr_ticks)写操作消耗的时间(以毫秒为单位)。
  9. (in_flight)当前未完成的I/O数量。在I/O请求进入队列时该值加1,在I/O结束时该值减1。 注意:是I/O请求进入队列时,而不是提交给硬盘设备时。
  10. (io_ticks)该设备用于处理I/O的自然时间(wall-clock time)。 请注意io_ticks与rd_ticks(字段#4)和wr_ticks(字段#8)的区别,rd_ticks和wr_ticks是把每一个I/O所消耗的时间累加在一起,因为硬盘设备通常可以并行处理多个I/O,所以rd_ticks和wr_ticks往往会比自然时间大。而io_ticks表示该设备有I/O(即非空闲)的时间,不考虑I/O有多少,只考虑有没有。在实际计算时,字段#9(in_flight)不为零的时候io_ticks保持计时,字段#9(in_flight)为零的时候io_ticks停止计时。
  11. (time_in_queue)对字段#10(io_ticks)的加权值。字段#10(io_ticks)是自然时间,不考虑当前有几个I/O,而time_in_queue是用当前的I/O数量(即字段#9 in-flight)乘以自然时间。虽然该字段的名称是time_in_queue,但并不真的只是在队列中的时间,其中还包含了硬盘处理I/O的时间。iostat在计算avgqu-sz时会用到这个字段。



   上面大部分字段都是直译的很容易理解的,稍微难理解的在于io_ticks。明明已经有了rd_ticks和wr_ticks 为什么还需一个io_ticks。这里需要搞清楚rd_ticks和wr_ticks是把每一个IO消耗时间累加起来,但是硬盘设备发展到现如今早就可以并行处理多个IO,因此,rd_ticks和wr_ticks之和很可能会比自然时间(wall-clock time)要大甚至大很多,这显然不符合我们正常的预期。所以引入了相对纯粹的io_ticks, 它并不关心队列中有多少个IO在排队,它只关心设备有IO的时间。即不考虑IO有多少,只考虑IO有没有。在实际运算中,in_flight不是0的时候保持计时,而in_flight 等于0的时候,时间不累加到io_ticks。

搞清楚这个再回去看上面的公式就很清楚了,util指的是采样周期内in_flight不为0的时间相对于总时间的占空比。

   但是这又带来了另外一个问题,util这个指标本意是用来表示磁盘当前的负载或者说磁盘性能的使用率,按照上面的定义计算的方法显然是有问题的,例如我有个盘处理1个I/O需要的时间是1ms,有能力并行处理10个I/O(有10个IO队列),一共有1000个I/O需要处理,如果我每次合并I/O数都是10那么总处理时间应该是100ms,util计算结果100/1000=10%,但是如果我把它串行起来一次只下发一个,那么总完成时间就是1000ms,util计算结果就1000/1000=100%,所以这个值在一定意义上取决于内核如何合并优化IO请求或者也取决于上层应用读写的离散程度,从这个角度来说util已经没有什么实际意义了。

   另一种更直观的场景,我上层发起IO的对象肯定是不唯一的,依然假设处理一条IO的时间为100ms,磁盘有10个队列,0-0.1s 在第一个队列中有IO,0.1-0.2s在第二个队列中有IO,依此类推如图,这样的话整个1s中in_flight始终不为0,所以计算得到的util = 100%,但是这显然不符合时间情况。 PS:svctm:已被废弃的指标,没什么意义,svctm=[util/tput]



网络异常,图片无法展示
|



   貌似一般这种问题到这里就可以收尾了,但是奈何客户有大量对比环境发现有一些是相对正常的,而有一些环境是明显有上述异常的且都是同版本系统空载情况下看到的,这样以来就不好解释了因为变化的因子只有云盘和后端的虚拟化环境了。

想要进一步排查的话就得知道到底是谁在更新io_ticks,我们看一下头问genhd.h,/proc/diskstats里的值主要在disk_stats这个结构体里维护,另有部分来自hd_struct:



网络异常,图片无法展示
|



我们读/proc/diskstats是由block/genhd.c中的diskstats_show处理的:



网络异常,图片无法展示
|



1

struct disk_stats {
        unsigned long sectors[2];       /* READs and WRITEs */
        unsigned long ios[2];
        unsigned long merges[2];
        unsigned long ticks[2];
        unsigned long io_ticks;
        unsigned long time_in_queue;
};

上面这些字段都是在内核处理IO请求的过程中实时更新的,目前发现主要在以下几个流中更新:part_round_stats



网络异常,图片无法展示
|



blk_account_io_start() 和blk_account_io_done()



网络异常,图片无法展示
|



  磁盘分区被remove之后依然有IO落到这个分区,这个场景不太清楚,看起来一般不会走到这里~



网络异常,图片无法展示
|



所以正常应该是在IO done的时候在更新一次。从上面看,io_ticks是实际影响util和svctm的核心指标,重新总结下我们遇到的问题:

  1. 表现为只要有IO,util就非常容易打到90%以上甚至100%
  2. 可以从截图中看到,裸盘的IO ticks明显比分区vda1大很多,而系统盘本来就只有一个分区,读写都是基于文件系统也就是vda1,所以理论上vda和vda1的ticks应该相等才对(至少系统盘挂载上来之后计数就应该相等了)



网络异常,图片无法展示
|

  1. 内核版本3.10.0-957-21.3

从上面分析看影响io_ticks计数可能的因素:

内核跨版本差异

磁盘多队列处理

磁盘调度算法

IO merge

后面实际验证中发现这是特定版本的问题,触发条件为内核版本3.10.0-957-21.3以及磁盘队列大于等于2时触发。所以实际只需要升级下内核就可以解决了。

PS:

额外的场景:complete中断被绑定到了比较繁忙的CPU上,实际表现为同等IO负载下,util比正常环境大很多,但是非持续100%,可以修改中断亲和性



网络异常,图片无法展示
|



echo 40 > /proc/irq/51/smp_affinity

echo 40 > /proc/irq/52/smp_affinity



目录
相关文章
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
2月前
|
Java
Java面试题之cpu占用率100%,进行定位和解决
这篇文章介绍了如何定位和解决Java服务中CPU占用率过高的问题,包括使用top命令找到高CPU占用的进程和线程,以及使用jstack工具获取堆栈信息来确定问题代码位置的步骤。
158 0
Java面试题之cpu占用率100%,进行定位和解决
|
4月前
|
存储 Java 编译器
刷完一千道java笔试题的常见题目分析
这篇文章是关于刷完一千道Java笔试题后的常见题目分析,涵盖了Java基础知识点,如标识符命名规则、抽象类与接口的区别、String类的equals方法、try-catch-finally块的执行逻辑、类与实例方法的区别、this与super关键字的用法、面向对象的基本概念、重写与重载的原则等,并建议结合JVM内存结构图加深理解。
刷完一千道java笔试题的常见题目分析
|
6月前
|
Java 开发者
揭秘!LinkedList是如何华丽变身成为Java队列之王的?
【6月更文挑战第18天】Java的`LinkedList`既是列表也是队列之星,实现`Queue`接口,支持FIFO操作。其内部的双向链表结构确保了添加/移除元素的高效性(O(1)),适合作为队列使用。它线程不安全,但可通过同步包装用于多线程环境。此外,`LinkedList`还能灵活变身栈或双端队列,提供多种数据结构功能。
69 11
《Java-SE-第三十章》之哲学家就餐问题
《Java-SE-第三十章》之哲学家就餐问题
113 0
|
存储 算法 Java
Java代码是如何被CPU狂飙起来的?
无论是刚刚入门Java的新手还是已经工作了的老司机,恐怕都不容易把Java代码如何一步步被CPU执行起来这个问题完全讲清楚。但是对于一个Java程序员来说写了那么久的代码,我们总要搞清楚自己写的Java代码到底是怎么运行起来的。另外在求职面试的时候这个问题也常常会聊到,面试官主要想通过它考察求职同学对于Java以及计算机基础技术体系的理解程度,看似简单的问题实际上囊括了JVM运行原理、操作系统以及CPU运行原理等多方面的技术知识点。我们一起来看看Java代码到底是怎么被运行起来的。
6376 7
Java代码是如何被CPU狂飙起来的?
|
监控 Java Linux
每日一面 - Java OOM都有哪些,说出几种?(下)
每日一面 - Java OOM都有哪些,说出几种?(下)
每日一面 - Java OOM都有哪些,说出几种?(下)
|
Java 编译器 Linux
每日一面 - Java OOM都有哪些,说出几种?(上)
每日一面 - Java OOM都有哪些,说出几种?(上)
每日一面 - Java OOM都有哪些,说出几种?(上)
|
消息中间件 缓存 网络协议
如果有人再问你 Java IO,把这篇文章砸他头上(二)
说到 I/O,想必大家都不会陌生, I/O 英语全称:Input/Output,即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出。
如果有人再问你 Java IO,把这篇文章砸他头上(二)