跳转至

使用 RocksDB 的时候,用户可能因为许多原因,希望控制最大写速度在一个范围。例如,闪存写的时候如果超过特定阈值,会引发严重的读延迟峰值。因为你已经在读这篇文章,我相信你已经知道为什么你需要一个限流器。事实上,RocksDB 自带一个 限流器,在大多数场景,应该都是够用了。

如何使用

通过调用 NewGenericRateLimiter 创建一个 RateLimiter 对象,可以对每个 RocksDB 实例分别创建,或者在 RocksDB 实例之间共享,以此控制落盘和压缩的写速率总量。

RateLimiter* rate_limiter = NewGenericRateLimiter(
    rate_bytes_per_sec /* int64_t */, 
    refill_period_us /* int64_t */,
    fairness /* int32_t */);

参数:

  • rate_bytes_per_sec:通常这是唯一一个你需要关心的参数。他控制压缩和落盘的总速率,单位为 bytes/秒。现在,RocksDB 并不强制限制除了落盘和压缩以外的操作(如写 WAL)
  • refill_period_us:这个控制令牌被填充的频率。例如,当 rate_bytes_per_sec 被设置为 10MB/s 然后 refill_period_us 被设置为 100ms,那么就每 100ms 会从新填充 1MB 的限量。更大的数值会导致突发写,而更小的数值会导致 CPU 过载。默认数值 100,000 应该在大多数场景都能很好工作了
  • fairness:RateLimiter 接受高优先级请求和低优先级请求。一个低优先级任务会被高优先级任务挡住。现在,RocksDB 把来自压缩的请求认为是低优先级的,把来自落盘的任务认为是高优先级的。如果落盘请求不断地过来,低优先级请求会被拦截。这个 fairness 参数保证低优先级请求,在即使有高优先级任务的时候,也会有 1/fairness 的机会被执行,以避免低优先级任务的饿死。默认是 10 通常是可以的。

尽管令牌会以 refill_period_us 设定的时间按照间隔来填充,我们仍然需要保证一次写爆发中的最大字节数,因为我们不希望看到令牌堆积了很久,然后在一次写爆发中一次性消耗光,这显然不符合我们的需求。GetSingleBurstBytes 会返回这个令牌数量的上限。

这样,每次写请求前,都需要申请令牌。如果这个请求无法被满足,请求会被阻塞,直到令牌被填充到足够完成请求。比如:

// block if tokens are not enough
rate_limiter->Request(1024 /* bytes */, rocksdb::Env::IO_HIGH); 
Status s = db->Flush();

如果有需要,用户还可以通过 SetBytesPerSecond 动态修改限流器每秒流量。参考 include/rocksdb/rate_limiter.h 了解更多细节

定制

对那些 RocksDB 提供的原生限流器无法满足需求的用户,他们可以通过继承 include/rocksdb/rate_limiter.h 来实现自己的限流器。