在写入数据的时候,经历写入WAL以及MemTable的过程后,会触发MemTable是否需要变为Immutable memtable的检测,当满足条件的时候,会标记为只读并且等待刷入磁盘。
检测是否转为只读的函数为CheckMemtableFull,该函数主要用于检测当前写操作的Column Family对应的Memtable。当该MemTable被标记为可Flush的状态的时候,则会尝试进行Flush调度。
当一个MemTable经过写入后被标记为FLUSH_REQUESTED状态,则可以继续FLUSH的流程,那何时会被标记为此状态呢?该状态标记位于函数MemTable::UpdateFlushState中,由如下几个位置会调用该函数:
该调用也很简单,主要根据几个条件判断是否满足变为Immutable Memtable的条件。
当当前的Memtable被标记为FLUSH_NOT_REQUESTED状态之后,则会被CheckMemtableFull函数检测到,并通过原子操作将原先的状态变更为FLUSH_SCHEDULED状态。假设变更成功,则会调用ScheduleFlush。
SchduleFlush函数在FlushScheduler类中实现,该类非常简单,记录所有的将被flush的Memtable队列。在加入到此队列前会首先将该Memtable增加一次引用计数,之后会生成一个Node对象,该对象是链表中的一个节点,next指针指向当前的头指针,最后在循环中调用CAS操作来将头节点替换为新增的节点。在CAS操作成功之后,该Node就成功的成为了flush链表中的首节点了。注意这时候Memtable还是处于可写状态,还没有被转为Immutable状态。
何时真正的进行该转换操作呢?转换操作在写WAL之前,PreprocessWrite函数内,ScheduleFlushes中,我们来看看它的实现。在这个函数中,其实就是一个while循环,不断的从flush_schduler中获取之前写入的Memtable,并调用SwitchMemtable来将其转为Immutable状态。
在switchMemtable中,注释明确的写明了调用线程必须在写队列的头部,因为PreprocessWriter函数只会由Leader线程调用,所以是满足条件的。在这时候,主要就是计算文件序号,创建sst文件并预分配空间,以最新的SN来生成新的Memtable供写入。同时还会生成一个新的SuperVersion。
在RocksDB中,Version表示了所有SST文件的集合,当SST文件列表发生变化,则会生成一个新的Version;而SuperVersion则是Version的超集,它还包含了所有的Memtable列表,所以在上述操作中,Immutable memtable的列表发生了变更,则会生成一个新的SuperVersion。
在SwitchMemtable中,会调用InstallSuperVersionAndScheduleWork来唤醒工作线程池来进行Compact和Flush操作。主要检查是否满足了flush条件,满足的话会加入到flush queue中并将对应的cfd设置pending flush的标记。接着调用MaybeSchduleFlushOrCompaction来完成真正的任务调度。
假设满足flush条件,则会将BGWorkFlush函数放入工作线程池中,让工作线程执行flush的工作。flush的最终入口点函数是BackgroundCallFlush函数。在其中调用的BackgroundFlush函数中,会尝试的去之前的flush queue中获取需要刷盘的Immutable memtable,然后最终使用函数FlushMemTableToOutputFile将Immutable memtable持久化至硬盘。
刷盘主要依赖FlushJob该类。首先我们需要找出所有需要刷盘的Immutable memtable,并放入结果集当中。这里需要注意的是,遍历Immutablememtable的时候,是反着遍历的,因为之前添加的时候我们是将最新的作为链表头,而这里我们需要从旧到新的顺序。之后则通过函数WriteLevel0Table将Immutable memtable写入到磁盘中。