Linux优化之IO子系统

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

1.磁盘IO 

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转换成什么即可

文件系统将数据接下来之后,最终存储为磁盘块的方式保存在磁盘上,因此这些文件系统最终还要转换数据为磁盘块,所以接下来还要有块层

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

如下图所示:

Linux优化之IO子系统监控与调优

(1)用户进程实现写操作 实现系统调用

(2)用户的写操作一定是跟VFS进行交互的

(3)VFS需要将其换换为特定的文件系统

(4)单个文件在虚拟文件系统存放都会转换成页面方式(page cache)

(5)写完之后通过block buffer快缓冲(知所以进行缓冲是因为磁盘太慢了,所以写的时候需要缓冲下来)

(6)然后由bio将每个page cache转换成块,并且在块缓冲这个层次上缓存下来

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

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

 

而IO调度器主要功能就是将随机IO尽可能合并为顺序IO  本文来自http://yijiu.blog.51cto.com 转载请说明,翻版可耻

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

所以:

(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的

 本文来自http://yijiu.blog.51cto.com 转载请说明,翻版可耻

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

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

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

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

 

观测当前磁盘IO活动

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

这里我们使用sar来观察其状态信息  本文 来自http://yijiu.blog.51cto.com 转载 请说明,翻版 可耻

例:

[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的百分比,比率越大,说明越饱,一般到95左右可能要引起关注

我们通常经验值是:

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

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

 本文 来自http://yijiu.blog.51cto.com 转载 请说明,翻版 可耻

 

CFQ完全公平队列

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

所以在交互式环境中,这种方式是比较实用的

 

但是在RHEL6.4上 它又提供了三个不同的调度等级:

1.实时 RT

2.最佳效果 BE

3.闲置

 

我们可以使用ionice命令手动分配调度等级,或者使用iopro_set系统调用编程分配,当然涉及到开发层面了

在实时调度等级和最佳效果两个级别都有8个IO等级,

数字越小优先级越高,最佳效果是默认调度等级 也就是4,不用更改

 

修改CFQ,以调节其性能

涉及参数文件:/sys/block/vda/queue/iosched/

修改默认调度器算法:

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

[root@node3 queue]# ls

add_random           hw_sector_size      max_hw_sectors_kb  minimum_io_size  physical_block_size  scheduler

discard_granularity  iosched             max_sectors_kb     nomerges         read_ahead_kb        unpriv_sgio

discard_max_bytes    iostats             max_segments       nr_requests      rotational

discard_zeroes_data  logical_block_size  max_segment_size   optimal_io_size  rq_affinity

而在其上层目录里,有一scheduler文件

查看scheduler文件

[root@node3 queue]# cat scheduler

noop anticipatory deadline [cfq]

因此更改磁盘IO调度器则去找这个目录下所对应的scheduler,注意的是,它只是针对每个磁盘进行调整的,如果有多块磁盘的话则需要对应每个磁盘进行修改

它没有办法使用sysctl进行控制,如果想开机生效,只能写到rc.local 或init脚本中

 

一旦更改调度算法之后,再来查看目录中的文件

[root@node3 queue]# ls /sys/block/vda/queue/iosched/

back_seek_max      fifo_expire_async  group_idle       low_latency  slice_async     slice_idle

back_seek_penalty  fifo_expire_sync   group_isolation  quantum     slice_async_rq  slice_sync

修改算法

[root@node3 queue]# echo deadline > scheduler

[root@node3 queue]# cat scheduler

noop anticipatory [deadline] cfq

再次观察iosched目录,并查看其是否有变化

[root@node3 queue]# ls/sys/block/vda/queue/iosched/

fifo_batch  front_merges read_expire  write_expire  writes_starved

所以我们更改调度算法后,每个调度算法在此目录都有很多可调整参数,每个参数都有值,只不过都表现为其文件内容而已,而每个调度器的值通过修改是可以优化调度器的工作特性的

 

比如对CFQ来讲,有以下几个值可以调整:

back_seek_max

    反向寻道可能有负面影响,负载小的时候可以启用,否则不要使用反向寻道太多值

 

back_seek_penal

    反向寻道做惩罚,如果不得不使用反向寻道的话,那么必须对其做出一定惩罚,一旦做完惩罚之后,必须要正向寻道更多次数

 

fifo_expire_async

    用来控制异步请求等待时间长度,默认是250毫秒,过期之后无法满足的异步请求将会被移动到调度队列中,也就意味着要重新调度。通常这些值不需要调整

 

fifo_expire_sync

    用于同步请求的,

严格来讲写操作都是在内存中完成 过周期之后才会同步至硬盘中,站在计算机角度来说这种操作都被称为异步,而同步则是为了尽可能保证数据会被第一时间写到磁盘上来,数据不会在内存上逗留,直接写入磁盘

 

low_latecy

    低延迟,简单来讲,每个进程都有可能发起读写请求,也就意味着最终满足用户读写请求是按进程为单位划分,在满足这个前提下,需要考虑每个进程都需要得到满足,所以必须关注每个进程发起IO请求之后最多等待多长时间,如果启动此值就意味着每个进程只要发起读写请求都要尽可能快速得到满足,默认就启用了低延迟

    在桌面系统环境,低延迟是非常有必要的

 

quantum

    CFQ一次可以发出的IO请求数,一批最大可以调度的IO数,限制IO队列深度的,简单来说就是定义设备一次可以接收的IO请求的队列长度,默认为8

    增加反而会有负面影响,因此谨慎调整

    如果随机IO请求数非常的多,这个值可以适当调大,如果顺序写非常多,那么不建议调整

 

设置IO允许消耗的时间

一次IO请求的操作,一次执行多久,应该执行多久,按理说硬盘只要是没有损坏,能正常运作,在正常范围内,那么它就应该写完、读完所以我们要定义好每次读写请求所最大允许消耗的时间,那么就是以下几个参数的意义了:

slice_async

    定义异步写入的超时值,每次异步写操作最长时长是多少,默认值为40秒

slice_idle

    等待IO请求的闲置时长

slice_sync

    定义同步写入操作超时值,因为同步比较慢,所以其默认值是100秒,因为是从进程直接到磁盘的,所以超时时间会长一点

在桌面环境和在服务环境下,他们如果都使用CFQ调度器,他们工作特性不一定,也就意味着我们关注其背后工作机制参数也不一样,所以要调整某些值做一些测试的评判

 

 

Deadline最后期限调度

最后期限

Linux优化之IO子系统监控与调优

如图所示,其分为了3个队列,分别是:

·读队列

·写对列

·排序队列

而后这些队列都被整合到派发队列中去而后由磁盘得到满足,我们从中任何一队列中选出一个操作得到满足之前必须要保证这类操作不能超期

简单来讲deadline就是将每个读写操作放到队列的时候都给他一个倒计时的计时器,将倒计时的计时器消耗完之前需要赶紧放到派发队列中,而后再同步至硬盘

而对服务器来讲,这种方式是比较理想的

 

常用可调参数

fifo_batch

    单批发出的读写数,在其最后期限满足之前将队列中的数据拿出并满足,但有写操作是需要排序的;默认为16,设置更多的值会获得更好的流量,但是会增加延迟

    比如一批读为16个,那么我们讲其改为32个,那意味着写的时间会更高

    当然所有都取决于测试数据,无论怎么调都不如换一块SSD硬盘

 

front_merges

    可以将多个请求合并在一起,但是有些请求压根不连续,不可能被合并在一起,那么我们可以禁止在满足IO之前进行合并的,禁止合并有可能会带来随机读写的特性的

    但允许合并也有一定的副作用,就是必须花时间去排序

 

read_expire

    每个读操作必须在多少期限内得到满足,默认为半秒钟

 

write_expire

    每个写操作必须在多久内得到满足,默认为5秒钟

 

#写操作是可以延迟满足的

 

writes_starved

    定义一批可以处理多少个读取批,这个值越高,读的效果就越好。默认为2,意味着2批读,一批写。如果服务器读多写少,内存缓冲足够大,那么可以将其调大

 

Noop

如果系统与cpu绑定,且使用高速存储(SSD),这就是最佳的IO调度程序

只要使用固态硬盘就要将其改为noop

 

如果调参数的话则需要直接去挑战/sys/block/vda/queue下的参数,而不是调度器的参数

 

add_random

    其作用是否使用商池

 

max_sectors_kb

    默认发送到磁盘的最大请求是多少,默认为512kb。我们知道每个扇区是512字节,那么每次发送512kb 意味着发送N个扇区,我们可以调整或增大减小该值,对于固态硬盘来讲不是所谓的扇区概念,由此可调。

    在此类情况下建议将max_hw_sectors_kb降低删除块大小

我们可以使用压力测试工具对其做测试,记录大小从512kb到1MB不等,哪个值的效果好就设置为哪个值,当然压力测试跟实际场景有出入的,所以要充分考虑环境尽可能模拟真实场景,尽可能要模拟随机读写

 

nr_request

    请求的队列的值,可以降低其值

rotational

    如果是SSD硬盘要将此值设置为0,禁用轮转模式

 

rq_affinity

    在触发IO不同的CPU中处理IO,一般来讲在同CPU上处理同IO是最好的

    因为CPU处理的仅仅是中断而已

 

因此对于SSD环境中常用的调整参数有:

max_sectors_kb

nr_requests

optimal_io_size

rotational

 

调整后如何测试性能是否提高

比较常用的硬盘压力测试工具

·aio-stress

·iozone

·fio

 

了解磁盘IO活动状况分析工具

blktrace

 

磁盘IO瓶颈分析工具

blkparse

gnuplot

 

 本文 来自http://yijiu.blog.51cto.com 转载 请说明,翻版 可耻

 

总结:IO优化大致思路

·最好换SSD

·调整raid级别

·选择IO调度器

·根据场景选择合适的文件系统

·配置选定调度器的参数

·优化结果是否理想,则使用工具进行分析

·写在开机启动项里