LevelDB学习之路--doc/index.html

Leveldb
Jeff Dean, Sanjay Ghemawat
Levaldb能够长期存储key-value类型的数据。key和value可以是任意的字节数组。根据用户指定的comparator funtion,key-value在存储的时候是有序的。 


Opening A Database

Leveldb database有一个相当于文件系统中的目录一样的名字。database中的所有
的内容都存在这个目录下。下面的举例演示如何打开一个数据库,如果数据库还没有创建的话需要先创建他:
#include
#include"leveldb/db.h"


leveldb::DB*db;
leveldb::Options options;
options.create_if_missing=true;
leveldb::Status status=leveldb::DB::Open(options,"/tmp/testdb",&db);
assert(status.ok());
...


如果这个database如果数据已经存在了,而你再次打开它,你想产生一个错误,需要在leveldb::DB::Open前加入一行:
options.error_if_exists=true;
(我的实验结果是无论你加不加这句,当你重复打开一个database的话都会报错。
错误提示是: 
IO error: LOCK /tmp/TestDB/LOCK: already held by precess)


Status
上面程序中使用到的leveldb::Status类型,是leveldb的很多函数的返回值。可以通过检查返回值判断调用是否成功,还开输出leveldb::Status存储的错误信息:
leveldb::Status s=...;
if(!s.ok())cerr<

Closing A Database

当你不打算使用database到了,记得关闭:
...open the db as described above...
...do something with db...
delete db;


Reads And Writes
database提供了Put,Delete,Get这些方法来修改/查询database。
下面例子,把key1中的value1存放到key2中,并且删除key1:
std::string value;
leveldb::Status s=db->Get(leveldb::ReadOptions(),key1,&value);
if(s.ok())s=db->Put(leveldb::WriteOptions(),key2,value);
if(s.ok())s=db->Delete(leveldb::WriteOptions(),key1);


Atomic Updates
注意到上面的例子,如果你的进程在执行完
Put(leveldb::WriteOptions(),key2,value)
之后挂掉了,那么接下的
Delete(leveldb::WriteOptons(),key1)
就没有执行。那么key1和key2中保留了相同的value值!(这可能不是你的本意!)
如果你使用WriteBatch class来完成上面一系列操作,就可以避免上述问题:
#include"leveldb/write_batch.h"
...
std::string value;
leveldb::Status s=db->Get(leveldb::ReadOptions(),key1,&value);
if(s.ok()){
leveldb::WriteBatch batch;
batch.Delete(key1);
batch.Put(key2,value);
s=db->Write(leveldb::WriteOptions(),&batch);
}
通过WriteBatch完成的一系列操作是按顺序执行的,上面是先调用的Delete之后才调用Put。这么做的好处是避免由于key1==key2而导致的误删除!
除了使用WriteBatch能够实现原子性操作以外!还可以把大量的不需要原子性的多个操作通过WriteBatch来实现,这样做的好处是可以加速大量操作的速度!(相对于不使 用WriteBatch而言!)


Synchronous Writes
对于leveldb的write是默认异步的:当你的进程在调用完write操作之后立马返回(这个阶段的write仅仅是将数据写入到operating system memory中)
而operating system memory到persistent storage的存储是异步的。
所以导致默认情况下,write操作都是异步的。
如果你想要同步的write操作,那么你需要设置sync flag。一旦你设置了这个flag,
那write操作只有在真正写入到persistent storage(应该是硬盘)之后才返回!
(在Pisix systems中,是通过调用fsnc(...)或者fdatasync(...)或者
msync(...,MS_SYNC)来实现的)。
leveldb::WriteOptions write_options;
write_options.sync=true;
db->Put(write_options,...);
异步写入比同步写入要快1000倍。但是在如果采用异步写入,机器一旦出错,最近更新的数据可能没来的及写入硬盘,就丢失了。(注意是机器故障,不是进程挂掉。如果 是进程挂掉,即使你采用的是异步写入,数据是不会丢失的。)
异步写入也可以在某种程度上认为是安全。例如当你往database中加载大量的数据时出错了,你可以重新开始加载啊!(我觉得作者一定是在逗我!!!这安全个蛋。)
另外一种复杂的机制是设定,每异步写入N个数据就将这N个数据同步将数据写入到硬盘中,这样在大量的往database中加载数据的时候挂掉了,那么你需要重新写入的数 据不超过N个!(嗯!!!这个还算差不多!)
WriteBatch也提供对异步和同步写入的选择!通过把write_options.sync设置为
true。


Concurrency
一个database只能够被一个进程打开一次!leveldb在实现的时候采用了系统层次的Lock来防止对leveldb的滥用!在一个多线程的进程中,多个线程可以安全的共用一个 leveldb::DB。不同的线程对leveldb::DB操作write,fetch iterators,Get的时候不用自己加上同步操作。(因为这些活leveldb实现的时候都干了!)
但是对于Iterator 和WriteBatch,你就必须自己加上同步操作!如果两个及以上的线程必须保证按照事先约定好的枷锁机制来共享Iterator和WriteBatch!
详细的请见public header files!
(好复杂。。。)


Iteration

下面举例演示如何打印出database中所有的key-value对!
leveldb::Iterator*it=db->NewIterator(leveldb::ReadOptions());
for(it->SeekToFirst();it->Valid();it->Next()){
cout<key().ToString()<<": "<value().ToString()< }
//Check for any errors found during the scan
assert(it->status().ok());
delete it;


下面演示如何仅对在[start,limit)范围内的key进行操作:
for(it->Seek(start);
it->Valid()&&it->key(),ToString() it->Next()){
...
}


你也可以逆序操作database中的记录:
(当然,通过reverse iteration来操作速度上肯定是慢一些)
for(it->SeekToLase();it->Valid();it->Prev()){
...
}


Snapshots
快照可以提供对整个database的一致性只读操作。
如果ReadOptions::snapshot是非空的,就表明是可以对某一个状态的database的快照操作!
如果ReadOptions::snapshot是空的。你又打算对快照操作,那么操作的快照就是当前状态的数据库!
快照是通过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 create...
delete iter;
db->ReleaseSnapshot(options.snapshot);
记得如果不在需要快照,记得释放!这样才可以让你从只读状态中解放出来!


Slice
上面的程序中,调用it->key()和it->value()的返回值类型是leveldb::Slice。
Slice是一个结构体,结构体内包含了一个指向数组的指针和一个表明数组长度的值。返回一个slice比返回一个std::string要廉价的多!因为std::string存在对key-value复制的 潜在开销!而且leveldb的方法是不会返回一个由'\0'结尾的
C-style的string,因为leveldb是允许key和value中存放'\0'这个字符的!


C++ string 和'\0'结尾的C-style string可以很方便的转换为Slice:
leveldb::Slice s1="hello";


std::string str("world");
leveldb::Slice s2=str;


一个Slice也可以很方便的转换为C++ string:
std::string str=s1.ToString();
assert(str==std::string("hello"));


当然对于Slice使用要小心。要确保Slice所指的数组是可访问的,要是已经被释放了,  使用Slice会出错:
leveldb::Slice slice;
if(...){
std::string str=...;
slice=str;
}
Use(slice);


Comparators

  也就是按照字典序来排。你可以提供一个自己的comparator,在打开database的时候作为参数转入。例如,一个database里面存储的key都是由两个数字组成的,我们定义 一个排序的规则,先按照第一个数字排序,如果第一个数字排序相同,就按照第二个数字来排序。接下来实现一个派生至leveldb::Comparator的comparator的子类:
  class TwoPartComparator:public leveldb::Comparator{
  public:
  //Three-way comparison function:
  // if a   // if a>b:positive result
  //else: zero result
  int Compar(const leveldb::Slice&a,
  const leveldb::Slice&b)const{
  int a1,a2,b1,b2;
  ParseKey(a,&a1,&a2);
  ParseKey(b,&b1,&b2);
  if(a1   if(a1>b1)return +1;
  if(a2   if(a2>b2)return +1;
  return 0;
  }


  //Ignore the following methods for now:
  const char*Name()const{return "TwoPartComparator";}
  void FindShortestSeparator(std::string*,
  const leveldb::Slice&)const{}
  void FindShortSuccessor(std::string*)const{}
  };
  使用上面定义的comparator创建一个database:
  TwoPartComparator cmp;
  leveldb::DB*db;
  leveldb::Options options;
  options.create_if_missing=true;
  options.comparator=&cmp;
  leveldb::Status status=leveldb::DB::Open(options,
  "/tmp/testdb",&db);
  ...


 Backwards compatibility
  comparator有一个Name方法,在你创建database的时候如果用到了comparator
  那么这个Name方法就与database息息相关了,在之后每次打开database都会检查这个Name方法。如果这个comparator发生了改变,那之后打开database会出错!
  所以,如果你非要改变comparator方法,最好是在这种情况下:
  新的key的格式和comparsion function不兼容,而且你已经做好丢弃目前
  database中所有的内容的准备!
  所以你最好对key的格式变化有一个提前的预案。比如,规定每个key的结尾后的数字是版本号。这样当你准备使用新版本的key格式时,你只要在
  TwoPartComparator的方法中加入一个新的参数表明版本号就行了:
  a)保证comparator的名字是不变的。
  b)更新新版本的key的版本号。
  c)更新comparator function的功能。


 Performance
  改变/include/leveldb/option.h中的默认值,就可以改变performance的性能。


  Block size
  leveldb把相邻的key聚集在同一个block中,这个block也是写入到硬盘上存储的最小单位。这个大小是4096字节。那些需要扫描大量数据到database的应用肯定希望block的 大小越大越好!可是那些read的操作量十分小,而多的应用肯定希望block的大小越小越好!所以block的大小如果大于兆字节,或者小于千字节,都不大好。对于block较大 的,压缩之后性能可能会提高!


  Compression
  每个block在写入到硬盘之前都是独立压缩的。压缩功能是非常便捷的,而且非常智能,对于不可压缩的数据,是不会进行压缩的。在很少的情况下,应用希望不要进行任 何压   缩,当然,最好确保不进行任何压缩的时候性能有所提高:
  leveldb::Options options;
  options.compression=leveldb::kNoCompression;
  ...leveldb::DB::Open(options,name,...)...


  Cache
  database的内容都是存储在文件系统的几个文件中,每个文件中存储的又是一串压缩的block。如果options.cache是非空的,那么他用来缓存那些需要经常使用到的未压缩 的block的内容。
  #include"leveldb/cache.h"
  leveldb::Options options;
  options.cache=leveldb::NewLRUCache(100*1048576);//100MB chche
  leveldb::DB*db;
  leveldb::DB::Open(options,name,&db);
  ...use the db...
  delete db;
  delete options.cache;
  cache的大小根据你应用的需求来分配。


  当你进行大量的读取操作时,应用可能希望放弃cache机制,免得应为内容不在cache而暂停(类似缺页中断):
  leveldb::ReadOptions options;
  options.fill_cache=false;
  leveldb::Iterator*it=db->NewIterator(options);
  for(it->SeekToFirst();it->Valid();it->Next()){
  ...
  }


  Key Layout

  cache和传送给硬盘的数据都是以block为单位。相邻的key通常会被存储在同一个block中。所以我们可以把经常需要一起存取的key-value存放在一起,把不经常且需要一起 存取的key-value分开才存储。
  例如,我们以leveldb为基础,实现了一个file system,我们存储在database中的数据是这样的:
  filename->permission-bits,length,list of file_block_id
  file_block_id->data
  我们希望在filename的key前加一个前缀字符'/',在file_block_id的key前加一个前缀字符'0',这样只扫描元数据即可,无需缓存大量的文件内容。


  Filters
  由于leveldb的数据在硬盘上的组织方式不同,一个Get()方法可能需要多次从硬盘中读取。选用FilterPolicy机制可以大体上减少不必要的读取操作。
  leveldb::Options options;
  options.filter_policy=NewBloomFilterPolicy(10);
  leveldb::DB*db;
  leveldb::DB::Open(options,"/tmp/testdb",&db);
  ...use the database...
  delete db;
  delete options.filter_policy;
  上面的代码中的过滤机制使用了Bloom filter算法,这个至少使得Get()的读取操作降低为原来的1/100。我们推荐那些工作集无法完全存放在内存中的,且需要经常进行随机 读取的应用,设置filter policy。


  如果你使用了自己定义的comparator,你也需要定义与自定义的comparator兼容的filter policy。例如,comparator在比较key的时候忽略key中末尾的空格。那么你filter policy 必须也忽略末尾的空格:
  class CustomFilterPolicy : public leveldb::FilterPolicy {
    private:
     FilterPolicy* builtin_policy_;
    public:
     CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }
     ~CustomFilterPolicy() { delete builtin_policy_; }


     const char* Name() const { return "IgnoreTrailingSpacesFilter"; }


     void CreateFilter(const Slice* keys, int n, std::string* dst) const {
       // Use builtin bloom filter code after removing trailing spaces
      std::vector trimmed(n);
       for (int i = 0; i < n; i++) {
         trimmed[i] = RemoveTrailingSpaces(keys[i]);
       }
       return builtin_policy_->CreateFilter(&trimmed[i], n, dst);
     }


     bool KeyMayMatch(const Slice& key, const Slice& filter) const {
       // Use builtin bloom filter code after removing trailing spaces
       return builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);
     }
   };
   有些应用可能不想使用Bloom filter算法,改用自己定义的算法!
   详情请见leveldb/filter_policy.h


Checksums
leveldb的checksum是和存储内容的file system有关的。这里提供两种方式的checksum:
ReadOptions::verify_checksums:
如果设置了这个标志为true,那么所有从file system中数据都要经过checksum.默认情况下没必要这么做。
Options::paranoid_checks:
如果这个标志设置为true,那么在打开database之前,检测到了一个内部的错误,他会马上抛出。根据出错的部分,有可能是在打开database的时候抛出错误,也有可能 是在之后的某个操作是抛出错误。默认的,也没有必要设置这个,这是为了保证错后任然能够运行。
出错后你可以调用leveldb::RepairDB来尽量恢复数据。


Approximate Sizes
调用GetApproximateSizes方法可以获取一个key范围内的数据在file system中占用的大致空间。
leveldb::Range ranges[2];
    ranges[0] = leveldb::Range("a", "c");
    ranges[1] = leveldb::Range("x", "z");
    uint64_t sizes[2];
    leveldb::Status s = db->GetApproximateSizes(ranges, 2, sizes);
    size[0]存储的是key在[a..c)这个范围内的数据的空间大小,size[1]存储的是[x..z)范围内的空间大小。


Environment
leveldb的实现中,所有的文件操作(以及其他系统调用)都是在leveldb::Evn中实现的。有些复杂的系统想要自己实现Env以获得更好的控制权。例如,有些应用想要人为的 推迟文件IO方面的操作,以此减少leveldb对系统中其他活动的影响。
class SlowEnv : public leveldb::Env {
    .. implementation of the Env interface ...
   };


   SlowEnv env;
   leveldb::Options options;
   options.env = &env;
   Status s = leveldb::DB::Open(options, ...);


Porting
leveldb可以移植到其他平台,只要修改了leveldb/port/port.h中内容就可以。详情请见leveldb/port/port_example.h
除此之外,移植到新的平台还需要实现leveldb::Env。详情请见leveldb/util/env_posix。

你可能感兴趣的:(C++)