技术解析
Hi ,我是 Zorro 。这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦。
另外,我的其他联系方式:
Email: [email protected]
QQ: 30007147
本文PDF
今天我们来谈谈:
跟内存管理那部分复杂度类似, IO 的资源隔离要讲清楚也是比较麻烦的。这部分内容都是这样,配置起来简单,但是要理解清楚确没那么简单。这次是跟 Linux 内核的 IO 实现有关系。对于 IO 的速度限制,实现思路跟 CPU 和内存都不一样。 CPU 是针对进程占用时间的比例限制,内存是空间限制,而当我们讨论 IO 资源隔离的时候,实际上有两个资源需要考虑,一个是空间,另一个是速度。对于空间来说,这个很简单,大不了分区就是了。现实手段中,分区、 LVM 、磁盘配额、目录配额等等,不同的分区管理方式,不同的文件系统都给出了很多不同的解决方案。所以,空间的限制实际上不是 cgroup 要解决的问题,那就是说,我们在这里要解决的问题是:如何进行 IO 数据传输的速度限制。
限速这件事情,现实中有很多模型、算法去解决这个问题。比如,如果我们想控制高速公路上的汽车单位时间通过率,就让收费站每隔固定时间周期只允许通过固定个数的车就好了。这是一种非常有效的控制手段--漏斗算法。现实中这种算法可能在特定情况下会造成资源浪费以及用户的体验不好,于是又演化出令牌桶算法。这里我们不去详细分析这些算法,但是我们要知道,对 io 的限速基本是一个漏斗算法的限速效果。无论如何,这种限速都要有个“收费站”这样的设施来执行限速,那么对于 Linux 的 IO 体系来说,这个”收费站”建在哪里呢?于是我们就必须先来了解一下:
Linux 的 IO 体系是个层级还算清晰的结构,它基本上分成了如图示这样几层:
Linux 的 IO 体系层次结构
我们可以通过追踪一个 read()系统调用来一窥这些层次的结构,当 read()系统调用发生,内核首先会通过汇编指令引发一个软中断,然后根据中断传入的参数查询系统调用影射表,找到 read()对应的内核调用方法名,并去执行相关调用,这个系统调用名一般情况下就是 sys_read()。从此,便开始了调用在内核中处理的过程的第一步:
根据这几层的特点,如果你是设计者,你会在哪里实现真对块设备的限速策略呢? 6 、 7 都是相关具体设备的,如果在这个层次提供,那就不是内核全局的功能,而是某些设备自己的 feture 。文件系统层也可以实现,但是如果要全局实现也是不可能的,需要每种文件系统中都实现一遍,成本太高。所以,可以实现限速的地方比较合适的是 VFS 、缓存层、通用块层和 IO 调度层。而 VFS 和 page cache 这样的机制并不是面向块设备设计的,都是做其他事情用的,虽然也在 io 体系中,但是并不适合用来做 block io 的限速。所以这几层中,最适合并且成本最低就可以实现的地方就是 IO 调度层和通用块层。 IO 调度层本身已经有队列了,我们只要在队列里面实现一个限速机制即可,但是在 IO 调度层实现的限速会因为不同调度算法的侧重点不一样而有很多局限性,从通用块层实现的限速,原则上就可以对几乎所有的块设备进行带宽和 iops 的限制。截止目前( 4.3.3 内核), IO 限速主要实现在这两层中。
根据 IO 调度层和通用块层的特点,这两层分别实现了两种不同策略的 IO 控制策略,也是目前 blkio 子系统提供的两种控制策略,一个是权重比例方式的控制,另一个是针对 IO 带宽和 IOPS 的控制。
我们需要先来认识一下 IO 调度层。这一层要解决的核心问题是,如何提高块设备 IO 的整体性能?这一层也主要是针对用途最广泛的机械硬盘结构而设计的。众所周知,机械硬盘的存储介质是磁介质,并且是盘状,用磁头在盘片上移动进行数据的寻址,这类似播放一张唱片。这种结构的特点是,顺序的数据读写效率比较理想,但是如果一旦对盘片有随机读写,那么大量的时间都会浪费在磁头的移动上,这时候就会导致每次 IO 的响应时间很长,极大的降低 IO 的响应速度。磁头在盘片上寻道的操作,类似电梯调度,如果在寻道的过程中,能把路过的相关磁道的数据请求都“顺便”处理掉,那么就可以在比较小影响响应速度的前提下,提高整体 IO 的吞吐量。所以,一个好的 IO 调度算法的需求就此产生。在最开始的阶段, Linux 就把这个算法命名为 Linux 电梯算法。目前在内核中默认开启了三种算法,其实严格算应该是两种,因为第一种叫做 noop ,就是空操作调度算法,也就是没有任何调度操作,并不对 io 请求进行排序,仅仅做适当的 io 合并的一个 fifo 队列。
目前内核中默认的调度算法应该是 cfq ,叫做完全公平队列调度。这个调度算法人如其名,它试图给所有进程提供一个完全公平的 IO 操作环境。它为每个进程创建一个同步 IO 调度队列,并默认以时间片和请求数限定的方式分配 IO 资源,以此保证每个进程的 IO 资源占用是公平的, cfq 还实现了针对进程级别的优先级调度,这里我们不去细节解释。我们在此只需要知道,既然时间片分好了,优先级实现了,那么 cfq 肯定是实现进程级别的权重比例分配的最好方案。内核就是这么做的, cgroup blkio 的权重比例限制就是基于 cfq 调度器实现的。如果你要使用权重比例分配,请先确定对应的块设备的 IO 调度算法是 cfq 。
查看和修改的方法是:
[[email protected] ~]$ cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
[[email protected] ~]$ echo cfq > /sys/block/sda/queue/scheduler