leveldb程式碼精讀 插入操作

liiinuuux發表於2015-11-26
leveldb插入資料時,必然做的操作是先寫logfile,再將資料放到cache裡
不過在此之前,會先進行一下預處理
1 將要寫的資料封裝到到writer裡,將write加入寫佇列,等待輪到它寫。
2 檢查cache是否已滿,是否需要“做檢查點”
3 leveldb的cache有兩個狀態,當前狀態和只讀狀態。
當cache寫滿,需要寫檔案時,會將cache轉成只讀狀態,進行寫檔案和檔案壓縮操作。
所以每次寫檔案前都要先等待之前的只讀cache完成自己的使命。
4 由於新資料需要些到level 0檔案,而level 0檔案的個數是有限制的
當達到soft limit時,需要sleep1毫秒,將cpu資源讓給正在進行中的壓縮操作。
當達到hard limits時,直接進入等待。
5 維護cache狀態,維護file number,建立新檔案。。。
6 嘗試進行一次檔案壓縮。

對外的put函式

點選(此處)摺疊或開啟

  1. Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) {
  2.   return DB::Put(o, key, val);
  3. }


  4. Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  5.   WriteBatch batch;
  6.   batch.Put(key, value);
  7.   return Write(opt, &batch);
  8. }

真正的功能入口函式

點選(此處)摺疊或開啟

  1. Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
  2.   // 將WriteBatch封裝到一個Writer裡,設定一些選項。
  3.   Writer w(&mutex_);
  4.   w.batch = my_batch;
  5.   w.sync = options.sync;
  6.   w.done = false;


  7.   MutexLock l(&mutex_);


  8.   /*
  9.   把Writer放到到寫佇列裡,等待writer升到 writers_.front 就能開始了。
  10.   */
  11.   writers_.push_back(&w);
  12.   while (!w.done && &w != writers_.front()) {
  13.     w.cv.Wait();
  14.   }
  15.   if (w.done) {
  16.     return w.status;
  17.   }


  18.   // May temporarily unlock and wait.
  19.   /*
  20.   MakeRoomForWrite會對寫入操作所需的條件進行一系列判斷,
  21.   如:
  22.   level 0 檔案數是否超過限制,
  23.   cache是否還有空間,是否真的需要寫檔案,
  24.   imm cache是否能夠釋放
  25.   是否需要做壓縮
  26.   條件都滿足,確定需要寫檔案時,進行:
  27.   生成新的檔案號,新的logfile,將當前cache轉成imm cache等操作
  28.   引數為my_batch == NULL的意思是:如果my_batch為空,則視為想讓MakeRoomForWrite嘗試做一次壓縮。
  29.   */
  30.   Status status = MakeRoomForWrite(my_batch == NULL);


  31.   // sequence是指寫batch的次數
  32.   uint64_t last_sequence = versions_->LastSequence();
  33.   Writer* last_writer = &w;


  34.   /*
  35.   my_batch是有可能為空的,可以利用空batch手動讓MakeRoomForWrite進行壓縮操作。
  36.   */
  37.   if (status.ok() && my_batch != NULL) { // NULL batch is for compactions
  38.     /*
  39.     WriteBatch裡有一個字串rep_,存放轉碼成儲存格式後的資料。
  40.     BuildBatchGroup的工作是從writers_裡找其它的WriteBatch,他們的rep_拼到一個WriteBatch裡
  41.     但是最終的rep_長度不能超過 1 << 20
  42.     */
  43.     WriteBatch* updates = BuildBatchGroup(&last_writer);
  44.     // WriteBatchInternal是一個由靜態函式組成的工具類
  45.     WriteBatchInternal::SetSequence(updates, last_sequence + 1);
  46.     last_sequence += WriteBatchInternal::Count(updates);


  47.     // Add to log and apply to memtable. We can release the lock
  48.     // during this phase since &w is currently responsible for logging
  49.     // and protects against concurrent loggers and concurrent writes
  50.     // into mem_.
  51.     {
  52.       mutex_.Unlock();
  53.       // 寫logfile
  54.       status = log_->AddRecord(WriteBatchInternal::Contents(updates));
  55.       bool sync_error = false;
  56.       if (status.ok() && options.sync) {
  57.         status = logfile_->Sync();
  58.         if (!status.ok()) {
  59.           sync_error = true;
  60.         }
  61.       }
  62.       if (status.ok()) {
  63.         // 將資料放入cache
  64.         status = WriteBatchInternal::InsertInto(updates, mem_);
  65.       }
  66.       mutex_.Lock();
  67.       if (sync_error) {
  68.         // The state of the log file is indeterminate: the log record we
  69.         // just added may or may not show up when the DB is re-opened.
  70.         // So we force the DB into a mode where all future writes fail.
  71.         RecordBackgroundError(status);
  72.       }
  73.     }
  74.     if (updates == tmp_batch_) tmp_batch_->Clear();


  75.     // 更新sequence
  76.     versions_->SetLastSequence(last_sequence);
  77.   }


  78.   while (true) {
  79.     Writer* ready = writers_.front();
  80.     writers_.pop_front();
  81.     if (ready != &w) {
  82.       ready->status = status;
  83.       ready->done = true;
  84.       ready->cv.Signal();
  85.     }
  86.     if (ready == last_writer) break;
  87.   }


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


  92.   return status;
  93. }





之所以需要MakeRoom是因為新資料需要寫入level 0 資料檔案,但是level 0檔案數量有限制。
可能需要做壓縮來減少level 0 檔案的數量。
同時當前cache也需要轉成imm cache,需要判斷之前的imm cache是否還佔著位置。

點選(此處)摺疊或開啟

  1. Status DBImpl::MakeRoomForWrite(bool force) {
  2.   mutex_.AssertHeld();
  3.   assert(!writers_.empty());
  4.   // 決定是否允許透過sleep來給壓縮操作讓出系統資源。
  5.   bool allow_delay = !force;
  6.   Status s;
  7.   while (true) {
  8.     if (!bg_error_.ok()) {
  9.       // Yield previous error
  10.       s = bg_error_;
  11.       break;
  12.     } else if (
  13.         /*
  14.         當允許delay,並且level 0的檔案數已經超過了8個,就要sleep 1毫秒,給複製壓縮的執行緒工作讓出CPU資源。
  15.         sleep一次後就將allow_delay設成false,這次寫入操作就不需要再sleep了。
  16.         */
  17.         allow_delay &&
  18.         versions_->NumLevelFiles(0) >= config::kL0_SlowdownWritesTrigger) {
  19.       // We are getting close to hitting a hard limit on the number of
  20.       // L0 files. Rather than delaying a single write by several
  21.       // seconds when we hit the hard limit, start delaying each
  22.       // individual write by 1ms to reduce latency variance. Also,
  23.       // this delay hands over some CPU to the compaction thread in
  24.       // case it is sharing the same core as the writer.
  25.       mutex_.Unlock();
  26.       env_->SleepForMicroseconds(1000);
  27.       allow_delay = false; // Do not delay a single write more than once
  28.       mutex_.Lock();
  29.     } else if (!force &&
  30.                /*
  31.                當cache不滿時,先不寫檔案。
  32.                */
  33.                (mem_->ApproximateMemoryUsage() <= options_.write_buffer_size)) {
  34.       // There is room in current memtable
  35.       break;
  36.     } else if (imm_ != NULL) {
  37.       /*
  38.       leveldb有兩種cache,一個是當前cache,就是目前正在寫新資料的cache。
  39.       當cache滿了,需要寫檔案時,就將當前cache轉成immunity cache,是一個只讀cache,由指標imm_管理。
  40.       imm cache 使用者查詢操作和壓縮操作。
  41.       如果imm cache存在,就要等它的對應的檔案壓縮完成才能將當前cache轉成imm cache。
  42.       */
  43.       // We have filled up the current memtable, but the previous
  44.       // one is still being compacted, so we wait.
  45.       Log(options_.info_log, "Current memtable full; waiting...\n");
  46.       bg_cv_.Wait();
  47.     } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) {
  48.       // 達到了level 0 檔案數的硬指標限制,不能再寫新的了。
  49.       // There are too many level-0 files.
  50.       Log(options_.info_log, "Too many L0 files; waiting...\n");
  51.       bg_cv_.Wait();
  52.     } else {
  53.       // 檢查條件結束,開始正式工作
  54.       // Attempt to switch to a new memtable and trigger compaction of old
  55.       assert(versions_->PrevLogNumber() == 0);


  56.       // 生成新的logfile number
  57.       uint64_t new_log_number = versions_->NewFileNumber();
  58.       WritableFile* lfile = NULL;
  59.       // 建立新檔案
  60.       s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile);
  61.       if (!s.ok()) {
  62.         // Avoid chewing through file number space in a tight loop.
  63.         versions_->ReuseFileNumber(new_log_number);
  64.         break;
  65.       }
  66.       delete log_;
  67.       delete logfile_;


  68.       // 將Logfile指向新檔案,設定新log number
  69.       logfile_ = lfile;
  70.       logfile_number_ = new_log_number;
  71.       log_ = new log::Writer(lfile);


  72.       // 將當前cache切換成imm cache,建立新的當前cache
  73.       imm_ = mem_;
  74.       has_imm_.Release_Store(imm_);
  75.       mem_ = new MemTable(internal_comparator_);
  76.       mem_->Ref();
  77.       force = false; // Do not force another compaction if have room


  78.       /*
  79.       如果需要,進行一次壓縮
  80.       這裡面進行了一下判斷,調了回撥函式
  81.       最終真正的功能入口是DBImpl::BackgroundCompaction()
  82.       */
  83.       MaybeScheduleCompaction();
  84.     }
  85.   }
  86.   return s;
  87. }


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26239116/viewspace-1847246/,如需轉載,請註明出處,否則將追究法律責任。

相關文章