IO调度算法探索

勿忘初心2018-10-24 11:26

此文已由作者顾贤杰授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


一、前言


每天和服务器打交道,经常遇到各种问题。在这些问题中很大一块,都和IO有关系。学过计算机的,在使用计算机多了后,会发现在实际使用的时候,我们最难扩展的计算资源莫过于IO资源。很多时候计算资源不够,可以搞分布式MR。但是当IO不够(网卡IO,硬盘IO,内存IO等)的时候,我们经常缺少有效的直接的手段去解决这个问题。相对于CPU计算速度的提升,IO速度提升一直都相对缓慢。因此操作系统的设计者们为了缓解IO压力,尤其是硬盘IO压力,做了很多的研究和实践工作。


因为文件系统IO很多时候非常的底层,除了内核开发人员,很少有人去认真地关注IO的具体情况。因此在这篇文章中,我主要介绍的是Linux文件系统IO,希望能以一个系统运维工程师的角度为大家分析一下当前Linux文件系统中的IO算法的特性。


二、关于Linux IO调度


很多人都知道随机读写和顺序读写在数据吞吐量上有很大的区别。这个是因为随机读写和顺序读写在IO寻址次数和系统调用次数上有很大的不同。


在一般情况下,服务器访问机械硬盘,数据存取的消耗主要是磁盘的寻道时间。当系统上的IO读取非常随机的时候,通常会看到磁盘的IO速度会直线下降。这是因为数据太过于随机,导致读写的消耗大部分都用在磁盘寻道上,真正用于IO的时间变少了。(SSD虽然没有了磁盘寻道,但是也需要寻址操作。)我们希望服务器上的IO速度能更快,但是现实情况是寻址操作非常消耗时间。这个时候Linux的文件系统IO调度器就派上了用场。IO调度器的主要任务为管理设备请求队列和分配资源给请求,最主要的工作方式就是通过对IO请求做合并和排序。合并针对相同硬盘区域的操作;排序针对读写块大小和地址,以达到最大IO速度。最终的目的是减少系统调用开销和寻址次数。当系统中的一个程序多次调用写操作的时候,如果没有IO调度,最可能的情况是多次寻址读写,而且程序本身也时常处于等待IO的状态。


在我们当前的服务器系统中,我们会有3个IO调度算法可以选择。它们分别是:NOOP,Deadline,CFQ。


1. AS调度算法


在说当前的内核调度算法前,我们可以先回顾一下内核2.6.18前的IO调度算法。在2.6.18前,系统默认的IO调度算法是AS(Anticipatory scheduling )。这个调度算法的特点是基于预测算法,目的是为了解决Deceptive idleness问题。在实际工作的时候,它处理完一个请求,不直接处理下一个请求,而是等待上一个请求的进程片刻(默认等6ms),如果该进程发出和当前扇区相同或者相邻扇区的请求的时候,优先处理。可以想象当系统中有大量相邻扇区操作的时候,性能会非常好。


2. CFQ调度算法


而在2.6.18后,内核的默认调度算法变为CFQ。这个算法的特点是:


  • 每个提交IO请求的进程都有一个自己单独的sorted queue
  • 在每个时间片中,CFQ会选择一个进程去处理它的IO队列
  • 这个选择过程,不是简单的轮询,而是基于进程优先级红黑树选择)
  • 保证各个进程的IO都能均衡地被处理
  • 有类似AS算法的等待机制


大家可以想象这样的一个场景,当大量进程同时请求IO的时候,有些进程的IO队列特别长。这个时候会有2种情况:


1.一直只处理高优先级的进程IO,低优先级的进程基本IO饿死


2.相同优先级的进程IO处理的时候,IO队列长的进程会长时间处于IO wait状态(单个时间片无法处理完,下次处理继续寻址)


默认情况下CFQ调度算法,系统中有多个IO controller或者LUNs,如果IO在这些控制器之间比较均衡,性能会非常好。但是当系统中有很多进程同时对一个IO controller做操作的时候,因为进程间独立的IO队列,无法合并进程间的相同扇区的IO请求,就很可能会导致频繁的寻址操作。


3. Deadline调度算法


在实际使用的时候,我们的系统经常会同时运行多个大读写IO的进程。我们使用发现CFQ调度,会遇到IO请求超时非常严重。Deadline算法因为其特殊的设计,对这样的场景会变得更加适合。


Deadline算法的特点是:


  • IO请求带超时的算法
  • 有3个队列,read queue,write queue,sorted queue
  • 请求同时插入到read/write/sorted队列


read queue和write queue按照IO过期时间排序,sorted queue(包含read/write请求)按照磁盘sector排序。Deadline算法中read queue的优先级较高,这个是因为一般情况下进程的写可以缓存到内存,而读操作是block操作。当一个写请求饥饿次数到一定程度的时候,会被立即处理,能有效的避免写饥饿。在多进程IO的环境中,因为Deadline算法的特性,进程间的IO读写序列能被优化(一个Nginx进程的日志写操作和一个tailfile进程的读操作就可能被安排到sorted queue队列中),降低进程切换导致的IO寻址时间。


Deadline算法看上去很好,但是当我们的系统中有大量顺序请求的时候,很有可能的情况是请求无法被很好地排序。比如说2个程序同时做大量顺序请求的时候,在队列中会呈现交替排序现象,会导致IO寻址不断地在2个程序间切换。


4. NOOP算法


NOOP算法是一种针对SSD优化的算法。因为在SSD中,随机找一个地址和顺序找一个地址在时间消耗上是一样的,因此这个算法对IO操作只做合并。所有的IO请求都在一个FIFO队列中,只做简单的IO请求合并。因此这个算法的使用场景比较有限。



5. 实际场景


在说完以上这些调度程序的内在原理,结合实际我们来看看我们线上的使用场景。


  • 数据归档业务 — 大量顺序请求,CFQ调度够用(程序不多的情况下Deadline无明显优势,可能会有劣势)
  • 云计算业务 — 大量进程同时读写单个控制器,适合Deadline调度
  • 跑在SSD上的业务 — 寻址不再是瓶颈,可以考虑NOOP调度,节省CPU对IO排序的消耗。


三、总结


在线上云环境一般情况下我们推荐使用Deadline算法。但是当使用SSD硬盘的时候,我们可以使用NOOP算法。在服务器只做大文件创建归档的时候,我们选择CFQ算法。



网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击