levelDB put 代码阅读

levelDB 写流程主要涉及到以下几点:

1、对应的banch及检测当前level0层文件数;

2、level 0对应的内存是否足够,检测是否需要生成imm;

3、level 0层文件数达到软硬文件限制;

4、合并写操作

levelDB写流程大概流程图如下:

 

// 写操作入口
Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  WriteBatch batch; //leveldb中不管单个插入还是多个插入都是以WriteBatch的方式进行的

  // 将key与value生成到writeBacth对应的req中
  batch.Put(key, value);
  return Write(opt, &batch);
}
// 处理过程
// 1. 队列化请求
//     mutex l上锁之后, 到了"w.cv.Wait()"的时候, 会先释放锁等待, 然后收到signal时再次上锁.
//     这段代码的作用就是多线程在提交任务的时候,一个接一个push_back进队列.
//     但只有位于队首的线程有资格继续运行下去. 目的是把多个写请求合并成一个大batch提升效率.
// 2. 写入的前期检查和保证
// 3. 按格式组装数据为二进制
// 4. 写入log文件和memtable
// 5. 唤醒队列的其他人去干活,自己返回
Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) {
  Writer w(&mutex_);
  w.batch = updates;
  w.sync = options.sync;
  w.done = false;

  //串行化writer。如果有其他writer在执行则进入队列等待被唤醒执行
  MutexLock l(&mutex_);
  writers_.push_back(&w); // 将需要写的数据加入到写队列,目的是把多个写请求合并成一个大的batch

  // 如果当前队列中有任务正在执行时,等待其他任务将该请求队列的任务执行完成
  while (!w.done && &w != writers_.front()) {
    w.cv.Wait();
  }

  //writer的任务被其他writer帮忙执行了,则返回。BuildBatchGroup会有合并写的操作。
  if (w.done) {
    return w.status;
  }

  // May temporarily unlock and wait.
  // 写入前的各种检查。是否该停写,是否该切memtable,是否该compact
  /**
   * 1、检查level0 中是否存在可用内存:
   *  1.1 level0 中内存未达到上限,返回可写
   *  1.2 level0 中内存达到内存上限,将内存中的数据写入到imuable memtable中,并且由后台完成compact任务
   *  1.3 level0 中内存达到内存上限申请新的内存,触发当前存在可用内存流程
   * 2、检查level0中的文件数是否达到level0要求的最大文件数对应的slow文件数(8)以及停止写文件数(12)
   *  2.1 达到软限制及文件数达到8时,让出CPU
   *  2.2 达到停止写文件是数12时,等待将level0中的文件合并到level1
   */
  Status status = MakeRoomForWrite(updates == nullptr);

  // 获取本次写入的版本号,其实就是个uint64
  uint64_t last_sequence = versions_->LastSequence();
  Writer* last_writer = &w;
  //这里writer还是队列中第一个,由于下面会队列前面的writers也可能合并起来,所以last_writer指针会指向被合并的最后一个writer
  if (status.ok() && updates != nullptr) {  // nullptr batch is for compactions
    WriteBatch* updates = BuildBatchGroup(&last_writer); //这里会把writers队列中的其他适合的写操作一起执行
    WriteBatchInternal::SetSequence(updates, last_sequence + 1); //把版本号写入batch中

    // 由于合并写,可能同时存在多个请求的情况,所以版本号向后跳合并个数
    last_sequence += WriteBatchInternal::Count(updates); //updates如果合并了n条操作,版本号也会跳跃n

    // Add to log and apply to memtable.  We can release the lock
    // during this phase since &w is currently responsible for logging
    // and protects against concurrent loggers and concurrent writes
    // into mem_.
    {
      mutex_.Unlock();
      status = log_->AddRecord(WriteBatchInternal::Contents(updates));  //第一步写入log,用于故障恢复,防止数据丢失。
      bool sync_error = false;
      if (status.ok() && options.sync) {
        status = logfile_->Sync();
        if (!status.ok()) {
          sync_error = true;
        }
      }

      // 写入日志成功,添加到内存中
      if (status.ok()) {
        status = WriteBatchInternal::InsertInto(updates, mem_); //插入memtable了
      }
      mutex_.Lock();
      if (sync_error) {
        // The state of the log file is indeterminate: the log record we
        // just added may or may not show up when the DB is re-opened.
        // So we force the DB into a mode where all future writes fail.
        RecordBackgroundError(status);
      }
    }
    if (updates == tmp_batch_) tmp_batch_->Clear();

    versions_->SetLastSequence(last_sequence);
  }

  // 将处理完的任务从队列里取出,并置状态为done,然后通知对应的CondVar启动。
  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; //直到last_writer通知为止。
  }

  // Notify new head of write queue。通知队列中第一个writer干活。
  if (!writers_.empty()) {
    writers_.front()->cv.Signal();
  }

  return status;
}
// REQUIRES: mutex_ is held
// REQUIRES: this thread is currently at the front of the writer queue
// 1. 后台任务有无出现错误?出现错误直接返回错误。(compaction是后台线程执行的)
// 2. level 0 的文件数超过8个,则等待1s再继续执行,因为level 0的文件数目需要严格控制
// 3. 如果memtable的大小小于4MB(默认值,可以修改),直接返回可以插入;
// 4. 到达4说明memtable已经满了,这时候需要切换为Imuable memtable。所以这时候需要等待旧的Imuable memtable compact到level 0,进入等待
// 5. 到达5说明旧的Imuable memtable已经compact到level 0了,这时候假如level 0的文件数目到达了12个,也需要等待
// 6. 到达6说明旧的Imuable memtable已经compact到磁盘了,level 0的文件数目也符合要求,这时候当前的memtable可以转换成Imuable memtable,并启动后台compact。同时生成新的memtable、log用于数据的写入。
Status DBImpl::MakeRoomForWrite(bool force) {
  mutex_.AssertHeld();
  assert(!writers_.empty());
  bool allow_delay = !force;  //allow_delay代表compaction可以延后
  Status s;
  while (true) {
    if (!bg_error_.ok()) {
      // Yield previous error 如果后台任务已经出错,直接返回错误
      s = bg_error_;
      break;
    } else if (allow_delay && versions_->NumLevelFiles(0) >=
                                  config::kL0_SlowdownWritesTrigger) {
      // level0的文件数限制超过8,睡眠1ms,简单等待后台任务执行。写入writer线程向压缩线程转让cpu。
      /**
       * 0、kL0_SlowdownWritesTrigger:Soft limit on number of level-0 files.  We slow down writes at this point.
       * 1、如果level0的文件数达到上限时,让出cpu给其他后台任务执行
       * 2、让出CPU后在循环check当前是否有可用内存
       */
      // We are getting close to hitting a hard limit on the number of
      // L0 files.  Rather than delaying a single write by several
      // seconds when we hit the hard limit, start delaying each
      // individual write by 1ms to reduce latency variance.  Also,
      // this delay hands over some CPU to the compaction thread in
      // case it is sharing the same core as the writer.
      mutex_.Unlock();
      env_->SleepForMicroseconds(1000);
      allow_delay = false;  // Do not delay a single write more than once
      mutex_.Lock();
    } else if (!force &&
               (mem_->ApproximateMemoryUsage() <= options_.write_buffer_size)) {
      // There is room in current memtable
      // 当前memtable,还有空间继续写入;退出执行写操作
      break;
    } else if (imm_ != nullptr) {
      /**
       * 1、当前Imuable memtable中的任务未执行完成,等待Imuable memtable中的任务compact到level0中
       * 2、如果当前不存在执行的Imuable memtable任务时,检查其他条件
       */
      // We have filled up the current memtable, but the previous
      // one is still being compacted, so we wait.
      // 等待之前的imuable memtable完成compact到level0
      Log(options_.info_log, "Current memtable full; waiting...\n");
      background_work_finished_signal_.Wait();
    } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) {
      /**
       * 1、level0的文件数达到level0最大文件上限,停止写入level0;等待level0的文件compact到level1
       */
      // There are too many level-0 files.
      Log(options_.info_log, "Too many L0 files; waiting...\n");
      background_work_finished_signal_.Wait();
    } else {
      /**
       * 1、如果当前内存已经达到上限时,但是level0文件数未达到上限时,生成新的log文件
       * 2、将当前内存的数据切换到imuable memtable中:后台执行
       */
      // Attempt to switch to a new memtable and trigger compaction of old
      // 当前compaction没有负载过重,可以切换到新的memtable并触发旧的进行compaction
      assert(versions_->PrevLogNumber() == 0);
      uint64_t new_log_number = versions_->NewFileNumber();
      WritableFile* lfile = nullptr;
      s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile); //生成新的log文件
      if (!s.ok()) {
        // Avoid chewing through file number space in a tight loop.
        versions_->ReuseFileNumber(new_log_number);
        break;
      }
      delete log_; //删除旧的log对象分配新的
      delete logfile_;
      logfile_ = lfile;
      logfile_number_ = new_log_number;
      log_ = new log::Writer(lfile);
      imm_ = mem_; //切换memtable到Imuable memtable
      has_imm_.store(true, std::memory_order_release);
      mem_ = new MemTable(internal_comparator_);
      mem_->Ref();
      force = false;  // Do not force another compaction if have room
      MaybeScheduleCompaction(); //如果需要进行compaction,后台执行
    }
  }
  return s;
}
// 作用:writer会被队列化,而只有排在第一位的writer才会往下执行。而writer有可能会把排在它后面的writer的活也拿过来一起干了。
// 机制:遍历队列后面的writer们,一直到遇到帮不了忙的writer就返回了。那么帮不帮忙的标准是啥?两个标准:
//  1.sync类型是否一样(我不需要马上flush到磁盘而你要,你的活还是自己干吧)
//  2.写入的数据量是不是过大了?(避免单次写入数据量太大)
// 只要没有符合这两个限制条件,就可以帮忙,合并多条write操作为一条操作
WriteBatch* DBImpl::BuildBatchGroup(Writer** last_writer) {
  mutex_.AssertHeld();
  assert(!writers_.empty());
  Writer* first = writers_.front();
  WriteBatch* result = first->batch;
  assert(result != nullptr);

  // 计算要自己要写入的byte大小
  size_t size = WriteBatchInternal::ByteSize(first->batch);

  // Allow the group to grow up to a maximum size, but if the
  // original write is small, limit the growth so we do not slow
  // down the small write too much.
  size_t max_size = 1 << 20;
  if (size <= (128 << 10)) {
    max_size = size + (128 << 10);
  }

  *last_writer = first;
  std::deque<Writer*>::iterator iter = writers_.begin();
  ++iter;  // Advance past "first"
  for (; iter != writers_.end(); ++iter) {
    Writer* w = *iter;
    if (w->sync && !first->sync) {  //sync类型不同,你的活我不帮你了
      // Do not include a sync write into a batch handled by a non-sync write.
      break;
    }

    if (w->batch != nullptr) {
      size += WriteBatchInternal::ByteSize(w->batch);
      if (size > max_size) {  //这个帮忙数据量过大了,我也不帮忙了
        // Do not make batch too big
        break;
      }

      // Append to *result
      if (result == first->batch) {
        // Switch to temporary batch instead of disturbing caller's batch
        result = tmp_batch_;
        assert(WriteBatchInternal::Count(result) == 0);
        WriteBatchInternal::Append(result, first->batch);
      }
      WriteBatchInternal::Append(result, w->batch); //来吧,你的活我可以帮你干了
    }
    *last_writer = w;
  }
  return result;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值