LevelDB源码剖析之snapshot原理

注意:本文转载自小和尚的藏经阁,无任何修改。

Snapshot(快照)关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。

快照的作用主要是能够进行在线数据备份与恢复1

leveldb也提供了快照。对于整个key-value存储状态,Snapshot提供了一致性只读视图。ReadOptions::snapshot不为NULL时表示读取数据库的某一个特定版本。如果Snapshot为NULL,则读取数据库的当前版本。

通常Snapshots通过方法DB::GetSnapshot()创建:

leveldb::ReadOptions options;
options.snapshot = db->GetSnapshot();
... apply some updates to db ...
leveldb::Iterator* iter = db->NewIterator(options);
... read using iter to view the state when the snapshot was created
...
delete iter;
db->ReleaseSnapshot(options.snapshot);

注意,当snapshot不再使用时,需要用DB::RealeaseSnapshot释放。这样底层就可以释放支持Snapshot的资源。

写操作也可以返回一个应用了一系列更新之后的Snapshot:

leveldb::Snapshot* snapshot;
leveldb::WriteOptions write_options;
write_options.post_write_snapshot = &snapshot;
leveldb::Status status = db->Write(write_options, ...);
... perform other mutations to db ...

leveldb::ReadOptions read_options;
read_options.snapshot = snapshot;
leveldb::Iterator* iter =
db->NewIterator(read_options);
... read as of the state just after the Write call returned ...
delete iter;
 
db->ReleaseSnapshot(snapshot);

leveldb中的Snapshot是如何实现的呢?

我们知道leveldb是只增加的数据库,在一定的时间范围内leveldb并不删除数据,它记录了所有的操作。比如:

  table["liming"] = 18
  del table["liming"]
   
  table["wangdong"] = 85
  table["wangdong"] = 30

转换成leveldb内部操作为:

  liming 1 kTypeValue     : 18
  liming 2 kTypeDeletion
  wandong 3 ktypeValue    : 85
  wandong 4 kTypeValue    : 30

内部按照key非递减,sequence非递增,kTypeValue非递增排序(保证kTypeDeletion在前面)进行排序(存储在SkipList中),那么在这些数据在skiplist中为:

  //userkey sequence type : value
  liming 2 kTypeDeletion
  liming 1 kTypeValue     : 18
   
  wandong 4 kTypeValue    : 30
  wandong 3 ktypeValue    : 85

这时我们获得了快照号4(最后更新的sequence号),我们只读取seqence <= 4的元素,如果查找table["wandong"], 那么找到wandong 4 kTypeValue : 30,可以获得table["wandong"]为 30,我们只取sequence最大的值作为数据的最新状态(自动忽略wandong 3 ktypeValue : 85), 如果查找table["liming"],找到liming的最新数据liming 2 kTypeDeletion,表示已经删掉了liming,则数据库中没有liming’.

如果之后又有更新比如:

  table["liming"] = 19
  table["wandong"]= 21
  table["jim"] = 30

这时skiplist中的内容变为:

  liming 5 kTypeValue     : 19
  liming 2 kTypeDeletion
  liming 1 kTypeValue     : 18
   
  jim    7 kTypeValue     : 30
   
  wandong 6 kTypeValue    : 21
  wandong 4 kTypeValue    : 30
  wandong 3 ktypeValue    : 85

这时我们查找table["liming"], 由于我们指读取sequence <= 4的数据所以我们会自动忽略新插入的元素liming 5 kTypeValue : 19’, jim 7 kTypeValue : 30,wandong 6 kTypeValue : 21, 所以我们snapshot为4时,数据库中仍没有liming, table[“wandong”] 为 30.

另外LevelDb中的SkipList中的指针为AtomicPointer,对它的读写操作是原子的。多线程写需要在外部加锁,因此最多只有一个线程写SkipList,

但是多个线程可以不用加锁并发的读取SkipList,Snapshot不关心新插入的key-value(因为他们的sequence>snapshot的sequence编号)。线程写SkipList的时候,仍然可以多线程读SkipList,并不会有问题。

赖明星 /
Published under (CC) BY-NC-SA in categories NoSql  tagged with levelDB  Snapshot