这篇文章中,将会介绍Linux上常见的IO Scheduler,以及如何修改Linux上面的默认的IO Scheduler。在翻译的过程中,对原文的内容有所删减,有些地方也重写排列了一下顺序。
原文链接: Improving Linux System Performance with I/O Scheduler Tuning
什么是IO调度器?
磁盘的访问速度,一直都是计算机的瓶颈。尽管现在固态硬盘的出现,缓解了一下这个尴尬的状况。但是,磁盘的访问速度依然是系统中的瓶颈。
对于机械硬盘来说,当我们需要访问硬盘上的数据的时候,首先需要经过一个被称为寻道时间的过程,即,将盘片旋转到特定的扇区,然后由磁头来读取数据。一般来说,这个寻道时间会占硬盘访问时间中的很大比重。
IO调度器就是为了优化这个过程,而工作的。IO调度器,一般是将访问磁盘上相邻数据的请求,放在一起处理,进而减少无谓的寻道时间,提高速度。
在Linux系统上,已经存在了好多个IO调度器。它们都有各自的特色。在这篇文章剩下的部分里,我们将会依次介绍这几个IO调度器,并比较他们的性能。
修改IO调度器
在本文中,我们使用Ubuntu来做实验,因为在Ubuntu中,我们既可以在运行时修改IO调度器,也可以在启动时就修改IO调度器。
如果想要在运行时修改IO调度器,只需要修改一个位于/sys目录中的文件的值,就可以了。
如果想要在启动时修改IO调度器,那么需要在启动时,修改GRUB项。
在修改IO调度器之前,我们先查看一下当前系统中的IO调度器。通过读取/sys/block/
# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
上面的输出,说明sda这个磁盘的IO调度器为deadline.
尽管IO调度器是内核级别的参数,但是,可以分别为不同的磁盘设置不同的IO调度器。如果我们修改sda的IO调度器,sda上面的全部的文件系统都会使用这个新的IO调度器。
正如性能调优的一般步骤那样,我们需要先了解我们要进行调优的环境,然后根据测试结果选择一个合适的IO调度器。
在运行时修改IO调度器
只需要修改/sys/block/
# echo "cfq" > /sys/block/sda/queue/scheduler
# cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
从上面的输出结果中我们可以看到,IO调度器被马上替换为CFQ。这也就意味着,我们并不需要重启我们的PostgreSQL实例或者其他的服务来进行测试。
启动时修改IO调度器
编辑/etc/default/grub,找到GRUB_CMDLINE_LINUX这一项。将它修改成下面这样,告诉它使用noop这个IO调度器。
GRUB_CMDLINE_LINUX="elevator=noop"
然后,运行update-grub2命令来使设置生效。
# update-grub2
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.4.0-62-generic
Found initrd image: /boot/initrd.img-4.4.0-62-generic
Found linux image: /boot/vmlinuz-4.4.0-57-generic
Found initrd image: /boot/initrd.img-4.4.0-57-generic
done
通过reboot命令重启系统之后,我们可以查看我们的设置是否生效。
# cat /sys/block/sda/queue/scheduler
[noop] deadline cfq
测试PostgreSQL在不同IO调度器下的性能
CFQ
CFQ的全称是Complete Fairness Queueing,它的工作原理是这样的,它先创建一个IO请求队列,然后在对队列中的请求根据上文我们说到的那种方式,即将要访问磁盘上相邻数据的请求放到一起,按照这种方式进行排序之后,再依次处理IO请求队列中的请求。
我们可以看到,CFQ实际上并没有区分不同优先级的IO请求。这也就意味着,在某些需要优先处理某些请求的场合,它并不适合。
在理解了CFQ之后,我们通过pgbench来测试我们的PostgreSQL实例。
# su - postgres
$ pgbench -c 100 -j 2 -t 1000 example
starting vacuum...end.
transaction type: TPC-B (sort of)
scaling factor: 50
query mode: simple
number of clients: 100
number of threads: 2
number of transactions per client: 1000
number of transactions actually processed: 100000/100000
latency average: 60.823 ms
tps = 1644.104024 (including connections establishing)
tps = 1644.228715 (excluding connections establishing)
从结果中,我们可以看到,现在的tps基本上是1644/s。尽管不是一个非常差的结果,但是这个结果并不是最优的。
Deadline
Deadline调度器,会创建两个队列,一个读队列,一个写队列。并且每个IO请求都会有一个与其相关联的过期时间戳。
当Deadline调度器在处理IO请求的时候,它会通过过期时间戳来对IO请求进行优先级排序。那些将要过期的IO请求会被赋予更高的优先级。
默认情况下,Deadline为读请求设置的过期时间为500ms,而为写请求设置的过期时间则为5000ms。我们可以看到,Deadline实际上比较适合于那种读请求比写请求多好多的场景中。
我们将IO调度器修改成Deadline,然后查看PostgreSQL的性能。
# echo deadline > /sys/block/sda/queue/scheduler
# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
现在我们已经将IO调度器修改成了Deadline,我们再次测试系统的性能:
# su - postgres
$ pgbench -c 100 -j 2 -t 1000 example
starting vacuum...end.
transaction type: TPC-B (sort of)
scaling factor: 50
query mode: simple
number of clients: 100
number of threads: 2
number of transactions per client: 1000
number of transactions actually processed: 100000/100000
latency average: 46.700 ms
tps = 2141.318132 (including connections establishing)
tps = 2141.489076 (excluding connections establishing)
我们可以看到,相对与CFQ,TPS有接近500/s的提升。
我们同样可以看到,尽管pgbench在测试时,基本上按照1:1的比例生成读写请求,但是依旧可以从Deadline调度器中受益。
Noop
Noop调度器是一个非常特殊的调度器。它并不会对特殊请求进行优先处理,与此相反,它将全部的IO请求都放到一个FIFO队列中。但是,它还会对相似的请求进行合并。
Noop调度器是为了那些实际上并不需要任何IO调度器的系统而进行优化的。比如,在那些虚拟机实例中,实际的IO请求都是由宿主机处理的。
在虚拟机场景中,因为宿主机上,实际上已经有了IO调度器了。所以,在这种场景中,每个IO请求都会被传递到两个IO调度器中,一个是VM的调度器,一个是宿主机上的IO调度器。
# echo noop > /sys/block/sda/queue/scheduler
# cat /sys/block/sda/queue/scheduler
[noop] deadline cfq
# su - postgres
$ pgbench -c 100 -j 2 -t 1000 example
starting vacuum...end.
transaction type: TPC-B (sort of)
scaling factor: 50
query mode: simple
number of clients: 100
number of threads: 2
number of transactions per client: 1000
number of transactions actually processed: 100000/100000
latency average: 46.364 ms
tps = 2156.838618 (including connections establishing)
tps = 2157.102989 (excluding connections establishing)
从结果中,我们可以看到,仅仅只是比Deadline调度器的结果好一点点。
由于我们的这次测试是在虚拟机中进行的,所以,尽管修改了虚拟机上面的IO调度器,实际上起作用的还是宿主机上的IO调度器。