ceph rbd:qos

基本介绍

rbd qos控制采取了令牌桶算法来实现,最初版本及算法介绍见:
https://blog.csdn.net/Dongsheng_Yang/article/details/77689521

最初始的pull request:
https://github.com/ceph/ceph/pull/17032

相关commits

2018 Jun 11   4ada1cbaaf6df3d54ebded392df93d26c00c8c7a     TokenBucketThrottle: keep the order of request we want to throttle
2018 Apr 24   9c2dcfdf4b4bc2da1421467cad0533339f1b720f     librbd: support bps throttle and throttle read and write seperately.
2018 Jan 3    fa37ed1a48fd804ac199509bd78c470480ecbb22     common/throttle: start using 64-bit values 
2018 Feb 16   3e572b3628171fb77a47e81e7f1f64a530754075     librbd: separated queued object IO requests from state machine
2017 Sep 13   bf4e454a2256168e7792d887051297498de14f33     librbd: limit IO per second by TokenBucketThrottle
2017 Aug 3    8366ebceb54c138ff33523e467ae655d6c0fc194     Throttle: add a new TokenBucketThrottle
2017 Jul 27   24be70a65b631152dc07ce94ac6100afac935433     throttle: Do not destroy condition variables with waiters
2017 Sep 30   b10d26dfa84627b2622d405d272b1133bb773245     librbd: avoid dynamically refreshing non-atomic configuration settings 
2017 Sep 29   ede691323d94dc04a30f81aca5576a3d6d1930af     librbd: image-meta config overrides should be dynamically refreshed

代码流程

下面是当前master分支的实现(2018.09.19)。

初始化qos控制组件

1.设置相关参数

qos的相关参数是
rbd_qos_iops_limit
rbd_qos_bps_limit
rbd_qos_read_iops_limit
rbd_qos_write_iops_limit
rbd_qos_read_bps_limit
rbd_qos_write_bps_limit

上述参数设为0,表示关闭对应的操作的qos控制,如果>0,则表示开启控制。

2.创建qos控制组件

初始化是在ImageRequestWQ的构造函数中完成的,会为所有类型的qos创建一个TokenBucketThrottle对象,该对象实现了基于令牌桶算法的qos控制策略。
此时,所有qos控制组件的max和avg都是0,表示关闭qos控制。所以此时qos控制不会生效

static std::list<uint64_t> throttle_flags = {
  RBD_QOS_IOPS_THROTTLE,
  RBD_QOS_BPS_THROTTLE,
  RBD_QOS_READ_IOPS_THROTTLE,
  RBD_QOS_WRITE_IOPS_THROTTLE,
  RBD_QOS_READ_BPS_THROTTLE,
  RBD_QOS_WRITE_BPS_THROTTLE
};

ImageRequestWQ<I>::ImageRequestWQ

  for (auto flag : throttle_flags) {
    m_throttles.push_back(make_pair(
      flag, new TokenBucketThrottle(cct, 0, 0, timer, timer_lock)));
  }

3.根据用户参数开启对应的qos控制

在ImageCtx中处理用户参数的函数apply_metadata中,通过ImageRequestWQ得apply_qos_limit函数,为上一步初始化的所有组件设置qos参数。
传入的limit如果为0,表示关闭对应操作的qos控制;大于0表示开启,limit的值会被设置到令牌桶算法的max和avg值上。max表示该桶最多有多少令牌,avg表示每秒向桶中加入多少令牌。

ImageCtx::apply_metadata

io_work_queue->apply_qos_limit(qos_iops_limit, RBD_QOS_IOPS_THROTTLE);
io_work_queue->apply_qos_limit(qos_bps_limit, RBD_QOS_BPS_THROTTLE);
io_work_queue->apply_qos_limit(qos_read_iops_limit, RBD_QOS_READ_IOPS_THROTTLE);
io_work_queue->apply_qos_limit(qos_write_iops_limit, RBD_QOS_WRITE_IOPS_THROTTLE);
io_work_queue->apply_qos_limit(qos_read_bps_limit, RBD_QOS_READ_BPS_THROTTLE);
io_work_queue->apply_qos_limit(qos_write_bps_limit, RBD_QOS_WRITE_BPS_THROTTLE);

qos控制的作用流程

以librbd的aio_write和aio_read为例。

1.发出读写请求到ImageRequestWQ

读、写过程的入口函数分别是:ImageRequestWQ<I>::aio_readImageRequestWQ<I>::aio_write。其基本流程是:

  • 1)调用start_in_flight_io,将m_in_flight_ios加一
  • 2)获取m_image_ctx.owner_lock的读写锁
  • 3)判断请求是加入ImageRequestWQ异步执行,还是直接执行。
    对于读请求,如果我们设置了non_blocking_aio参数,或者有写请求被qos控制组件阻塞(m_write_blockers > 0),或者ImageRequestWQ中存在写请求(m_queued_writes > 0)时,需要将该请求加入队列。
    对于写请求,如果我们设置了non_blocking_aio参数,或者有写请求被qos控制组件阻塞(m_write_blockers > 0)时,需要将该请求加入队列。
  • 4)调用ImageRequestWQ<I>::queue函数将请求加入队列的同时,根据请求类型,分别将m_queued_writesm_queued_reads加一
  • 5)释放m_image_ctx.owner_lock的读写锁

2.ImageRequestWQ中请求的后续处理

ImageRequestWQ对应的线程池中的线程,会从ImageRequestWQ中取出请求,开始做处理,取出请求的函数为ImageRequestWQ<I>::_void_dequeue,在这里,实现了qos控制的分支处理。

  • 1)查看wq队头第一个请求,对其调用needs_throttle函数,
    如果需要被blocked,跳到3.;
    如果不需要blocked,则直接跳到5.。

3.请求被qos控制组件加入阻塞队列

如果开启了流控,每个请求会有一个m_throttled_flag来标记这个请求被哪些流控组件放行,只有前文所述的六种qos控制类型的flag都被设置到m_throttled_flag,这个请求才会被调度到线程池执行。一个请求,可能被多种流控组件所限制,m_throttled_flag的意义就在此。

needs_throttle函数会遍历所有的qos控制组件,通过m_qos_enabled_flag(标识开启了哪些类型的qos控制)来确认rbd开启了哪些qos控制。对于未开启的qos控制类型,直接为请求设置flag到m_throttled_flag

当遇到开启的qos控制类型,则需要先通过tokens_requested函数,获得该请求执行所需的令牌数,然后调用qos控制组件的get函数,判断是否有足够的令牌,如果有,设置m_throttled_flag,然后放行。如果没有足够令牌,则将该请求从ImageRequestWQ中取出,放入对应qos控制组件的blocker队列,并注册回调函数handle_throttle_ready。同时会将m_io_throttled加一。

template <typename I>
bool ImageRequestWQ<I>::needs_throttle(ImageDispatchSpec<I> *item) { 
  uint64_t tokens = 0;
  uint64_t flag = 0;
  bool blocked = false;
  TokenBucketThrottle* throttle = nullptr;

  for (auto t : m_throttles) {
    flag = t.first;
    // 判断该类型的qos控制是否已经放行
    if (item->was_throttled(flag))
      continue;
    // 设置flag,表示该类型的qos控制放行
    if (!(m_qos_enabled_flag & flag)) {
      item->set_throttled(flag);
      continue;
    }

    throttle = t.second;
    tokens = item->tokens_requested(flag);
    // 判断是否有足够令牌,不足则将请求放入阻塞队列
    if (throttle->get<ImageRequestWQ<I>, ImageDispatchSpec<I>,
          &ImageRequestWQ<I>::handle_throttle_ready>(
        tokens, this, item, flag)) {
      blocked = true;
    } else {
      item->set_throttled(flag);
    }
  }
  return blocked;
}

4.请求从qos控制组件阻塞队列requeue到ImageRequestWQ队列

qos控制组件存在一个每秒执行的定时器,执行函数为TokenBucketThrottle::schedule_timer
这个函数会每秒向令牌桶中增加一定数目的令牌,增加令牌后,从前往后遍历阻塞队列中的请求,如果此时的令牌能够满足请求的执行,则将该请求从阻塞队列中取出,为每个取出的请求调用handle_throttle_ready函数。

void TokenBucketThrottle::schedule_timer() {
  add_tokens();

  m_token_ctx = new FunctionContext(
      [this](int r) {
        schedule_timer();
      });

  m_timer->add_event_after(1, m_token_ctx);
}
void TokenBucketThrottle::add_tokens() {
  list<Blocker> tmp_blockers;
  {
    // put m_avg tokens into bucket.
    Mutex::Locker lock(m_lock);
    m_throttle.put(m_avg);
    // check the m_blockers from head to tail, if blocker can get
    // enough tokens, let it go.
    while (!m_blockers.empty()) {
      Blocker blocker = m_blockers.front();
      uint64_t got = m_throttle.get(blocker.tokens_requested);
      if (got == blocker.tokens_requested) {
          // got enough tokens for front.
        tmp_blockers.splice(tmp_blockers.end(), m_blockers, m_blockers.begin());
      } else {
          // there is no more tokens.
        blocker.tokens_requested -= got;
        break;
      }
    }
  }

  for (auto b : tmp_blockers) {
    // 调用handle_throttle_ready函数
    b.ctx->complete(0);
  }
}

handle_throttle_ready函数会设置该流控组件的flag到请求的m_throttled_flag,表示该请求被该组件放行,然后通过item->were_all_throttled()函数判断,该请求是否被所有流控组件放行,如果是,则将请求requeue到ImageRequestWQ的front端,并将m_io_throttled减一;如果否,则不处理(此时该请求已经从该流控组件的阻塞队列中移除,其requeue动作交由阻塞该请求的最后一个流控组件完成)。

template <typename I>
void ImageRequestWQ<I>::handle_throttle_ready(int r, ImageDispatchSpec<I> *item, uint64_t flag) {
  CephContext *cct = m_image_ctx.cct;
  ldout(cct, 15) << "r=" << r << ", " << "req=" << item << dendl;

  ceph_assert(m_io_throttled.load() > 0);
  item->set_throttled(flag);
  if (item->were_all_throttled()) {
    this->requeue(item);
    --m_io_throttled;
    this->signal();
  }
}

5.请求出ImageRequestWQ队列被执行

  • 1)如果是写请求,且!lock_required && !refresh_required,将m_in_flight_writes加一
  • 2)将请求从ImageRequestWQ队列中取出,依次调用ImageDispatchSpec::start_op函数和ImageRequestWQ<I>::process函数完成请求。
  • 3)在process函数中,完成请求后,还会:

    调用finish_queued_iom_queued_readsm_queued_writes减一(根据请求类型);

    如果是写请求,调用finish_in_flight_writem_in_flight_writes减一,如果此时m_in_flight_writes减为0,且m_write_blocker_contexts非空,调用flush_image函数,执行flush操作;flush操作结束后会调用handle_blocked_writes函数,将m_write_blocker_contexts中所有context执行完成。

    调用finish_in_flight_io,将m_in_flight_ios减一。

一些问题

注:下面是个人理解,可能不正确。
1.设置qos后,客户端超出qos发送请求,有没有相关机制,阻塞请求的发送,如果没有,请求会堆积在哪里?

没有,通过lidrbd,用户可以无限发送aio请求,这些请求会堆积在ImageRequestQueue或qos控制组件的阻塞队列中。取决于qos的限制和线程池处理请求的速度。

比如,用户每秒发送2000个请求,线程池每秒处理10000个请求,qos控制为1000,则每秒阻塞队列都会增加1000个请求。
比如,用户每秒发送20000个请求,线程池每秒处理10000个请求,qos控制为1000,则每秒有9000个请求加入阻塞队列,有10000个请求滞留在ImageRequestQueue。

2.对同一image的混合读写请求,是否有完成顺序的保证?

没有

两个点:
1)线程池从ImageRequestWQ取出请求的过程是顺序的,但取出后的执行过程没有顺序保证。
2)因为流控阻塞队列的存在,被要求流控的类型的请求,在令牌不足时会被加入流控阻塞队列,此时,其后面的不需要流控的请求,会被优先执行。

3.qos是可以针对不同类型的请求设置的,当对写请求设置qos后,读写请求是否有完成顺序的保证?

没有

两个点:
1)读请求是否会被写请求的流控组件所限制;不会
2)设置写请求流控组件会不会同步设置读请求的流控组件;不会

第一点可从下面类看出。该仿函数根据传入的不同请求,返回该请求所需的令牌数,如代码所示,读请求在遇到写类型的流控组件时,返回的所需令牌数为0;反之亦然。

template <typename I>
struct ImageDispatchSpec<I>::TokenRequestedVisitor
  : public boost::static_visitor<uint64_t> {
  ImageDispatchSpec* spec;
  uint64_t flag;

  TokenRequestedVisitor(ImageDispatchSpec* spec, uint64_t _flag)
    : spec(spec), flag(_flag) {
  }

  uint64_t operator()(const Read&) const {
    if (flag & RBD_QOS_WRITE_MASK) {
      return 0;
    }

    if (flag & RBD_QOS_BPS_MASK) {
      return spec->extents_length();
    }
    return 1;
  }

  uint64_t operator()(const Flush&) const {
    return 0;
  }

  template <typename T>
  uint64_t operator()(const T&) const {
    if (flag & RBD_QOS_READ_MASK) {
      return 0;
    }

    if (flag & RBD_QOS_BPS_MASK) {
      return spec->extents_length();
    }
    return 1;
  }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容