[leveldb] 6-Put()操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/simonyucsdy/article/details/81589143

熟悉了LevelDB整个脉络之后, Put方法是相当简单的, 一章就可以解决. 数据删除和写入是一个概念, 删除就是写入特殊deletion marker; 批量(batch)和单条写入也是一个概念, 单条写入就是只有一条记录的batch. 整个流程很短, 基本上写个log就好了, 因此速度很快.

Put对于多线程的处理非常精妙, 主体在DBImpl::Write函数中.

Code:db/db_impl.cc(1203-1216)

Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
  Writer w(&mutex_); //Writer就是一个写任务
  w.batch = my_batch;
  w.sync = options.sync;
  w.done = false;

  MutexLock l(&mutex_); //构造时上锁,析构时解锁
  writers_.push_back(&w); //w加入writers队列
  while (!w.done && &w != writers_.front()) {
    w.cv.Wait(); // 线程可能多次被唤醒
  }
  if (w.done) {
    return w.status;
  }

mutex l上锁之后, 到了"w.cv.Wait()"的时候, 会先释放锁等待, 然后收到signal时再次上锁。这段代码的作用就是多线程在提交任务的时候, 一个接一个push_back进队列。但只有位于队首的线程有资格继续运行下去,目的是把多个写请求合并成一个大batch提升效率。

// May temporarily unlock and wait.
  Status status = MakeRoomForWrite(my_batch == nullptr);
  uint64_t last_sequence = versions_->LastSequence();
  Writer* last_writer = &w;
  if (status.ok() && my_batch != nullptr) {  // nullptr batch is for compactions
    WriteBatch* updates = BuildBatchGroup(&last_writer);
    WriteBatchInternal::SetSequence(updates, last_sequence + 1);
    last_sequence += WriteBatchInternal::Count(updates);

MakeRoomForWrite也是一个非常有趣的点,挺长的。从中可以看出二位作者对(Linux)文件系统的不信任或者说太了解运作方式了,有一些手动调度上的优化。一般程序没必要这么写,未必会有很好的效果。

  • 在level 0的table数量快要接近阈值的时候, sleep 1ms

这是因为文件系统表示写入完成并不一定真写到硬盘里了. 数量接近阈值说明快要进行下一次compaction了. 这时候如果文件系统的buffer积压了太多, 会造成硬盘一下子满负载. 还有可能已经在compact了, 这时候sleep就可以让CPU周期给更重要的任务.

  • log具有最高优先级无论如何都要写, 但immemtable只能一张一张写

这也很make sense啊,本来LSMT特色就是连续顺序写入,同时发起三个不同位置的顺序写入请求,那还跟随机写入有什么差别.

所以这节告诉我们工程上优化性能要考虑方方面面,不能光想着自己程序写得好,还要配合操作系统。接下来的代码就是合并batch然后写入硬盘和memtable了。

Code:db/db_impl.cc(1257-1273)

 while (true) {
    Writer* ready = writers_.front();
    writers_.pop_front();
    if (ready != &w) {
      ready->status = status;
      ready->done = true;
      ready->cv.Signal();
    }
    if (ready == last_writer) break;
  }

  // Notify new head of write queue
  if (!writers_.empty()) {
    writers_.front()->cv.Signal();
  }

  return status;

 以上代码从头开始检查队列,把完成的任务标记为done,然后唤醒,如果队列还有别的任务,继续唤醒第一个。

猜你喜欢

转载自blog.csdn.net/simonyucsdy/article/details/81589143