这个话题其实由来已久,很多以ceph为原型的存储厂商都会遇到这个问题,有没有块设备的QOS,答案是,,,,还没有。
好吧,既然这样,福利来了:https://github.com/ceph/ceph/pull/17032
下面是rbd qos 的实现介绍,这也是我在ceph meetup上面分享过的一个topic:
背景结束,下面来干货吧:
首先来看看我们为什么需要rbd 的rate limiting:
如上,理由很简单。 我们总的ceph集群的io能力是有限的,比如带宽,iops。所以需要把有限的IO能力合理分配。保证重要的业务有足够的io能力。
其实这是一个很普遍的业务需求,cinder 现在支持两种磁盘的QOS,一种是front-end的,也就是借用qemu的disk qos。另一种是back-end的,如果使用
ceph,那就不支持。还有iscsi和nbd。(图上有个typo)。
那么好,现在都知道rbd 的qos 是有必要的,我们就需要实现一个rbd 的qos。在这之前,我们先来了解一下rbd 的读写流程吧
上图可以看到,用户先发起aio_write请求,又Image类来处理,image会调用ImageRequestWQ的aio_write 来处理。听名字其实就可以看出来,这是一个workqueue。他会接收到的所有Request 一个一个的queue到自己得队列里面,然后唤起处理线程,处理线程会一个一个的出队列,并进行处理。
那么我们先来看一下都会有哪些Request 会进入到这个ImageRequestWQ呢:
如上,这是现在的ImageReqeust的一个结构,librbd会接受上面的几种类型的request,我们第一个版本的QOS 是对所有的request进行限制的,当然,我们以后会分开限制。比如一个image,读的iops可以达到1000,但是我希望限制写的iops只有500. 这个时候就需要对不同的request进行不同的限制了。
既然我们已经知道了我们的读写请求都会被queue到一个ImageRequestWQ里面,然后再由后台线程一个一个的处理,那现在其实已经很清楚我们该做什么了,在出队列的地方,对处理的速率进行限制,就可以达到rate limiting的效果了。这时候可能有人会问:为什么是dequeue的时候而不是enqueue?原因是因为enqueue是在client端的aio_write路径上面的,我们需要尽快让aio_write返回,所以这个路径越短越好。
话已至此,无需多言。来吧,怎么限制request的dequeue?
是的,TokenBucket 算法。
顾名思义,这就是通过一个装满令牌的桶来实现对流量的控制。如上图,当我们的request在dequeue的时候,需要做的就是去令牌桶里面拿到一个相应的令牌。然后才能顺利dequeue。如果拿不到,就等着。这样的话,我只需要简单的通过产生令牌的速率来达到限制request的处理速率。Perfect!所以我根本不用care 到底request是什么,我只需要care我一秒钟需要产生多少个令牌即可。这也是为什么我选择了令牌桶算法,而不是漏桶算法的原因。
okey, 所以第一件事是我们需要在ceph里面引入一个令牌桶。
上图就是一个TokenBucketThrottle的实现。
Blocker:
这个类用来保存我们一个对应关系,tokens_requested -> Context。 每个因为token不足需要被block的request 都需要传一个Context 回调函数过来,然后我们的TokenBucketThrottle会新建一个Blocker来维护这个对应关系,然后加入到blockers_list里面。这样在我们获得足够的token的时候就可以从blockers_list里面一个一个的release这些blocker。
Bucket:
顾名思义,这是一个桶,桶里面会装有令牌,每次一个request过来,我们就需要从指定的桶里面请求令牌。
TockenBucketThrottle:
这是对外的接口类,也是实现令牌桶算法的类。有一个timer,每一秒钟向bucket里面投入m_avg个令牌。按照令牌桶的算法逻辑,我们就可以通过这个方式来控制申请令牌的用户按照每秒m_avg的速率来出队列。
令牌桶的实现已经有了,我们只需要看一下怎么样使用一个tokenbucketthrottle就行了。
上图就是我们在ImageRequestWQ里面使用tokenbucketThrottle的一个实例。(最终版本跟这个有不同,使用了template来传递回调函数类型)
可以看到使用方法很简单,在ImageRequestWQ里面添加了一个成员变量,iops_throttle,这是一个TokenBucketThrottle的实例。然后我们只需要
在void_dequeue的时候,每一次处理io就需要通过iops_throttle来get 一个token。只有顺利获得token才能继续进行,否则就会被加入到blockers_list里面,等待有足够的token才能继续。
至此,理论和实现都已经介绍完毕,下面不废话,直接上图
如图,这就完成了一个iops 的rate limiting,而且可以支持在线修改,修改命令简单而且和rbd本身的已有机制完美融洽:
rbd image-meta set IMAGE conf_rbd_qos_iops_limit IOPS
第一个版本已经介绍到这, 下面看看还有哪些待完成的事情:
(1)读写单独限制,很多场景下,我们对于读和写的限制并不一样,大多情况下,写的iops 会比读低很多。
(2)iops and bps,我们需要限制iops,这对于小io场景很有用,但是很多情况下,我们需要的是带宽限制。
(3)bursting,流量突发场景,比如VM当中,在vm启动的时候,我们需要大量的IO,但是启动结束之后我们需要限制。这就是突发io限制的意义。
(4)其他场景,不只是rbd image qos,很多其他地方也需要rate limiting,比如rate limiting in rbd-mirroring。
以上这些工作,在TokenBucketThrottle的基础上都能很容易的意义实现,剩下的问题只有,,,,,,忙的没时间!!