RocksDB代码分析——写入流程

分析均基于v6.27.3。以下流程为了方便理解均经过了简化。

首先,我们通过调用DB::Open来创建数据库,它返回了一个DB*DB::Open内部调用了DBImpl::Open,在里面构造了一个DBImpl*并转换成DB*返回。所以我们拿到的DB*其实是DBImpl*

然后我们调用DB::Put来写入数据。DB::Put是个virtual函数,它被DBImpl::Put给override了:

Status DBImpl::Put(const WriteOptions& o, ColumnFamilyHandle* column_family,
                   const Slice& key, const Slice& val) {
  return DB::Put(o, column_family, key, val);
}

它显式调用了DB::PutDB::Put中首先构造了WriteBatch,然后调用了DB::Write,它是个纯虚函数,被DBImpl::Write override了。DBImpl::Write调用了DBImpl::WriteImpl

DBImpl::WriteImpl先检查了一堆option,这里我们先不管。然后将WriteBatch *my_batch以及WriteOption都转移到了WriteThread::Writer w中。然后调用WriteThread::JoinBatchGroup

  • 假如我们不是leader

leader可能帮我们做了插入,这时直接返回即可。

如果leader告诉我们要自己插入到MemTable,那么调用WriteBatchInternal::InsertInto把数据插入到MemTable中,然后返回。

  • 假如我们是leader(每个时刻只有一个leader)

调用DBImpl::PreprocessWrite

如果flush_scheduler_中有任务,就调用DBImpl::ScheduleFlushes(唯一调用者)。

先调用FlushScheduler::TakeNextColumnFamily(正常状态下唯一调用者),它是唯一从FlushScheduler中取出flush任务(以ColumnFamilyData的形式)的方法。
逐个将这些取出的ColumnFamilyData cfd作为参数调用DBImpl::SwitchMemtable,将这些column family data的memory table变成immutable memory table,然后新建一个memory table。这里没有用原子操作,但是其他非leader的writer应该是没有拿DB mutex的。可能这是因为其他非leader的writer都在等leader给出指示。具体没细看。
将之前取出的ColumnFamilyData cfd作为DBImpl::GenerateFlushRequest的参数,得到FlushRequest flush_req,再将其作为DBImpl::SchedulePendingFlush的参数。
DBImpl::SchedulePendingFlush中,将给传进来的flush_req里的cfdcfd->Ref(),然后加入到DBImpl::flush_queue_里。
接下来的flush流程看这里:

写入WAL
调用WriteBatchInternal::InsertInto把数据插入到MemTable。

WriteBatchInternal::InsertInto中先构造了MemTableInserter inserter,然后执行writer->batch->Iterate(&inserter),即执行WriteBatch::Iterate(Handler* handler),其中handler其实是MemTableInserter*类型的。WriteBatch::Iterate中调用了WriteBatchInternal::Iterate

WriteBatchInternal::Iterate中将WriteBatch中的record逐个读出来,按照record上的tag执行相应的操作。我们这里只关注tag为kTypeValuekTypeColumnFamilyValue的record,对这种record,执行handler->PutCF(column_family, key, value),即调用WriteBatch::Handler::PutCF,将对应的key和value插入。WriteBatch::Handler::PutCF是个virtual函数,注意到这里的handler其实是MemTableInserter*类型的,所以这里调用的其实是MemTableInserter::PutCFMemTableInserter::PutCF中又调用了MemTableInserter::PutCFImpl

MemTableInserter::PutCFImpl先调用MemTable::Add将key和value插入到MemTable中,再调用MemTableInserter::CheckMemtableFull

MemTableInserter::CheckMemtableFull大意:

if (cfd->mem()->ShouldScheduleFlush())
    flush_scheduler_->ScheduleWork(cfd);

FlushScheduler::ScheduleWork中,先cfd->Ref(),然后把cfd放进链表里。前面提到了,在DBImpl::WriteImpl中作为leader时,会检查FlushScheduler flush_scheduler_中有没有任务,有的话就拿出来放到DBImpl::flush_queue_中。

我的问题:为什么不直接放到DBImpl::flush_queue_中?

posted @ 2024-09-28 13:39  寻找繁星  阅读(25)  评论(0编辑  收藏  举报