金宝搏平台:Linux优化之IO子系统监控与调优

金宝搏平台 1

Linux优化之IO子系统

起源

本篇起源于对Kafka的一个问题排查,大致的原因是达到磁盘性能瓶颈。在追踪问题的时候用到iostat
-x这命令,详细示例如下:

1.png

可以看到%idle(%idle小于70%说明IO压力已经比较大了)和%util的值都处于非正常状态。不过这里并不讲述Kafka的问题排查过程,反而是来讲述下IO指标的一些知识。每次遇到需要查看磁盘相关信息的时候,一些指标都会或多或少的遗忘,还要翻阅各种资料了解,故这里对相关的信息做一个相关的整理,在巩固相关知识点的同时也方便以后的查阅。

上面示例中的各个指标的含义分别为:

avg-cpu说明:

%user:在用户级别运行所使用的CPU的百分比。

%nice:带nice值(和进程优先级相关)的用户模式下运行所使用的CPU的百分比。

%system:在系统级别运行所使用CPU的百分比。

%iowait:CPU等待IO完成的时间百分比。(单个iowait指标值偏高并不能说明磁盘存在IO瓶颈,下面会有详述。)

%steal:管理程序维护另一个虚拟处理器时,虚拟CPU的无意识等待时间的百分比。

%idle:CPU空闲时间的百分比。(idle值高,表示CPU较空闲。)

device说明:

rrqm/s:每秒进行merge的读操作数目。即:rmerge/s

wrqm/s:每秒进行merge的写操作数目。即:wmerge/s

r/s:每秒完成的读IO设备的次数。即rio/s

金宝搏平台,w/s:每秒完成的写IO设备的次数。即wio/s

rsec/s:每秒读扇区数。即rsect/s(每个扇区大小为512B。)

wsec/s:每秒写扇区数。即wsect/s

avgrq-sz:平均每次设备IO操作的数据大小(扇区);平均单次IO大小。

avgqu-sz:平均IO队列长度。

await:从请求磁盘操作到系统完成处理,每次请求的平均消耗时间,包括请求队列等待时间;平均IO响应时间(毫秒)。

svctm:平均每次设备IO操作的服务时间(毫秒)。

%util:一秒中有百分之多少的时间用于 I/O 操作,即被io消耗的cpu百分比。

正常情况下svctm应该是小于await值的,而svctm的大小和磁盘性能有关,CPU、内存的负荷也会对svctm值造成影响,过多的请求也会间接的导致svctm值的增加。await值的大小一般取决于svctm的值和IO队列的长度以及IO请求模式,如果scvtm比较接近await,说明IO几乎没有等待时间;如果await远大于svctm,说明IO请求队列太长,IO响应太慢,则需要进行必要优化。

如果%util接近100%,说明产生的IO请求太多,IO系统已经满负荷,该磁盘可能存在瓶颈。

队列长度(avgqu-sz)也可作为衡量系统 I/O 负荷的指标,但由于 avgqu-sz
是按照单位时间的平均值,所以不能反映瞬间的 I/O
泛洪,如果avgqu-sz比较大,则说明有大量IO在等待。

(可以看完下面一节再来回顾这段内容。)

作为服务器主机来讲,最大的两个IO类型 :

相关原理

对于await,
svctm以及%util等,光从概念上来说,比较晦涩,可以通过下图的磁盘IO流程来加深理解:

2.png

(此图来源于遗产流….重新画了一遍)

磁盘IO场景

1.
用户调用标准C库进行IO操作,数据流为:应用程序buffer->C库标准IObuffer->文件系统page
cache->通过具体文件系统到磁盘。

  1. 用户调用文件IO,数据流为:应用程序buffer->文件系统page
    cache->通过具体文件系统到磁盘。

  2. 用户打开文件时使用O_DIRECT,绕过page cache直接读写磁盘。

4.
用户使用类似dd工具,并使用direct参数,绕过系统cache与文件系统直接写磁盘。

发起IO请求请的步骤简析

(以最长链路为例)

写操作:

1.
用户调用fwrite把数据写C库标准IObuffer后就返回,即写操作通常是个异步操作。

2.
数据到C库标准IObuffer后,不会立即刷新到磁盘,会将多次小数据量相邻写操作先缓存起来合并,最终调用write函数一次性写入(或者将大块数据分解多次write调用)page
cache。

  1. 数据到page
    cache后也不会立即刷新到磁盘,内核有pdflush线程在不停的检测脏页,判断是否要写回到磁盘中,如果是则发起磁盘IO请求。

读操作:

  1. 用户调用fread到C库标准IObuffer读取数据,如果成功则返回,否则继续。

  2. 到page cache读取数据,如果成功则返回,否则继续。

3.
发起IO请求,读取到数据后缓存buffer和C库标准IObuffer并返回。可以看出,读操作是同步请求。

IO请求处理

1.
通用块层根据IO请求构造一个或多个bio结构并提交给调度层。bio结构描述对一个磁盘扇区读/写操作。

2.
调度器将bio结构进行排序和合并组织成队列且确保读写操作尽可能理想:将一个或多个进程的读操作合并到一起读,将一个或多个进程的写操作合并到一起写,尽可能变随机为顺序(因为随机读写比顺序读写要慢),读必须优先满足,而写也不能等太久。

IO调度算法

188金宝搏官网,Linux的IO调度器有时也称之为磁盘调度器,工作机制是控制块设备的请求队列,确定队列中那些IO的优先级更高以及何时下发IO到块设备,以此来减少磁盘寻到时间,从而提高系统的吞吐量。

目前Linux共有如下几种IO调度算法:

  1. NOOP

NOOP算法的全写为No
Operation。该算法实现了最最简单的FIFO队列,所有IO请求大致按照先来后到的顺序进行操作。之所以说“大致”,原因是NOOP在FIFO的基础上还做了相邻IO请求的合并,并不是完完全全按照先进先出的规则满足IO请求。

假设有如下的io请求序列:

100,500,101,10,56,1000

NOOP将会按照如下顺序满足:

100(101),500,10,56,1000

2、CFQ

CFQ算法的全写为Completely Fair
Queuing。该算法的特点是按照IO请求的地址进行排序,而不是按照先来后到的顺序来进行响应。

假设有如下的io请求序列:

100,500,101,10,56,1000

CFQ将会按照如下顺序满足:

100,101,500,1000,10,56

CFQ是默认的磁盘调度算法,对于通用服务器来说最好的选择。它视图均匀地分布对IO带宽的访问。CFQ为每个进程单独创建一个队列来管理该进程所产生的请求,也就是说每个进程一个队列,各队列之间的调度使用时间片来调度,以此来保证每个进程都能被很好的分配到IO带宽。IO调度器每次执行一个进程的4次请求。在传统的SAS盘上,磁盘寻道花去了绝大多数的IO响应时间。CFQ的出发点是对IO地址进行排序,以尽量少的磁盘旋转次数来满足尽可能多的IO请求。在CFQ算法下,SAS盘的吞吐量大大提高了。但是相比于NOOP的缺点是,先来的IO请求并不一定能被满足,可能会出现饿死的情况。

3、DEADLINE

DEADLINE在CFQ的基础上,解决了IO请求饿死的极端情况。除了CFQ本身具有的IO排序队列之外,DEADLINE额外分别为读IO和写IO提供了FIFO队列。读FIFO队列的最大等待时间为500ms,写FIFO队列的最大等待时间为5s。FIFO队列内的IO请求优先级要比CFQ队列中的高,而读FIFO队列的优先级又比写FIFO队列的优先级高。优先级可以表示如下:

FIFO(Read) > FIFO(Write) > CFQ

4、ANTICIPATORY

CFQ和DEADLINE考虑的焦点在于满足零散IO请求上。对于连续的IO请求,比如顺序读,并没有做优化。为了满足随机IO和顺序IO混合的场景,Linux还支持ANTICIPATORY调度算法。ANTICIPATORY的在DEADLINE的基础上,为每个读IO都设置了6ms的等待时间窗口。如果在这6ms内OS收到了相邻位置的读IO请求,就可以立即满足。
anticipatory
算法通过增加等待时间来获得更高的性能,假设一个块设备只有一个物理查找磁头(例如一个单独的SATA硬盘),将多个随机的小写入流合并成一个大写入流(相当于给随机读写变顺序读写),使用这个原理来使用读取写入的延时换取最大的读取写入吞吐量.适用于大多数环境,特别是读取写入较多的环境。

不同的磁盘调度算法(以及相应的IO优化手段)对Kafka这类依赖磁盘运转的应用的影响很大,建议根据不同的业务需求来测试选择合适的磁盘调度算法(以后的文章中会有相关的测试介绍)。

查看设备当前的IO调度器:cat
/sys/block/{DEVICE-NAME}/queue/scheduler。其中{DEVICE-NAME}指的是磁盘设备的名称,即文章开头iostat
-x中Device下方的vda,vdb等。

举例:

[root@hidden ~]# cat /sys/block/vda/queue/scheduler

noop anticipatory deadline [cfq]

修改当前的IO调度器: echo {SCHEDULER-NAME} >
/sys/block/{DEVICE-NAME}/queue/scheduler。其中{SCHEDULER-NAME}取值为noop、anticipatory、deadline、cfq其中之一。

举例:

[root@hidden ~]# echo noop > /sys/block/vda/queue/scheduler

[root@hidden ~]# cat /sys/block/vda/queue/scheduler

[noop] anticipatory deadline cfq

以上设置重启之后会失效,如果要想重启后配置仍然生效,需要在内核启动参数中将elevator={SCHEDULER-NAME}写入/boot/grub/menu.lst文件中。在修改这个文件之前最好先备份一份,然后将elevator={SCHEDULER-NAME}添加到文件末尾即可。

1.磁盘IO

巩固iowait

单独拎出iowait来说明是因为很多人对这个指标有一定的误区,包括笔者也经常把iowait和await混淆起来。顾名思义,就是系统因为io导致的进程wait。再深一点讲就是:这时候系统在做IO,导致没有进程在干活,cpu在执行idle进程空转,所以说iowait的产生要满足两个条件,一是进程在等IO,二是等IO时没有进程可运行。

常用的top命令中也有iowait的指标展示(%wa就是%iowait),示例如下:

3.png

对 iowait 常见的误解有两个:1. 误以为 iowait 表示CPU不能工作的时间;2.
误以为 iowait 表示I/O有瓶颈问题。iowait
的首要条件就是CPU空闲,既然空闲当然就可以接受运行任务,只是因为没有进程可以运行,CPU才进入空闲状态的。那为什么没有进程可以运行呢?因为进程都处于休眠状态、在等待某个特定事件:比如等待定时器、或者来自网络的数据、或者键盘输入、或者等待I/O操作完成,等等。iowait的升高并不能证明等待IO进程的数量增多了,也不能证明等待IO的总时间增加了。例如,在CPU繁忙期间发生的I/O,无论IO是多还是少,iowait都不会变;当CPU繁忙程度下降时,有一部分IO落入CPU空闲时间段内,导致iowait升高。再比如,IO的并发度低,iowait就高;IO的并发度高,iowait可能就比较低。所以iowait
所含的信息量非常少,它是一个非常模糊的指标,如果看到 iowait
升高,还需检查I/O量有没有明显增加,相应的一些指标有没有明显增大,应用有没有感觉变慢,如果都没有,就没什么好担心的。

Plus:
可以使用iotop命令来查找引起高iowait对应的进程。查看CPU使用率及负载的一些命令有:top、vmstat、mpstat、uptime等。


PS:消息中间件(Kafka、RabbitMQ)交流可加微信:hiddenzzh
欢迎支持笔者新书:《RabbitMQ实战指南》以及关注微信公众号:Kafka技术专栏。

欢迎关注

2.网络IO

这是我们调整最多的两个部分所在

磁盘IO是如何实现的

在内存调优中,一直在讲到为了加速性能,linux内核一般情况下都会尝试将磁盘上的慢速设备上的文件缓存至内存中,从而达到加速效果;

虚拟内存的概念:

读写都在内存中完成,当某一进程在cpu运行的时候,进程要访问自己地址空间中的某一内存页,当进程需要访问页面中的数据,而这个页面最终是要对应在物理内存中的某个物理页面,而进程只能看到自己的线性地址空间,而这个地址并不存在,一旦访问这个地址,那么会通过MMU(内存管理单元)机制中的存储当前进程的线性地址到物理地址的映射表

由此通过MMU实现对应的地址查询于是得到了其映射的地址,最终进程虽然访问的数据是来自于映射过的地址,这种访问访问我们被称为虚拟地址或虚拟内存

如果由于我们使用交换内存或其他方式有可能这个进程所打开的文件长时间没有被访问,这个文件所对应的内存已经被清出去了,所以使用mmu地址转换后的地址对应的数据在内存中不存在了,这时候会产生页错误,我们也被称为缺页异常

缺页异常

缺页异常分为大异常和小异常:

如果数据不存在使得不得不在磁盘中载入页面文件,这时CPU就会进入内核模式,访问磁盘,每次CPU访问内存就要3个周期,访问磁盘需要N个周期,首先需要定位数据的准确位置,而后定位物理内存中开辟数据空间,最后将数据总线贯通,从而将数据从磁盘转入到内存–blockin

当我们找一个空闲空间,而事实上当进程访问这段数据就需要访问新位置的数据,所以我们要更新这个映射表,明确说明所要访问的逻辑地址所要对应的空间的转换的位置,并且让进程重新发起一次访问,这时需要先查找TLB(缓存缓冲器),再次进行查表

而将磁盘装入内存的过程就会发生IO,如果进程修改了数据,最终数据还需要写到磁盘中去,而写到磁盘中去,过程使得数据比原来的文件更大了

具体是由文件系统模块根据进程发起的请求,内核指挥文件系统模块开辟更多的存储块而后将数据存储,这种过程被称为
block out

#缓冲器负责将之前缓冲过的缓存下来,那么如果N个条目,而缓冲器只能缓存有限的几个,那么命中率可能会很低,如果我们使用大页面的话,那么命中率可以大大提高。

机械硬盘的特性

同一方向的操作是合并起来完成的,而后在这个方向结束之后则是另外一方向的

对硬盘来讲,读写是不同类型的操作,读写是不能同时进行的

 

磁盘是如何操作的

将一个或多个进程的读操作合并到一起读

将一个或多个进程的写操作合并到一起写

所以读写操作是两类不同的操作而且是同一方向合并的

如果是读文件,这个文件一定是来自于磁盘的

如果是写文件,那么写入到内存中,对于进程来讲是已经完成的,那么用户对计算机性能感知是来自于读,因为读一定是与IO相交互

1.读是在同方向合并的

2.写也是需要合并的,而且两者是不同方向的操作

因为在同一方向可以节省很多资源

读必须优先满足,而写也不能等太久,因此必须有一种良好的算法让其尽可能都得到满足,而又不能让用户感到性能下降

因此在IO系统上有个非常重要的模块—IO调度器

IO调度器

用来实现合并同一方向的读写操作并且将读写操作尽可能理想的这种状况,IO调度器本身的完成,最终用户实现写的时候进程级别所看到的数据是文件接口,那么文件接口输出的时候就意味着将磁盘空间以文件接口的方式输出,其需要文件系统,也就意味着进程与磁盘上的数据打交道是依赖文件系统的,所以用户的请求先到文件系统,而文件系统通过内核输出是虚拟文件接口(VFS)
通过VFS找到各特定文件系统相关模块,当然对应的文件是哪个那么则通过vfs转换成什么即可,文件系统将数据接下来之后,最终存储为磁盘块的方式保存在磁盘上,因此这些文件系统最终还要转换数据为磁盘块,所以接下来还要有块层

块层主要是将数据转换为磁盘块格式,而后再由磁盘块格式转换成调度以后存储在磁盘上

如下图所示:

金宝搏平台 1

  • (1)用户进程实现写操作 实现系统调用
  • (2)用户的写操作一定是跟VFS进行交互的
  • (3)VFS需要将其换换为特定的文件系统
  • (4)单个文件在虚拟文件系统存放都会转换成页面方式(page cache)
  • (5)写完之后通过block
    buffer快缓冲(知所以进行缓冲是因为磁盘太慢了,所以写的时候需要缓冲下来)
  • (6)然后由bio将每个page
    cache转换成块,并且在块缓冲这个层次上缓存下来

这就是缓冲队列,而在块层实现缓冲之后每个块最终都要交给块层来处理,块层中最重要的一个组件就是IO调度器,IO调度器接收blockbuffer中所发送过来的多个请求块,这多个请求块需要排序的:同方向合并,图中都是写操作的

至于如何排序,一定是最靠近写请求的最优先满足

而IO调度器主要功能就是将随机IO尽可能合并为顺序IO
本文来自 转载请说明,翻版可耻

但是我们有说过,尽可能同一方向合并尽可能会随机变为顺序,但是我们又不得不读饥饿也不能写饥饿,所以要交替进行的

所以:

(10)由IO调度器调度完成之后,提交给Device Driver ,由Device
Driver控制磁盘控制器,由控制器将电器信号转换为磁信号写入到磁盘中去

为何随机读写比顺序读写要慢:

·随机读写:

我们可能写任意一个磁道的任意一个扇区,那么硬盘磁头可能来回晃动才能完成一次写

·顺序读写:

在一个方向转动即可完成,不用再去移动磁臂的

磁头操作是电磁运动,而磁臂操作是机械运动,所以任何时候随机读写性能都比顺序读写都要差的很多

调度算法

IO调度器事实上是用程序完成的调度算法,对linux来讲,2.6的内核一共有4个

1、CFQ

完全公平队列,比较适合于交互式场景

2、Deadline

最后期限,任何一个读写请求,都有自己的满足期限,当期限到来时之前,必须达到需求的满足(一般建议在数据库服务器上使用此调度算法)

3、anticpatory

预期的,任何一个数据读完之后,有可能与其相邻的数据也可能被读到,所以它大致所实现的方法就是,读完之后先不满足,则不处理,需等一段时间后查看是否有相近数据访问过,如果有马上先满足,所以这只能在行为预估的场景下可用

4、Noop

不排队不合并,先到先得

#像固态硬盘,因为它不是机械硬盘,它的读写就算是随机IO那么它的性能跟顺序IO差别也不是很大,反而如果想让调度器去调取它的算法,那么调度器本身运行会占用很高的CPU的时钟周期,有可能会得不偿失,所以noop在这种场景下是最好的算法

#有些RAID设备控制器在硬件设备上自己就有读写操作排序的,也就意味着在硬件级别排好序之后在操作系统级别会将其打散重新排序,得不偿失,所以RAID设备有自己的调度器的话,最好也使用noop

一般来讲,默认是CFQ的

本文来自 转载请说明,翻版可耻

有时候在不同场景下,他们所最佳所适用的算法可能不一样,比如:

如果是web服务器,这里只是访问放web上的分区的页面数据

如果是db数据库,访问的是db数据库的文件,他们最适用的算法未必会一样,因为他们的访问风格不同,所以这时候我们就需要修改他们的调度器算法

CFQ比较适合于交互式场景,于是在很多时候会将服务器设置为Deadline,当然只是一种假定,具体需要自己测试然后做决定

观测当前磁盘IO活动

一般 ethstatus iotio pt-ioprofile sar等查工具看哪些进程引起的io比较高等

这里我们使用sar来观察其状态信息 本文 来自
转载 请说明,翻版 可耻

例:

[root@node3 ~]# sar -d 1 5
Linux2.6.32-431.20.3.el6.x86_64 (node3.test.com) 09/20/2014 _x86_64_(4 CPU)
09:16:00 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
09:16:01 PM dev252-0 46.46 0.00 46795.96 1007.13 2.65 580.00 2.26 10.51
09:16:01 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
09:16:02 PM dev252-0 3.00 0.00 144.00 48.00 0.00 1.33 1.00 0.30
09:16:02 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
09:16:03 PM dev252-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
09:16:03 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
09:16:04 PM dev252-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
09:16:04 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
09:16:05 PM dev252-0 72.73 0.00 59967.68 824.56 2.61 35.88 1.21 8.79

用参数-p可以打印出sda,hdc等磁盘设备名称,如果不用参数-p,设备节点则有可能是dev8-0,dev22-0

参数解释:

tps:每秒从物理磁盘I/O的次数.多个逻辑请求会被合并为一个I/O磁盘请求,一次传输的大小是不确定的

rd_sec/s:每秒读扇区的次数.

avgrq-sz:平均每次设备I/O操作的数据大小(扇区).

avgqu-sz:磁盘请求队列的平均长度.

await:从请求磁盘操作到系统完成处理,每次请求的平均消耗时间,包括请求队列等待时间,单位是毫秒(1秒=1000毫秒).(一次完成的任务,它的IO完成的平均耗时)

svctm:系统处理每次请求的平均时间,不包括在请求队列中消耗的时间,

%util:I/O请求占CPU的百分比,比率越大,说明越饱

我们通常经验值是:

svctm不超过0.5;

await不超过5;

主要看当前设备

核心要点:

1、tps(iops)越高,但%util越低,说明io能力容量越大

2、await、svctm越低越好,说明io响应延迟很低,iops能力很高

调整buffer,提高性能

无非就是调整队列数,以及增加预读数,下面我们来手动做一下

·增加队列长度

 

格式:

/sys/block/vda(特定某设备)/queue/nr_requests

由于我这里跑的是kvm虚机,所以设备号默认都以vdx开头

默认队列为128个长度

[root@node3 ~]# cat /sys/block/vda/queue/nr_requests

128

这个值是可以调大一点的

2.增加预读数

/sys/block/vda(特定某设备)/queue/read_ahead_kb

表示事先预读数据的kb数,默认也是128

[root@node3 ~]# cat /sys/block/vda/queue/read_ahead_kb

128

这个值也是可以调大的,具体多少自行而定

本文 来自 转载 请说明,翻版 可耻

CFQ完全公平队列

IO调度是在各进程之间平均分配的,主要是根据进程的IO需求来讲IO能力平均分配调度

发表评论

电子邮件地址不会被公开。 必填项已用*标注