QEMU QoS特性及原理分析和Librbd QoS定义

由于公司的分布式存储产品(基于Ceph)需要提供QoS特性,之前也相关经验,所以打算先分析QEMU QoS的特性及其实现原理,然后基于此,给出Librbd QoS的定义,内容如下:

由于GNU Linux系统的cgroup不支持网络设备(nfs,ceph)的资源控制,为提供一套通用的I/O流控机制。QEMU实现了一个独立的流控模块来控制磁盘I/O操作,该模块能实现磁盘TPSOPS潮汐式流量控制,具体特性如下:

QEMU QoS 特性

磁盘I/O的两个方面: 每秒的数据量(TPS)以及每秒的I/O操作(OPS):

  • QEMU能分别为TPSOPS提供全局配置或者针对读写的独立配置
  • 全局配置针对读写的差异配置不能同时使用
  • 支持同时控制TPSOPS
  • 支持潮汐I/O流量控制
  • 支持I/O大小控制
  • 支持基于磁盘组的流控控制
  • QEMU默认不开启流量控制

全局配置读写差异配置

QEMU针对TPSOPS分别提供全局配置及读写配置。全局配置(*-total)同时控制读写I/O操作,而读写配置(*-read, *-write)则通过设置不同的参数分别控制读写I/O操作。

全局配置和读写配置不能同时使用

潮汐式流量控制

除了上述标准的TPSOPS控制,QEMU还支持潮汐式流控,允许磁盘I/O在指定长度的时间(*-total-max-length)内超过标准配置,出现I/O波峰(*-total-max),这在计划内的流量高峰期很有用(如:系统启动,服务启动)。

控制I/O大小

在执行OPS控制时,通常不考虑I/O的大小,所有的I/O都同等对待,从TPS时序图上看,就是一个波浪形的流量线。QEMU支持配置(io-size)来平滑,磁盘流量。大于io-size的请求将会正确的分解为多个I/O,而小于io-size的请求会被当做一个I/O

磁盘组流控

前述的各种配置都是针对单个磁盘的。此外,QEMU还提供了磁盘组流控功能,加入相同流控配置组的所有磁盘共享相同的限制。默认情况下,每个磁盘设备都会创建一个以自身名字为组名的配置组,同时一个设备只能加入一个配置组。

QEMU QoS原理

leaky bucket algorithm是网络世界中流量塑型(Traffic Shaping)或速率限制(Rate Limiting)时常用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。通过漏桶算法,突发流量可以被整形(Shaping)以便为网络提供一个稳定的流量。

QEMU中也采用漏桶算法实现I/O限制。这就像是一个漏水的桶,倒入桶中的水就是要执行的I/O, 桶满了,I/O就不能被加入,被堵塞了。举下面的例子来说明:

iops-total = 100
iops-total-max = 2000
iops-total-max-length = 60
  • 水从桶中漏走的速度是 100 IOPS
  • 往桶中倒水的速度是 2000 IOPS
  • 桶的大小是 2000 * 60 = 12000
  • 如果iops-total-max没有设置,桶的大小就是 100 * 60 = 600

开始的时候桶是空的,所以可以以 2000 IOPS的速度往桶里面加入I/O,直到桶满。桶满后,就只能以漏水的速度往桶里面加入I/O,也就是 100 IOPS。如果之后加水的速度小于漏水的速度,到某个时刻桶就会空,这个时候就又可以以 2000 IOPS的速度往桶里面加入I/O

需要注意的是,桶是一直在漏水的,所以桶加满水的时间通常是大于60秒。另外,如果加水的速度小于2000 IOPS,桶加满水的时间也是大于60秒。

QEMU QoS实现

QEMUI/O控制由块层实现,流控模块实现包含在Throttle.h/.c,Throttle-groups.h/.c文件中,完整的块设备I/O限制,还依赖于协程coroutine及定时器(timer)。

QEMU 协程

协程(coroutine)是一种实现堆栈切换的机制,通常用来实现用户态线程间的协作。针对不同的系统,QEMU提供了多种协程实现方式(gthread,signalstack,ucontext)等,代码实现在coroutine-*文件中,QEMU中主要提供了三个接口:qemu_coroutine_create,qemu_coroutine_enter,qemu_coroutine_yield来使用协程

  • qemu_coroutine_create 创建协程
  • qemu_coroutine_enter 执行协程
  • qemu_coroutine_yield 阻塞,等待退出

其中,qemu_coroutine_yield可以不主动调用,QEMU执行完用户代码后,会主动执行qemu_coroutine_switch(co, co->caller, COROUTINE_TERMINATE)在(coroutine_trampoline)函数里。如果调用了qemu_coroutine_yield,如在异步IO中,此协程会被挂起,等到I/O完成时,在回调(callback)中调用qemu_coroutine_enter进入该协程运行,刚好该协程运行的是COROUTINE_TERMINATE那句,从而完成了协程的退出。

这里不打算解析具体代码,只通过图表的形式,以ucontext实现为例,展示QEMU coroutine的工作原理:

  1. 用户代码调用qemu_coroutine_create创建协程
    1.1 调用qemu_coroutine_new创建协程,返回coroutine对象
    1.1.1 切换到coroutine_trampoline执行,设置协程环境信息(self->env)
    1.1.2 siglongjmp跳转到qemu_coroutine_newsigsetjmp处继续执行
    1.1.3 返回协程对象

  2. 用户代码调用qemu_coroutine_enter执行协程
    2.1 调用qemu_coroutine_switch,执行堆栈切换
    2.1.1 siglongjmp跳转到coroutine_tramplinesigsetjmp处继续执行
    2.2 执行co->entry用户代码
    2.3 调用qemu_coroutine_switch,执行堆栈切换,回到之前的qemu_coroutine_switchsigsetjmp处继续执行
    2.4 返回到qemu_coroutine_enter,操作完成,删除当前协程

I/O控制

QEMU通过协程来执行I/O请求,在Block-backend.c文件的blk_aio_prwv函数中调用前述方法完成协程的创建和执行,协程入口函数为:blk_aio_write_entry,流程图如下:

I/O的后续处理过程中Exceed Limits部分,由QEMU Throttling模块实现。在设备初始化过程中,如果开启了I/O throttling, 就会在blockdev.cblockdev_init方法中初始化throttling配置,设置读写timer并与设备关联,类图如下:

要点如下:

  • 每个设备关联一个ThrottleGroup(如果开启了I/O流量限制), 所有的组由全局变量throttle_groups管理
  • 每个ThrottleConfig可包含达6个I/O限制配置(LeakyBucket
  • 每个设备分别关联读写两个定时器(timer),当超过I/O限制时,I/O协程被阻塞,定时器被设置并添加到event loop,定时器超时后,会重新进入协程继续I/O处理

I/O throttling模块内部的处理逻辑如下:

Ceph Librbd QoS定义

默认情况下,Ceph Rbd并不提供QoS功能,然而在Ceph Cluster集群存储能力一定的情况下,为避免某个Rbd image的突发流动对其他的image带来影响,限制某个Rbd设备的I/O流量,就很有必要。

结合上述对QEMU QoS的特性及原理分析,我们将librbd QoS定义如下:

  • 提供基于TPSOPSI/O限速功能
  • TPSOPS提供全局配置或者针对读写的独立配置(两种配置不能同时设置)
  • 支持潮汐I/O流量控制
  • 支持I/O大小控制

猜你喜欢

转载自blog.csdn.net/lzw06061139/article/details/52901904
QOS