<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>petermao的技术blog &#187; leveldb</title>
	<atom:link href="http://www.petermao.com/tag/leveldb/feed" rel="self" type="application/rss+xml" />
	<link>http://www.petermao.com</link>
	<description>欢迎探讨，共同进步</description>
	<lastBuildDate>Fri, 17 Feb 2017 07:03:09 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.1</generator>
		<item>
		<title>leveldb注释8&#8211;snapshot机制</title>
		<link>http://www.petermao.com/leveldb/leveldb-8-snapshot.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-8-snapshot</link>
		<comments>http://www.petermao.com/leveldb/leveldb-8-snapshot.html#comments</comments>
		<pubDate>Sun, 14 Jul 2013 12:55:54 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[snapshot]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=775</guid>
		<description><![CDATA[snapshot，也就是快照机制的作用，就是使得读操作不受写操作影响。其巧妙的使用了SequenceNumber来实现这一机制。 上一节中，我们知道，SequenceNumber使得对写入的任何key在内部存储时都会附加一个递增的SequenceNumber。而在leveldb内部，一次snapshot也对应一个全局唯一的SequenceNumber。因此，在调用GetSnapshot获取snapshot时，leveldb所做的仅仅是生成一个SequenceNumber。db内部使用双向循环链表保存先后生成的snapshot。获取快照GetSnapshot会将新snapshot加入链表，而释放快照ReleaseSnapshot则将snapshot从链表中删除。DBImpl类中的snapshots_成员就是这个链表。这部分代码主要在snapshot.h与db_impl.cc/.h中。代码很简单，内部是一个双向循环链表的封装。 const Snapshot* DBImpl::GetSnapshot() { MutexLock l(&#38;mutex_); return snapshots_.New(versions_-&#62;LastSequence()); } snapshot机制主要会影响指定的读操作与所有后续的压缩操作，直到该snapshot释放。 从前面的使用接口章节中，我们知道，当读操作Get函数指定snapshot选项时，从snapshot生成至Get函数调用的这段时间里的写入操作对该次读没有影响。当指定snapshot时，Get函数会使用这个snapshot的sequencenumber+输入的key来构造要查找的LookupKey（没有指定snapshot，则使用当前已使用的最大sequencenumber）。而对于要查找的数据结构memtable、immunable memtable与sstable来讲，其内部存储的&#8221;key&#8221;都是带sequencenumber的（上一章节的分析），其最终查找到的&#8221;key&#8221;会满足key == 输入的key，而sequencenumber]]></description>
			<content:encoded><![CDATA[<p>snapshot，也就是快照机制的作用，就是使得读操作不受写操作影响。其巧妙的使用了SequenceNumber来实现这一机制。</p>
<p>上一节中，我们知道，SequenceNumber使得对写入的任何key在内部存储时都会附加一个递增的SequenceNumber。而在leveldb内部，一次snapshot也对应一个全局唯一的SequenceNumber。因此，在调用GetSnapshot获取snapshot时，leveldb所做的仅仅是生成一个SequenceNumber。db内部使用双向循环链表保存先后生成的snapshot。<span id="more-775"></span>获取快照GetSnapshot会将新snapshot加入链表，而释放快照ReleaseSnapshot则将snapshot从链表中删除。DBImpl类中的snapshots_成员就是这个链表。这部分代码主要在snapshot.h与db_impl.cc/.h中。代码很简单，内部是一个双向循环链表的封装。</p>
<pre class="wp-code-highlight prettyprint">
const Snapshot* DBImpl::GetSnapshot() {
  MutexLock l(&amp;mutex_);
  return snapshots_.New(versions_-&gt;LastSequence());
}
</pre>
<p>snapshot机制主要会影响指定的读操作与所有后续的压缩操作，直到该snapshot释放。</p>
<p>从前面的使用接口章节中，我们知道，当读操作Get函数指定snapshot选项时，从snapshot生成至Get函数调用的这段时间里的写入操作对该次读没有影响。当指定snapshot时，Get函数会使用这个snapshot的sequencenumber+输入的key来构造要查找的LookupKey（没有指定snapshot，则使用当前已使用的最大sequencenumber）。而对于要查找的数据结构memtable、immunable memtable与sstable来讲，其内部存储的&#8221;key&#8221;都是带sequencenumber的（上一章节的分析），其最终查找到的&#8221;key&#8221;会满足key == 输入的key，而sequencenumber<=Get函数传入的sequencenumber。这样，对于那些key相同但sequencenumber比较大（也就是后续针对同一个key做写入操作）的slice，则查找不到，自动的被屏蔽掉了。关于读操作Get的详细细节，我们在后续章节进行分析。</p>
<pre class="wp-code-highlight prettyprint">
Status DBImpl::Get(const ReadOptions&amp; options,
                   const Slice&amp; key,
                   std::string* value) {
  Status s;
  MutexLock l(&amp;mutex_);
  SequenceNumber snapshot;
  if (options.snapshot != NULL) {
    snapshot = reinterpret_cast&lt;const SnapshotImpl*&gt;(options.snapshot)-&gt;number_;
  } else {
    snapshot = versions_-&gt;LastSequence();
  }

  MemTable* mem = mem_;
  MemTable* imm = imm_;
  Version* current = versions_-&gt;current();
  mem-&gt;Ref();
  if (imm != NULL) imm-&gt;Ref();
  current-&gt;Ref();
  &#8212;
  {
    mutex_.Unlock();
    // mem与imm是带sequence_number以保证不会查到新数据
    // 查找顺序依次是：memtable、immutable memtable、sstable文件
    // First look in the memtable, then in the immutable memtable (if any).
    LookupKey lkey(key, snapshot);
    if (mem-&gt;Get(lkey, value, &amp;s)) {
      // Done
    } else if (imm != NULL &amp;&amp; imm-&gt;Get(lkey, value, &amp;s)) {
      // Done
    } else {
      s = current-&gt;Get(options, lkey, value, &amp;stats);
      have_stat_update = true;
    }
    &#8212;
  }
</pre>
<p>leveldb的snapshot机制不会在snapshot生成时直接影响系统，而是通过影响压缩操作来影响。比如redis的快照机制，其生成快照时，相当于dump整个内存中的db。<br />
通过前面的Get分析，我们知道，因为SequenceNumber对查找的影响，使得后续的写是完全透明的。但compact操作会将写入的值与已有的值进行合并。这样，后续写入的值可能会覆盖前面已写过的值或者后续的删除操作删除了该key。实际上，在进行major compact时（minor compact只是将immunable memtable转储至1个level 0的sstable文件），会检查这些(key,value)的sequencenumber。当有相同key的多个操作序列时，正常情况下，只会按操作第一个序列（也就是最新的），后续的直接drop。但snapshot机制，会使得对于这些多个操作序列的除第一个之外的操作，只有sequencenumber比系统中最小的snapshot还小的才丢弃（实际细节比这个还复杂点）。这样，这些操作序列都会被保存下来用于后续的snapshot操作。这部分代码主要在DBImpl::DoCompactionWork函数中，更多细节有待我们讲述压缩机制时再来分析。</p>
<pre class="wp-code-highlight prettyprint">
Status DBImpl::DoCompactionWork(CompactionState* compact) {
   ---
   if (snapshots_.empty()) {
    compact-&gt;smallest_snapshot = versions_-&gt;LastSequence();
  } else {
    compact-&gt;smallest_snapshot = snapshots_.oldest()-&gt;number_;
  }
  ---
  Iterator* input = versions_-&gt;MakeInputIterator(compact-&gt;compaction);
  input-&gt;SeekToFirst();
  Status status;
  ParsedInternalKey ikey;
  std::string current_user_key;
  bool has_current_user_key = false;
  SequenceNumber last_sequence_for_key = kMaxSequenceNumber;
  // 遍历输入
  for (; input-&gt;Valid() &amp;&amp; !shutting_down_.Acquire_Load(); ) {
    // Prioritize immutable compaction work
    if (has_imm_.NoBarrier_Load() != NULL) {
      ---
    }

    Slice key = input-&gt;key();
    if (compact-&gt;compaction-&gt;ShouldStopBefore(key) &amp;&amp;
        compact-&gt;builder != NULL) {
        // 输出新文件
      status = FinishCompactionOutputFile(compact, input);
      if (!status.ok()) {
        break;
      }
    }

    // Handle key/value, add to state, etc.
    bool drop = false;
    // 因为有delete、多次插入，同一个key可能出现多次
    if (!ParseInternalKey(key, &amp;ikey)) {
      // Do not hide error keys
      current_user_key.clear();
      has_current_user_key = false;
      last_sequence_for_key = kMaxSequenceNumber;
    } else {
      if (!has_current_user_key ||
         // 注意这里的比较是直接比较user_key，也就是不带sequencenumber
          user_comparator()-&gt;Compare(ikey.user_key,
                                     Slice(current_user_key)) != 0) {
        // First occurrence of this user key
        current_user_key.assign(ikey.user_key.data(), ikey.user_key.size());
        has_current_user_key = true;
        last_sequence_for_key = kMaxSequenceNumber;
      }

      if (last_sequence_for_key &lt;= compact-&gt;smallest_snapshot) {
        // Hidden by an newer entry for same user key
        // 多个key，后续的是更老的，直接drop，保留第一个
        drop = true;    // (A)
      } else if (ikey.type == kTypeDeletion &amp;&amp;
                 ikey.sequence &lt;= compact-&gt;smallest_snapshot &amp;&amp;
                 compact-&gt;compaction-&gt;IsBaseLevelForKey(ikey.user_key)) {
        // For this user key:
        // (1) there is no data in higher levels
        // (2) data in lower levels will have larger sequence numbers
        // (3) data in layers that are being compacted here and have
        //     smaller sequence numbers will be dropped in the next
        //     few iterations of this loop (by rule (A) above).
        // Therefore this deletion marker is obsolete and can be dropped.
        drop = true;
      }

      last_sequence_for_key = ikey.sequence;
    }
    ---
</pre>
<p>由于snapshot对压缩机制有影响，对snapshot后续插入的多个值都不会压缩。因此在使用时，用完后要尽可能快释放。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-8-snapshot.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>leveldb注释7–key与value</title>
		<link>http://www.petermao.com/leveldb/leveldb-7-key-value.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-7-key-value</link>
		<comments>http://www.petermao.com/leveldb/leveldb-7-key-value.html#comments</comments>
		<pubDate>Sun, 14 Jul 2013 03:04:51 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[key]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=768</guid>
		<description><![CDATA[作为一个kv的系统，key的存储至关重要。在leveldb中，主要涉及到如下几个key，user_key、InternalKey与LookupKey(memtable_key)。 其关系构成如下图。user_key就是用户输入的key，而InternalKey在user_key的基础上封装了sequence_num+type。sequence_num是一个全局递增的序列号，每一次Put操作都会递增。这样，不同时间的写入操作会得到不一样的sequence_num。前面章节中提到的sstable单条record中的key，其内部其实就是一个InternalKey。sequence_num主要跟snapshot机制与version机制相关，对压缩会产生一定影响。这些我们在后续章节分析。根据type字段，可以获知本次写入操作是写还是删除（也就是说删除是一种特殊的写）。而LookupKey/memtable_key用于在memtable中，多了一个长度字段。代码主要在dbformat.cc/.h中。 static uint64_t PackSequenceAndType(uint64_t seq, ValueType t) { assert(seq &#60;= kMaxSequenceNumber); assert(t &#60;= kValueTypeForSeek); return (seq &#60;&#60; \8) &#124; t; } skiplist中的单个节点不仅存储了Key，也存储了value。格式如下图。尽管单个节点的开头部分是一个LookupKey，但其内部比较时，还是使用的InternalKey。也就是说，比较时，先使用InternalKey内部的user_key进行比较，再比较sequence_num。这样不管是memtable还是sstable文件，其内部都是按InternalKey有序的。 int InternalKeyComparator::Compare(const Slice&#38; akey, const Slice&#38; bkey) const { // Order by: // increasing user key (according &#8230; <a href="http://www.petermao.com/leveldb/leveldb-7-key-value.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>作为一个kv的系统，key的存储至关重要。在leveldb中，主要涉及到如下几个key，user_key、InternalKey与LookupKey(memtable_key)。<br />
其关系构成如下图。user_key就是用户输入的key，而InternalKey在user_key的基础上封装了sequence_num+type。sequence_num是一个全局递增的序列号，每一次Put操作都会递增。这样，不同时间的写入操作会得到不一样的sequence_num。前面章节中提到的sstable单条record中的key，其内部其实就是一个InternalKey。sequence_num主要跟snapshot机制与version机制相关，对压缩会产生一定影响。这些我们在后续章节分析。根据type字段，可以获知本次写入操作是写还是删除（也就是说删除是一种特殊的写）。而LookupKey/memtable_key用于在memtable中，多了一个长度字段。代码主要在dbformat.cc/.h中。<span id="more-768"></span><br />
<div id="attachment_797" class="wp-caption alignnone" style="width: 485px"><a href="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-lookup-key.png"><img src="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-lookup-key.png" alt="leveldb lookup key" title="leveldb lookup key" width="475" height="230" class="size-full wp-image-797" /></a><p class="wp-caption-text">leveldb lookup key</p></div></p>
<pre class="wp-code-highlight prettyprint">
static uint64_t PackSequenceAndType(uint64_t seq, ValueType t) {
  assert(seq &lt;= kMaxSequenceNumber);
  assert(t &lt;= kValueTypeForSeek);
  return (seq &lt;&lt;  \8) | t;
}
</pre>
<p>skiplist中的单个节点不仅存储了Key，也存储了value。格式如下图。尽管单个节点的开头部分是一个LookupKey，但其内部比较时，还是使用的InternalKey。也就是说，比较时，先使用InternalKey内部的user_key进行比较，再比较sequence_num。这样不管是memtable还是sstable文件，其内部都是按InternalKey有序的。<br />
<div id="attachment_799" class="wp-caption alignnone" style="width: 631px"><a href="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-skiplist.png"><img src="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-skiplist.png" alt="leveldb skiplist node" title="leveldb skiplist node" width="621" height="72" class="size-full wp-image-799" /></a><p class="wp-caption-text">leveldb skiplist node</p></div></p>
<pre class="wp-code-highlight prettyprint">
int InternalKeyComparator::Compare(const Slice&amp; akey, const Slice&amp; bkey) const {
  // Order by:
  //    increasing user key (according to user-supplied comparator)
  //    decreasing sequence number
  //    decreasing type (though sequence# should be enough to disambiguate)
  int r = user_comparator_-&gt;Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
  if (r == 0) {
    const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);
    const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);
    if (anum &gt; bnum) {
      r = -1;
    } else if (anum &lt; bnum) {
      r = +1;
    }
  }
  return r;
}
</pre>
<p>在进行Get操作时，leveldb会使用用户传入的user_key与当前db最大的sequence_num进行合并，以得到LookupKey（实际还会受snapshot机制影响）。但在内部查找时还是使用的InternalKey。</p>
<p>Put操作会稍微复杂点。leveldb的3种写入操作最终都会封装成WriteBatch。这3种写入操作分别是Put(写入单条key<br />
/value)、Delete(删除单条key)、Write(批量进行Put与Delete操作)。WriteBath的内部格式如下图。WriteBatch所表示的多条记录最终会按SkipList所要求的格式1条条地顺序插入到memtable中。<br />
<div id="attachment_801" class="wp-caption alignnone" style="width: 631px"><a href="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-writebatch.png"><img src="http://www.petermao.com/wp-content/uploads/2013/11/leveldb-writebatch.png" alt="leveldb writebatch" title="leveldb writebatch" width="621" height="141" class="size-full wp-image-801" /></a><p class="wp-caption-text">leveldb writebatch</p></div></p>
<pre class="wp-code-highlight prettyprint">
Status WriteBatchInternal::InsertInto(const WriteBatch* b,
                                      MemTable* memtable) {
  MemTableInserter inserter;
  // memtable 的初始sequence为WriteBatchInternal中的seq
  inserter.sequence_ = WriteBatchInternal::Sequence(b);
  inserter.mem_ = memtable;
  return b-&gt;Iterate(&amp;inserter);
}

Status WriteBatch::Iterate(Handler* handler) const {
  Slice input(rep_);
  if (input.size() &lt; kHeader) {
    return Status::Corruption(&quot;malformed WriteBatch (too small)&quot;);
  }
  // kHeader=12 = seq(8字节) + count(4字节)
  input.remove_prefix(kHeader);
  Slice key, value;
  int found = 0;
  while (!input.empty()) {
    found++;
    char tag = input[0];
    // type:1字节
    input.remove_prefix(1);
    switch (tag) {
      case kTypeValue:
        if (GetLengthPrefixedSlice(&amp;input, &amp;key) &amp;&amp;
            GetLengthPrefixedSlice(&amp;input, &amp;value)) {
          handler-&gt;Put(key, value);
        } else {
          return Status::Corruption(&quot;bad WriteBatch Put&quot;);
        }
        break;
      case kTypeDeletion:
        if (GetLengthPrefixedSlice(&amp;input, &amp;key)) {
          handler-&gt;Delete(key);
        } else {
          return Status::Corruption(&quot;bad WriteBatch Delete&quot;);
        }
        break;
      default:
        return Status::Corruption(&quot;unknown WriteBatch tag&quot;);
    }
  }
  if (found != WriteBatchInternal::Count(this)) {
    return Status::Corruption(&quot;WriteBatch has wrong count&quot;);
  } else {
    return Status::OK();
  }
}
</pre>
<p>关于读写压缩等对key/value的具体操作，我们在后续章节进行分析，这里只需要了解大概。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-7-key-value.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>leveldb注释6–log文件</title>
		<link>http://www.petermao.com/leveldb/leveldb-6-log.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-6-log</link>
		<comments>http://www.petermao.com/leveldb/leveldb-6-log.html#comments</comments>
		<pubDate>Fri, 12 Jul 2013 08:45:35 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[leveldb log]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=542</guid>
		<description><![CDATA[在leveldb中，除了sstable文件格式，还有log文件格式。该文件格式用于存储写操作的日志与manifest文件（不同的文件名）。前者用于异常回滚。后者用于记录sstable文件的元数据。整体架构中提到过，leveldb在将记录写入内存中的memtable之前，会先写入log文件，memtable会延后持久化。在这个过程中进程可能down掉。有了log写操作文件后，即使系统发生故障，levelDB也可以根据log写操作日志文件恢复内存的memtable内容，不会造成丢失数据。而manifest文件用于记录所有的sstable文件的元数据，比如sstable文件的编号，key范围。 log文件格式也是分块存储的。跟sstable文件不同的是，一方面sstable是有序存储的，因此为了加速读取，有相关索引，而log文件始终是顺序读写的，不需要定位某个key，因而不需要索引信息。另一方面sstable的data block虽大致按4KB分块，但实际上存储的块大小通常会比4KB大，这主要是因为单条record不会跨block。而log文件中的block则严格保证为一样，默认值为32KB。因此，log文件中的record可能会跨块，为了区分record是否结束，不同的block有不同的类型（kFullType/kFirstType/kMiddleType/kLastType）。如果block剩余的空间足以存储新的record，则type取值为kFullType，否则则有可能出现1个FirstType的block（剩余部分block存储record的部分内容）+ 0或者多个kMiddleType的block + 1个kLastType的block。除了type字段之外，record还包括check sum与数据的长度，最后才是具体的数据。校验和针对type+length+data。详细的格式如下图所示。如果剩余block的空间]]></description>
			<content:encoded><![CDATA[<p>在leveldb中，除了sstable文件格式，还有log文件格式。该文件格式用于存储写操作的日志与manifest文件（不同的文件名）。前者用于异常回滚。后者用于记录sstable文件的元数据。整体架构中提到过，leveldb在将记录写入内存中的memtable之前，会先写入log文件，memtable会延后持久化。在这个过程中进程可能down掉。有了log写操作文件后，即使系统发生故障，levelDB也可以根据log写操作日志文件恢复内存的memtable内容，不会造成丢失数据。而manifest文件用于记录所有的sstable文件的元数据，比如sstable文件的编号，key范围。<span id="more-542"></span></p>
<p>log文件格式也是分块存储的。跟sstable文件不同的是，一方面sstable是有序存储的，因此为了加速读取，有相关索引，而log文件始终是顺序读写的，不需要定位某个key，因而不需要索引信息。另一方面sstable的data block虽大致按4KB分块，但实际上存储的块大小通常会比4KB大，这主要是因为单条record不会跨block。而log文件中的block则严格保证为一样，默认值为32KB。因此，log文件中的record可能会跨块，为了区分record是否结束，不同的block有不同的类型（kFullType/kFirstType/kMiddleType/kLastType）。如果block剩余的空间足以存储新的record，则type取值为kFullType，否则则有可能出现1个FirstType的block（剩余部分block存储record的部分内容）+ 0或者多个kMiddleType的block + 1个kLastType的block。除了type字段之外，record还包括check sum与数据的长度，最后才是具体的数据。校验和针对type+length+data。详细的格式如下图所示。如果剩余block的空间<7（checksum+length+type），则剩余的直接填充0，另起一块存储数据。leveldb严格按照32k为一个单位读写一个block。<br />
<div id="attachment_757" class="wp-caption alignnone" style="width: 440px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-log1.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-log1.png" alt="leveldb log" title="leveldb log" width="430" height="110" class="size-full wp-image-757" /></a><p class="wp-caption-text">leveldb log</p></div><br />
doc/log_format.txt中讲述了这种文件格式的benefit与downside。<br />
好处如下：<br />
（1）容错性好。不需要额外的信息来同步。发现出错了，直接跳至下一个block（32K为一个block）。<br />
（2）容易切分，适合mapreduce等大数据处理方式。切分时，需按逻辑上的一个Record。<br />
（3）对于大记录，也不需要额外的字段来表示其长度。自然的按32k切分了嘛。<br />
一些限制如下：<br />
（1）对于小记录，没有pack机制。<br />
（2）没有压缩。<br />
对于这些限制，作者认为可以通过添加新的type来实现。</p>
<p>代码主要在log_format.h，log_reader.cc/.h，log_writer.cc/.h。前者定义了一些，log::Writer类用于写，比较简单，log::Reader考虑到容错，稍微复杂些。但整体上还是很简单的，给点耐心看吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-6-log.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>leveldb注释5&#8211;sstable文件</title>
		<link>http://www.petermao.com/leveldb/leveldb-5-sstable.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-5-sstable</link>
		<comments>http://www.petermao.com/leveldb/leveldb-5-sstable.html#comments</comments>
		<pubDate>Fri, 12 Jul 2013 03:25:10 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[sstable]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=490</guid>
		<description><![CDATA[本节主要介绍sstable文件的格式及单个sstable文件的get/put。sstable文件全称是sorted string table，是一个有序且带索引的文件。 从前面的分析可知，level 0级的sstable文件是由内存中的immunable memtable经过minor compact形成的(也就是直接dump)，而level>0级的文件是由level-1级别的文件经过major compact形成的。关于compact过程，我们在后续章节分析。这里着重于分析单个sstable文件的相关接口，代码在table/目录。 下图是sstable文件的整体结构。整体上，sstable文件分为数据区与索引区，尾部的footer指出了meta index block与data index block的偏移与大小，data index block指出了各data block的偏移与大小，meta index block指出了各meta block的偏移与大小。 先看footer结构。如下图。footer位于sstable文件尾部，占用空间固定为48个字节。其末尾8个字节是一个magic_number。metaindex_handle与index_handle物理上占用了40个字节，但实际上存储可能连32字节都不到。每一个handle的结构BlockHandle如右图，逻辑上分别表示offset+size，在内存中占用16个字节，但存储时由于采用可变长度编码，每个handle的物理存储通常不到8+8字节。因此这里两个handle总共占用不到32个字节，剩余填充0。 BlockHandle指出了block的偏移与大小。在sstable文件中，一般有多个data block，多个meta block(当前版本只有一个filter block，可扩充)，1个meta index block，1个data index block。其中filter block的内部结构稍微不同于其他Block，但都是用BlockHandle来指向的。Footer与BlockHandle的代码主要format.cc/.h。其中Footer封装了footer的结构，BlockHandle对应图中的BlockHandle，另外有BlockContent表示从Block中实际读入的整个Block的字符串，而ReadBlock全局函数通过BlockHandle(指出了Block的偏移与大小)从指定文件中读取Block内容，并通过BlockContent返回。读取时可能需要校验crc32并解压。ReadBlock部分代码如下： Status ReadBlock(RandomAccessFile* file, const ReadOptions&#38; options, const BlockHandle&#38; handle, BlockContents* result) &#8230; <a href="http://www.petermao.com/leveldb/leveldb-5-sstable.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>本节主要介绍sstable文件的格式及单个sstable文件的get/put。sstable文件全称是sorted string table，是一个有序且带索引的文件。</p>
<p>从前面的分析可知，level 0级的sstable文件是由内存中的immunable memtable经过minor compact形成的(也就是直接dump)，而level>0级的文件是由level-1级别的文件经过major compact形成的。关于compact过程，我们在后续章节分析。这里着重于分析单个sstable文件的相关接口，代码在table/目录。<span id="more-490"></span></p>
<p>下图是sstable文件的整体结构。整体上，sstable文件分为数据区与索引区，尾部的footer指出了meta index block与data index block的偏移与大小，data index block指出了各data block的偏移与大小，meta index block指出了各meta block的偏移与大小。<br />
<a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-sstable.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-sstable.png" alt="sstable文件" title="leveldb sstable" width="397" height="252" class="alignnone size-full wp-image-719" /></a><br />
先看footer结构。如下图。footer位于sstable文件尾部，占用空间固定为48个字节。其末尾8个字节是一个magic_number。metaindex_handle与index_handle物理上占用了40个字节，但实际上存储可能连32字节都不到。每一个handle的结构BlockHandle如右图，逻辑上分别表示offset+size，在内存中占用16个字节，但存储时由于采用可变长度编码，每个handle的物理存储通常不到8+8字节。因此这里两个handle总共占用不到32个字节，剩余填充0。<br />
<div id="attachment_723" class="wp-caption alignnone" style="width: 721px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-footer.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-footer.png" alt="leveldb footer + block handle" title="leveldb footer" width="711" height="159" class="size-full wp-image-723" /></a><p class="wp-caption-text">leveldb footer + block handle</p></div><br />
BlockHandle指出了block的偏移与大小。在sstable文件中，一般有多个data block，多个meta block(当前版本只有一个filter block，可扩充)，1个meta index block，1个data index block。其中filter block的内部结构稍微不同于其他Block，但都是用BlockHandle来指向的。Footer与BlockHandle的代码主要format.cc/.h。其中Footer封装了footer的结构，BlockHandle对应图中的BlockHandle，另外有BlockContent表示从Block中实际读入的整个Block的字符串，而ReadBlock全局函数通过BlockHandle(指出了Block的偏移与大小)从指定文件中读取Block内容，并通过BlockContent返回。读取时可能需要校验crc32并解压。ReadBlock部分代码如下：</p>
<pre class="wp-code-highlight prettyprint">
Status ReadBlock(RandomAccessFile* file,
                 const ReadOptions&amp; options,
                 const BlockHandle&amp; handle,
                 BlockContents* result) {
  result-&gt;data = Slice();
  result-&gt;cachable = false;
  result-&gt;heap_allocated = false;

  // Read the block contents as well as the type/crc footer.
  // See table_builder.cc for the code that built this structure.
  size_t n = static_cast&lt;size_t&gt;(handle.size());
  char* buf = new char[n + kBlockTrailerSize];
  Slice contents;
  Status s = file-&gt;Read(handle.offset(), n + kBlockTrailerSize, &amp;contents, buf);
  if (!s.ok()) {
    delete[] buf;
    return s;
  }
  if (contents.size() != n + kBlockTrailerSize) {
    delete[] buf;
    return Status::Corruption(&quot;truncated block read&quot;);
  }

  // Check the crc of the type and the block contents
  const char* data = contents.data();    // Pointer to where Read put the data
  // crc32校验
  if (options.verify_checksums) {
    const uint32_t crc = crc32c::Unmask(DecodeFixed32(data + n + 1));
	// data + type + crc
    const uint32_t actual = crc32c::Value(data, n + 1);
    if (actual != crc) {
      delete[] buf;
      s = Status::Corruption(&quot;block checksum mismatch&quot;);
      return s;
    }
  }

  switch (data[n]) {
    case kNoCompression:
      ---
      // Ok
      break;
    // snappy压缩
    case kSnappyCompression: {
      size_t ulength = 0;
      if (!port::Snappy_GetUncompressedLength(data, n, &amp;ulength)) {
        delete[] buf;
        return Status::Corruption(&quot;corrupted compressed block contents&quot;);
      }
      char* ubuf = new char[ulength];
      if (!port::Snappy_Uncompress(data, n, ubuf)) {
        delete[] buf;
        delete[] ubuf;
        return Status::Corruption(&quot;corrupted compressed block contents&quot;);
      }
      delete[] buf;
      result-&gt;data = Slice(ubuf, ulength);
      result-&gt;heap_allocated = true;
      result-&gt;cachable = true;
      break;
    }
    default:
      ---
  }
  ---
}
</pre>
<p>前面通过ReadBlock读取的BlockContent内容一般通过new Block(BlockContent)初始化为具体的Block对象。现在我们看看Block的内部结构，如下图。逻辑上主要分为数据与重启点。重启点也是一个指针，指出了一些特殊的位置。data block中的key是有序存储的，相邻的key之间可能有重复，因此存储时采用前缀压缩，后一个key只存储与前一个key不同的部分。那些重启点指出的位置就表示该key不按前缀压缩，而是完整存储该key。除了减少压缩空间之外，重启点的第二个作用就是加速读取。如果说data index block可以通过二分来定位具体的block，那么重启点则可以通过二分的方法来定位具体的重启点位置，进一步减少了需要读取的数据。对于leveldb来讲，可以通过options.block_size与options.block_restart_interval来设置block的大小与重启点的间隔。默认data block的大小为4K。而重启点则每隔16个key。具体的单条record的存储格式如下图所示。<br />
<div id="attachment_726" class="wp-caption alignnone" style="width: 469px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block1.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block1.png" alt="leveldb block" title="leveldb block" width="459" height="393" class="size-full wp-image-726" /></a><p class="wp-caption-text">leveldb block</p></div><br />
<div id="attachment_729" class="wp-caption alignnone" style="width: 656px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key.png" alt="leveldb单条记录存储格式" title="leveldb key" width="646" height="81" class="size-full wp-image-729" /></a><p class="wp-caption-text">leveldb单条记录存储格式</p></div><br />
在进一步分析Block代码之前，我们先来搞清楚这样一个问题。前面提到，data block与meta index block、data index block都是采用block来存储的(filter block稍微不同)。而对于block来讲，其都是按(key,value)格式存储一条条的record的。对于这些不同类型的block，其(key,value)都是什么了？总结如下图。现在只有一个meta block用于filter，因此meta index block中也只有一条记录，其key是filter. + filter_policy的name。<br />
<div id="attachment_732" class="wp-caption alignnone" style="width: 578px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key-of-block.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-key-of-block.png" alt="不同block的(key,value)对" title="leveldb key of block" width="568" height="140" class="size-full wp-image-732" /></a><p class="wp-caption-text">不同block的(key,value)对</p></div><br />
block的具体实现在block.cc/.h与block_builder.cc/.h中。前者用于读，后者用于写。读取时，一般先通过前面提到的ReadBlock读取一块至BlockContent中，然后再通过Block的构造函数初始化，并通过Block::Iter子类用于外部遍历或者定位具体某个key。Block类代码简单。Block::Iter的代码内部结构如下图所示。data_指向读入的BlockContent内容，current_指向当前的value，restart_则指出了重启点数据的起始位置，restart_index则对应当前value的重启点索引。查找时，先通过restart_index指向的value定位，找到相应的重启点后，再在重启点内部遍历。顺序遍历整个block时，需要注意修改restart_index_。代码细节请自行阅读。<br />
<div id="attachment_735" class="wp-caption alignnone" style="width: 502px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block_iter.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-block_iter.png" alt="leveldb Block::Iter" title="leveldb Block::Iter" width="492" height="140" class="size-full wp-image-735" /></a><p class="wp-caption-text">leveldb Block::Iter</p></div><br />
写block，则通过block_builder.cc/.h中的BlockBuilder类来完成。一般是通过多次调用Add方法，再Finish结束这个block。BlockBuilder的内部结构如下图。由于block的重启点位于block尾部，因此需要先保存这些重启点，Finish时再顺序写入。图中的restarts_ vector就是用来保存各个重启点偏移的。counter_用于计数，当超过重启间隔时，需开启一个新的重启点。最后Finish时顺序写入这些重启点。代码也很简单。<br />
<div id="attachment_738" class="wp-caption alignnone" style="width: 398px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-blockbuilder.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-blockbuilder.png" alt="leveldb BlockBuilder" title="leveldb blockbuilder" width="388" height="140" class="size-full wp-image-738" /></a><p class="wp-caption-text">leveldb BlockBuilder</p></div><br />
现在我们再来看看稍微特殊的FilterBlock。FilterBlock内部不像Block有复杂的(key,value)格式，其只有一个字符串。该字符串的内部结构如下图。filterblock保存多个filter string，每个filter string针对一段block，间隔为2^base_lg_，保存在末尾，默认值为2KB，也就是说，filter block每隔2kb block offset的key生成一个filter string，offset_array_指出了这些filter string的偏移。过滤时，一般先通过data index block获取key的大致block offset，再通过filter string的offset_array_获取该block offset的filter string，再进行过滤。生成时，对同一个block offset范围的数据一起构建filter string。具体代码细节在filter_block.cc/.h中。读写分别用FilterBlockReader与FilterBlockBuilder来封装。<br />
<div id="attachment_740" class="wp-caption alignnone" style="width: 620px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-filter-block.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-filter-block.png" alt="leveldb filter block" title="leveldb filter block" width="610" height="116" class="size-full wp-image-740" /></a><p class="wp-caption-text">leveldb filter block</p></div><br />
最后我们来看看table类。table类表示了整个sstable文件。外部通过这个类访问sstable，table内部访问block/meta block。</p>
<p>外部使用时，主要有两类接口。一类是读，另一类是写。读接口在table.cc/.h中实现，写接口在table_builder.cc/.h中实现。读接口的调用顺序为，Open打开一个sst文件，然后NewIterator返回一个迭代器用于遍历整个table，或者调用InternalGet用于Seek某个key。这些接口一般通过table_cache类来调用。table_cache是table的缓存。table_cache会先查看是否有cache，没有则读table文件，写cache。table_cache的具体细节我们在Get章节中分析。写接口的调用顺序为，TableBuilder构造函数初始化&#8211;>多次调用Add(key,value)&#8211;>Finish/Abandon(异常退出)结束。InternalGet的主要代码如下。内部会先查询filter与BlockCache。</p>
<pre class="wp-code-highlight prettyprint">
Status Table::InternalGet(const ReadOptions&amp; options, const Slice&amp; k,
                          void* arg,
                          void (*saver)(void*, const Slice&amp;, const Slice&amp;)) {
  Status s;
  Iterator* iiter = rep_-&gt;index_block-&gt;NewIterator(rep_-&gt;options.comparator);
  //先定位该值的offset
  iiter-&gt;Seek(k);
  if (iiter-&gt;Valid()) {
    Slice handle_value = iiter-&gt;value();
    FilterBlockReader* filter = rep_-&gt;filter;
    BlockHandle handle;
	// 根据该offset查询filter
    if (filter != NULL &amp;&amp;
        handle.DecodeFrom(&amp;handle_value).ok() &amp;&amp;
        !filter-&gt;KeyMayMatch(handle.offset(), k)) {
      // Not found
    } else {
      Slice handle = iiter-&gt;value();
      // 没被过滤，表示该key可能存在，需要读取对应的block
      Iterator* block_iter = BlockReader(this, options, iiter-&gt;value());
      block_iter-&gt;Seek(k);
      if (block_iter-&gt;Valid()) {
        (*saver)(arg, block_iter-&gt;key(), block_iter-&gt;value());
      }
      s = block_iter-&gt;status();
      delete block_iter;
    }
  }
  ---
}

Iterator* Table::BlockReader(void* arg,
                             const ReadOptions&amp; options,
                             const Slice&amp; index_value) {
  Table* table = reinterpret_cast&lt;Table*&gt;(arg);
  Cache* block_cache = table-&gt;rep_-&gt;options.block_cache;
  Block* block = NULL;
  Cache::Handle* cache_handle = NULL;

  BlockHandle handle;
  Slice input = index_value;
  Status s = handle.DecodeFrom(&amp;input);
  // We intentionally allow extra stuff in index_value so that we
  // can add more features in the future.

  // block:先查block cache
  // cache的key是table的cache_id + block_offset
  // 每个table文件对应一个唯一的cache_id
  if (s.ok()) {
    BlockContents contents;
    if (block_cache != NULL) {
      char cache_key_buffer[16];

      EncodeFixed64(cache_key_buffer, table-&gt;rep_-&gt;cache_id);
      EncodeFixed64(cache_key_buffer+8, handle.offset());
      Slice key(cache_key_buffer, sizeof(cache_key_buffer));
      cache_handle = block_cache-&gt;Lookup(key);
      if (cache_handle != NULL) {
        block = reinterpret_cast&lt;Block*&gt;(block_cache-&gt;Value(cache_handle));
      } else {
        // 没有则直接读取，并加入block cache
        s = ReadBlock(table-&gt;rep_-&gt;file, options, handle, &amp;contents);
        if (s.ok()) {
          block = new Block(contents);
          if (contents.cachable &amp;&amp; options.fill_cache) {
            cache_handle = block_cache-&gt;Insert(
                key, block, block-&gt;size(), &amp;DeleteCachedBlock);
          }
        }
      }
    } else {
      s = ReadBlock(table-&gt;rep_-&gt;file, options, handle, &amp;contents);
      if (s.ok()) {
        block = new Block(contents);
      }
    }
  }
  ---
}
</pre>
<p>写table的主要函数Add与Finish代码如下。代码清晰，请自行阅读。</p>
<pre class="wp-code-highlight prettyprint">
void TableBuilder::Add(const Slice&amp; key, const Slice&amp; value) {
  Rep* r = rep_;
  assert(!r-&gt;closed);
  if (!ok()) return;
  // 需要有序插入
  if (r-&gt;num_entries &gt; 0) {
    assert(r-&gt;options.comparator-&gt;Compare(key, Slice(r-&gt;last_key)) &gt; 0);
  }

  // 前面提到过data index block的key，其值需要等候后一个block的key写入时才能构造，pending_index_entry就表示了前一个block的index，此时构造其key，并写入index_block
  if (r-&gt;pending_index_entry) {
    assert(r-&gt;data_block.empty());
    r-&gt;options.comparator-&gt;FindShortestSeparator(&amp;r-&gt;last_key, key);
    std::string handle_encoding;
    r-&gt;pending_handle.EncodeTo(&amp;handle_encoding);
    r-&gt;index_block.Add(r-&gt;last_key, Slice(handle_encoding));
    r-&gt;pending_index_entry = false;
  }

  // 往filter block中添加key
  if (r-&gt;filter_block != NULL) {
    r-&gt;filter_block-&gt;AddKey(key);
  }

  r-&gt;last_key.assign(key.data(), key.size());
  r-&gt;num_entries++;
  r-&gt;data_block.Add(key, value);

  const size_t estimated_block_size = r-&gt;data_block.CurrentSizeEstimate();
  // block_size限制
  if (estimated_block_size &gt;= r-&gt;options.block_size) {
    Flush();
  }
}

Status TableBuilder::Finish() {
  Rep* r = rep_;
  Flush();
  assert(!r-&gt;closed);
  r-&gt;closed = true;

  BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;

  // Write filter block
  if (ok() &amp;&amp; r-&gt;filter_block != NULL) {
    WriteRawBlock(r-&gt;filter_block-&gt;Finish(), kNoCompression,
                  &amp;filter_block_handle);
  }

  // Write metaindex block
  if (ok()) {
    BlockBuilder meta_index_block(&amp;r-&gt;options);
    if (r-&gt;filter_block != NULL) {
      // Add mapping from &quot;filter.Name&quot; to location of filter data
      std::string key = &quot;filter.&quot;;
      key.append(r-&gt;options.filter_policy-&gt;Name());
      std::string handle_encoding;
      filter_block_handle.EncodeTo(&amp;handle_encoding);
      meta_index_block.Add(key, handle_encoding);
    }

    // TODO(postrelease): Add stats and other meta blocks
    WriteBlock(&amp;meta_index_block, &amp;metaindex_block_handle);
  }

  // Write index block
  if (ok()) {
    if (r-&gt;pending_index_entry) {
      r-&gt;options.comparator-&gt;FindShortSuccessor(&amp;r-&gt;last_key);
      std::string handle_encoding;
      r-&gt;pending_handle.EncodeTo(&amp;handle_encoding);
      r-&gt;index_block.Add(r-&gt;last_key, Slice(handle_encoding));
      r-&gt;pending_index_entry = false;
    }
    WriteBlock(&amp;r-&gt;index_block, &amp;index_block_handle);
  }

  // Write footer
  if (ok()) {
    Footer footer;
    footer.set_metaindex_handle(metaindex_block_handle);
    footer.set_index_handle(index_block_handle);
    std::string footer_encoding;
    footer.EncodeTo(&amp;footer_encoding);
    r-&gt;status = r-&gt;file-&gt;Append(footer_encoding);
    if (r-&gt;status.ok()) {
      r-&gt;offset += footer_encoding.size();
    }
  }
  return r-&gt;status;
}
</pre>
<p>另外还有two_level_iterator.cc/.h与merger.cc/.h类。前者用于构造table上的迭代器，后者用于多个迭代器的归并。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-5-sstable.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>leveldb注释4–平台相关</title>
		<link>http://www.petermao.com/leveldb/leveldb-4-port.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-4-port</link>
		<comments>http://www.petermao.com/leveldb/leveldb-4-port.html#comments</comments>
		<pubDate>Sun, 07 Jul 2013 11:10:20 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[leveldb env]]></category>
		<category><![CDATA[leveldb port_posix]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=539</guid>
		<description><![CDATA[0 编译 leveldb没有使用autoconf/automake来生成Makefile，而是自己编写了Makefile以及平台检测脚本。Makefile中会先调用检测脚本build_detect_platform，并将设置的环境变量保存在build_config.mk文件里。随后Makefile会include build_config.mk文件以进行编译，相关Makefile代码如下，其中的PLATFORM_CCFLAGS等参数将在build_detect_platform中设置并输出给build_config.mk文件： --- # detect what platform we're building on $(shell CC=$(CC) CXX=$(CXX) TARGET_OS=$(TARGET_OS) \ ./build_detect_platform build_config.mk ./) # this file is generated by the previous line to set build flags and sources include build_config.mk CFLAGS += -I. &#8230; <a href="http://www.petermao.com/leveldb/leveldb-4-port.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>0 编译<br />
leveldb没有使用autoconf/automake来生成Makefile，而是自己编写了Makefile以及平台检测脚本。Makefile中会先调用检测脚本build_detect_platform，并将设置的环境变量保存在build_config.mk文件里。随后Makefile会include build_config.mk文件以进行编译，相关Makefile代码如下，其中的PLATFORM_CCFLAGS等参数将在build_detect_platform中设置并输出给build_config.mk文件：<span id="more-539"></span></p>
<pre class="wp-code-highlight prettyprint">
---
# detect what platform we're building on
$(shell CC=$(CC) CXX=$(CXX) TARGET_OS=$(TARGET_OS) \
    ./build_detect_platform build_config.mk ./)
# this file is generated by the previous line to set build flags and sources
include build_config.mk

CFLAGS += -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
CXXFLAGS += -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT)

LDFLAGS += $(PLATFORM_LDFLAGS)
LIBS += $(PLATFORM_LIBS)
---
</pre>
<p>build_detect_platform脚本内部使用&#8217;uname -s&#8217;命令来检测类Unix/Linux操作系统的各版本并设置合适的变量(如CC/PLATFORM等变量，以及选择合适的底层平台文件，主要是port/port_posix.cc)，然后会依次检测编译环境中是否支持c++ 0x、snappy、tcmalloc，比如检测snappy是否安装的代码如下，对于snappy，若已安装，则会定义-DSNAPPY宏：</p>
<pre class="wp-code-highlight prettyprint">
    # Test whether Snappy library is installed
    # http://code.google.com/p/snappy/
    $CXX $CXXFLAGS -x c++ - -o /dev/null 2&gt;/dev/null  &lt;&lt;EOF
      #include &lt;snappy.h&gt;
      int main() {}
EOF
    if [ &quot;$?&quot; = 0 ]; then
        COMMON_FLAGS=&quot;$COMMON_FLAGS -DSNAPPY&quot;
        PLATFORM_LIBS=&quot;$PLATFORM_LIBS -lsnappy&quot;
    fi
</pre>
<p>如果你想更进一步，连leveldb本身提供的Makefile都不想使用，你想将源代码集成进来，则只需要注意包含port/port_posix.cc文件或者其他合适的跨平台文件，以及定义合适的平台参数，比如对于POSIX环境，可简单的使用&#8217;-DLEVELDB_PLATFORM_POSIX&#8217;就可以编译。</p>
<p>这里简单介绍了leveldb的编译过程，下面来看看其对平台的具体支持。</p>
<p>leveldb大致通过3层来实现平台语义的支持。<br />
第一层是对外接口，在env.cc/.h与mutexlock.h中实现，mutexlock.h提供了锁的上层接口，而env则主要提供了文件与线程操作的上层接口。这部分代码对于不同平台没有多大差别。主要定义了一个抽象接口。<br />
第二层是文件与线程操作的实际实现，对于posix，则为env_posix.cc。<br />
第三层则是一些原子操作的封装，比如信号、锁等。对于posix，对应的文件为port_posix.cc/.h。</p>
<p>第一层：<br />
Env：环境对象，提供文件、目录、线程等的操作接口。用户可自定义新Env对象，以加强对系统的控制；否则会使用默认的Env对象。对于posix，就是PosixEnv对象。<br />
SequentialFile：顺序读文件<br />
RandomAccessFile：随机读文件<br />
WritableFile：顺序写文件<br />
FileLock：文件锁</p>
<p>第二层：<br />
PosixSequentialFile是对SequentialFile接口的实现，也就是简单调用fread读文件。<br />
posix的RandomAccessFile实现有两个，一个是PosixRandomAccessFile，另一个是PosixMmapReadableFile，前者调用pread读取指定偏移的文件数据，后者为使用mmap来读取文件。<br />
PosixMmapFile是对WritableFile接口的实现，始终使用mmap来写文件。</p>
<p>最终的PosixEnv对象通过调用这些子类来对外提供服务。对于随机读文件，在64位环境下，最多允许1000个文件使用mmap的方式，其他则使用pread方式。</p>
<p>第三层：<br />
利用C++的语言特性定义了Mutex、CondVar，以在使用过程中自动释放pthread_mutex_t、pthread_cond_t资源。另外，也提供了Snappy的操作接口：Snappy_Compress与Snappy_Uncompress。</p>
<p>大部分代码并不复杂，这里就不一一分析了。</p>
<p>helpers/memenv目录下实现了自定义的Env类 &#8212; InMemoryEnv，完全基于内存操作的Env对象，对自定义Env对象感兴趣的可了解下。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-4-port.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>leveldb注释3–基础类</title>
		<link>http://www.petermao.com/leveldb/leveldb-3-util.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-3-util</link>
		<comments>http://www.petermao.com/leveldb/leveldb-3-util.html#comments</comments>
		<pubDate>Sun, 07 Jul 2013 05:29:40 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[leveldb arena]]></category>
		<category><![CDATA[leveldb bloom filter]]></category>
		<category><![CDATA[leveldb cache]]></category>
		<category><![CDATA[leveldb coding]]></category>
		<category><![CDATA[leveldb comparator]]></category>
		<category><![CDATA[leveldb crc32]]></category>
		<category><![CDATA[leveldb hash]]></category>
		<category><![CDATA[leveldb skiplist]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=545</guid>
		<description><![CDATA[这一节主要分析下util目录下的代码。这部分代码相对独立，功能明确，很容易看明白。可先尝试理解这部分代码，以为进一步理解leveldb代码打下基础。 1 内存管理(arena.cc/.h) leveldb的大部分内存管理依赖于C++语言的默认实现，也就是不对内存进行管理。只是在memtable的实现中用到了一个简单的内存管理器(arena)。因为memtable的内部实现skip list写入时，需要分配新节点，大量节点的直接分配可能会带来较多的碎片，影响运行效率。因此，leveldb在每个memtable中都会绑定一个arena，在memtable进行minor compact后，memtable销毁时进行统一释放。 下图是一个arena某个运行时刻的截图。从图中可以看出，arena内部使用的基本块大小为4K，已分配的块的指保存在一个vector中。具体分配策略如下。若分配的内存可从剩余的块中分配，则直接从剩余块中分配并调整剩余块指针的位置。否则检查要分配的size是否大于1/4的block(也就是1K)，若大于则直接new所需大小的内存，将指针保存在vector中并返回内存指针。从这里可以看出，vector保存的内存块的指针大小可能>4K，也就是对大内存块直接分配。前面两个条件若都不满足，则需要new一个基本块(=4K)，将new出来的新块作为当前块，从这块当中分配内存并调整当前内存指针位置。原来当前块的剩余内存就被浪费掉了。 实际中可考虑用tcmalloc/jemalloc以提升内存管理效率。 // alloc_ptr_指向基本块空闲内存位置，alloc_bytes_remaining_为剩余内存大小 // blocks_保存了所有的块，包括了基本块与直接分配的大块 // Allocation state char* alloc_ptr_; size_t alloc_bytes_remaining_; // Array of new[] allocated memory blocks std::vector&#60;char*&#62; blocks_; // Bytes of memory in blocks allocated so far size_t blocks_memory_; 2 &#8230; <a href="http://www.petermao.com/leveldb/leveldb-3-util.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>这一节主要分析下util目录下的代码。这部分代码相对独立，功能明确，很容易看明白。可先尝试理解这部分代码，以为进一步理解leveldb代码打下基础。</p>
<p>1    内存管理(arena.cc/.h)<span id="more-545"></span><br />
leveldb的大部分内存管理依赖于C++语言的默认实现，也就是不对内存进行管理。只是在memtable的实现中用到了一个简单的内存管理器(arena)。因为memtable的内部实现skip list写入时，需要分配新节点，大量节点的直接分配可能会带来较多的碎片，影响运行效率。因此，leveldb在每个memtable中都会绑定一个arena，在memtable进行minor compact后，memtable销毁时进行统一释放。<br />
下图是一个arena某个运行时刻的截图。从图中可以看出，arena内部使用的基本块大小为4K，已分配的块的指保存在一个vector中。具体分配策略如下。若分配的内存可从剩余的块中分配，则直接从剩余块中分配并调整剩余块指针的位置。否则检查要分配的size是否大于1/4的block(也就是1K)，若大于则直接new所需大小的内存，将指针保存在vector中并返回内存指针。从这里可以看出，vector保存的内存块的指针大小可能>4K，也就是对大内存块直接分配。前面两个条件若都不满足，则需要new一个基本块(=4K)，将new出来的新块作为当前块，从这块当中分配内存并调整当前内存指针位置。原来当前块的剩余内存就被浪费掉了。<br />
实际中可考虑用tcmalloc/jemalloc以提升内存管理效率。<br />
<div id="attachment_670" class="wp-caption alignnone" style="width: 609px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-arena.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-arena.png" alt="leveldb arena" title="leveldb arena" width="599" height="359" class="size-full wp-image-670" /></a><p class="wp-caption-text">leveldb arena</p></div></p>
<pre class="wp-code-highlight prettyprint">
  // alloc_ptr_指向基本块空闲内存位置，alloc_bytes_remaining_为剩余内存大小
  // blocks_保存了所有的块，包括了基本块与直接分配的大块
  // Allocation state
  char* alloc_ptr_;
  size_t alloc_bytes_remaining_;

  // Array of new[] allocated memory blocks
  std::vector&lt;char*&gt; blocks_;

  // Bytes of memory in blocks allocated so far
  size_t blocks_memory_;
</pre>
<p>2 过滤器bloom filter的实现(bloom.cc)<br />
leveldb通过接口NewBloomFilterPolicy提供了一个filter的实现，使用时通过传入该filter可提升对不存在的数查找时的效率。open db时的options.filter_policy参数指定该filter。如果指定了filter，leveldb会在生成新sst文件时对该文件的所有key构造一个合适的bloom filter字符串并保存在sst文件中。现在我们来看看这个bloom filter的实现机制。bloom filter需要指定bits_per_key参数，表示每个key需要检测多少个bit位/保存多少个bit位。创建bloom filter的核心代码如下：</p>
<pre class="wp-code-highlight prettyprint">
  virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const {
    // Compute bloom filter size (in both bits and bytes)
    size_t bits = n * bits_per_key_;

    // For small n, we can see a very high false positive rate.  Fix it
    // by enforcing a minimum bloom filter length.
    if (bits &lt; 64) bits = 64;

    size_t bytes = (bits + 7) / 8;
    bits = bytes * 8;

    const size_t init_size = dst-&gt;size();
    dst-&gt;resize(init_size + bytes, 0);
	// 将探测需要的尾数放至最后
    dst-&gt;push_back(static_cast&lt;char&gt;(k_));  // Remember # of probes in filter
    char* array = &amp;(*dst)[init_size];
    for (size_t i = 0; i &lt; n; i++) {
      // Use double-hashing to generate a sequence of hash values.
      // See analysis in [Kirsch,Mitzenmacher 2006].
      uint32_t h = BloomHash(keys[i]);
      const uint32_t delta = (h &gt;&gt; 17) | (h &lt;&lt; 15);  // Rotate right 17 bits
      for (size_t j = 0; j &lt; k_; j++) {
        const uint32_t bitpos = h % bits;
        array[bitpos/8] |= (1 &lt;&lt; (bitpos % 8));
        h += delta;
      }
    }
</pre>
<p>创建时，先预留bloom filter所需空间(n * bits_per_key_/8对齐后)，并将需要检测的位数保存至字符串末尾，以方便使用时解出。然后遍历key，计算其hash值，将hash值所对应的bit位置1(需要设置bits_per_key_次，并且后续的bit位从原始的hash值右移17位得到)。过滤函数KeyMayMatch基本就是上述过程的逆过程。bloom filter字符串的内部结构可如下图所示。<br />
<div id="attachment_677" class="wp-caption alignnone" style="width: 694px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-bloom.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb-bloom.png" alt="leveldb bloomfilter结构" title="leveldb bloomfilter结构" width="684" height="225" class="size-full wp-image-677" /></a><p class="wp-caption-text">leveldb bloomfilter结构</p></div></p>
<p>3 默认的cache机制&#8212;LRU实现(cache.cc/.h)<br />
leveldb的cache主要是为了加快get操作的性能。cache分为block cache与table cache。table cache针对sst文件，key是file_number，cache的大小为options.max_open_files &#8211; 10，在db初始化时创建，且cache机制使用默认的LRU算法，外部没法更改。</p>
<pre class="wp-code-highlight prettyprint">
  // Reserve ten files or so for other uses and give the rest to TableCache.
  const int table_cache_size = options.max_open_files - 10;
  table_cache_ = new TableCache(dbname_, &amp;options_, table_cache_size);
</pre>
<p>而block_cache则可以在open时通过options.block_cache参数指定cache实例。外部可自定义合适的cache机制替换系统自带的LRU cache。如果外部没有提供block_cache参数，则leveldb会在初始化时构造8MB大小的cache，该cache同样是LRU cache的实例。该cache的key是block所属table文件的一个cache_id + block在文件中的偏移。</p>
<pre class="wp-code-highlight prettyprint">
  // 没有提供block_cache，则内部使用8MB的LRUCache
  if (result.block_cache == NULL) {
    result.block_cache = NewLRUCache(8 &lt;&lt; 20);
  }
</pre>
<pre class="wp-code-highlight prettyprint">
      char cache_key_buffer[16];
      EncodeFixed64(cache_key_buffer, table-&gt;rep_-&gt;cache_id);
      EncodeFixed64(cache_key_buffer+8, handle.offset());
      Slice key(cache_key_buffer, sizeof(cache_key_buffer));
      cache_handle = block_cache-&gt;Lookup(key);
</pre>
<p>如果用户想自定义block_cache算法，则可参考cache.h中Cache类所定义的抽象接口，主要接口代码如下所示。可以看出，也就是key的增删查改以及句柄的释放。</p>
<pre class="wp-code-highlight prettyprint">
  // When the inserted entry is no longer needed, the key and
  // value will be passed to &quot;deleter&quot;.
  virtual Handle* Insert(const Slice&amp; key, void* value, size_t charge,
                         void (*deleter)(const Slice&amp; key, void* value)) = 0;

  // If the cache has no mapping for &quot;key&quot;, returns NULL.
  //
  // Else return a handle that corresponds to the mapping.  The caller
  // must call this-&gt;Release(handle) when the returned mapping is no
  // longer needed.
  virtual Handle* Lookup(const Slice&amp; key) = 0;

  // Release a mapping returned by a previous Lookup().
  // REQUIRES: handle must not have been released yet.
  // REQUIRES: handle must have been returned by a method on *this.
  virtual void Release(Handle* handle) = 0;

  // Return the value encapsulated in a handle returned by a
  // successful Lookup().
  // REQUIRES: handle must not have been released yet.
  // REQUIRES: handle must have been returned by a method on *this.
  virtual void* Value(Handle* handle) = 0;

  // If the cache contains entry for key, erase it.  Note that the
  // underlying entry will be kept around until all existing handles
  // to it have been released.
  virtual void Erase(const Slice&amp; key) = 0;

  // Return a new numeric id.  May be used by multiple clients who are
  // sharing the same cache to partition the key space.  Typically the
  // client will allocate a new id at startup and prepend the id to
  // its cache keys.
  virtual uint64_t NewId() = 0;
</pre>
<p>NewLRUCache函数可调用自带的LRU实现。现在我们来看看LRU的具体实现。下图是LRUCache的内部实现图与相关代码。<br />
<div id="attachment_683" class="wp-caption alignnone" style="width: 481px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb单个LRUCache.png"><img src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb单个LRUCache.png" alt="单个LRUCache" title="leveldb单个LRUCache" width="471" height="298" class="size-full wp-image-683" /></a><p class="wp-caption-text">单个LRUCache</p></div></p>
<pre class="wp-code-highlight prettyprint">
struct LRUHandle {
  void* value;
  void (*deleter)(const Slice&amp;, void* value);
  LRUHandle* next_hash; // hash链表指针
  LRUHandle* next;       // 循环链表指针
  LRUHandle* prev;
  size_t charge;      // TODO(opt): Only allow uint32_t?
  size_t key_length;
  uint32_t refs;
  uint32_t hash;      // Hash of key(); used for fast sharding and comparisons
  char key_data[1];   // Beginning of key
--
};
class HandleTable {
  ---
  uint32_t length_;
  uint32_t elems_;
  LRUHandle** list_;
  ---
};

class LRUCache {
  ---
  // Initialized before use.
  size_t capacity_;
  ---
  // Dummy head of LRU list.
  // lru.prev is newest entry, lru.next is oldest entry.
  LRUHandle lru_;
  HandleTable table_;
};

static const int kNumShardBits = 4;
static const int kNumShards = 1 &lt;&lt; kNumShardBits;
class ShardedLRUCache : public Cache {
  ---
  LRUCache shard_[kNumShards];
  --
};
</pre>
<p>每个节点有3个指针，一个指针用于hash table的单链接指向。另外两个形成双向循环链表，以实现lru功能，方便控制cache的大小。插入时除了在hash table中查找合适的位置并插入，还需要插入到循环链表的开头。如果数据过多，就通过head来删除最老的节点。对于table，插入的元素个数大于桶的数目时，需要重新调整大小，新的size将是2的幂，但比元素个数多。<br />
leveldb为了加快查找速度，并不只是使用了1个如上所示的LRUCache，而是16个(也就是说会按key再分一次桶)，最终调用的是ShardedLRUCache接口。</p>
<p>4 内部编码/解析器(coding.cc/.h)<br />
leveldb为了节省空间，可对int进行可变长度编码，也就是对小于2^7使用1个字节存储，小于2^14使用两个字节。。。。编码时将输入每隔7位分组，将字节的最高位始终置1，一直到最后1个字节的最高位置0表示结束。比如对int型的二进制数00000000,00000001,11011001,01000110，每隔7位分组则为111,0110010,1000110，再将每个字节的高位置1，则实际存储的为00000111,10110010,11000110。这样比起始终用4个字节存储，省了1个字节的存储空间。实际的编码代码如下：</p>
<pre class="wp-code-highlight prettyprint">
char* EncodeVarint32(char* dst, uint32_t v) {
  // Operate on characters as unsigneds
  unsigned char* ptr = reinterpret_cast&lt;unsigned char*&gt;(dst);
  static const int B = 128;
  if (v &lt; (1&lt;&lt;7)) {
    *(ptr++) = v;
  } else if (v &lt; (1&lt;&lt;14)) {
    *(ptr++) = v | B;
    *(ptr++) = v&gt;&gt;7;
  } else if (v &lt; (1&lt;&lt;21)) {
    *(ptr++) = v | B;
    *(ptr++) = (v&gt;&gt;7) | B;
    *(ptr++) = v&gt;&gt;14;
  } else if (v &lt; (1&lt;&lt;28)) {
    *(ptr++) = v | B;
    *(ptr++) = (v&gt;&gt;7) | B;
    *(ptr++) = (v&gt;&gt;14) | B;
    *(ptr++) = v&gt;&gt;21;
  } else {
    *(ptr++) = v | B;
    *(ptr++) = (v&gt;&gt;7) | B;
    *(ptr++) = (v&gt;&gt;14) | B;
    *(ptr++) = (v&gt;&gt;21) | B;
    *(ptr++) = v&gt;&gt;28;
  }
  return reinterpret_cast&lt;char*&gt;(ptr);
</pre>
<p>5 默认比较器的实现&#8212;字节级比较器<br />
由于leveldb内部是有序的，因此其比较器是至关重要的。头文件中的Comparator类提供了比较器的抽象接口。Name定义了该比较器的名字，会在open时进行检查。至于FindShortestSeparator、FindShortSuccessor表示什么意思，可参看默认的字节级比较器BytewiseComparator的实现，相当简单。</p>
<pre class="wp-code-highlight prettyprint">
  // Three-way comparison.  Returns value:
  //   &lt; 0 iff &quot;a&quot; &lt; &quot;b&quot;,
  //   == 0 iff &quot;a&quot; == &quot;b&quot;,
  //   &gt; 0 iff &quot;a&quot; &gt; &quot;b&quot;
  virtual int Compare(const Slice&amp; a, const Slice&amp; b) const = 0;

  // The name of the comparator.  Used to check for comparator
  // mismatches (i.e., a DB created with one comparator is
  // accessed using a different comparator.
  //
  // The client of this package should switch to a new name whenever
  // the comparator implementation changes in a way that will cause
  // the relative ordering of any two keys to change.
  //
  // Names starting with &quot;leveldb.&quot; are reserved and should not be used
  // by any clients of this package.
  virtual const char* Name() const = 0;

  // Advanced functions: these are used to reduce the space requirements
  // for internal data structures like index blocks.

  // If *start &lt; limit, changes *start to a short string in [start,limit).
  // Simple comparator implementations may return with *start unchanged,
  // i.e., an implementation of this method that does nothing is correct.
  virtual void FindShortestSeparator(
      std::string* start,
      const Slice&amp; limit) const = 0;

  // Changes *key to a short string &gt;= *key.
  // Simple comparator implementations may return with *key unchanged,
  // i.e., an implementation of this method that does nothing is correct.
  virtual void FindShortSuccessor(std::string* key) const = 0;
</pre>
<p>未完待续</p>
<p>6 crc32</p>
<p>7 hash</p>
<p>8 histogram</p>
<p>9 logging.cc</p>
<p>10 random.h</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-3-util.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>leveldb注释1&#8211;整体介绍</title>
		<link>http://www.petermao.com/leveldb/leveldb-1-overview.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-1-overview</link>
		<comments>http://www.petermao.com/leveldb/leveldb-1-overview.html#comments</comments>
		<pubDate>Fri, 05 Jul 2013 08:31:58 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=481</guid>
		<description><![CDATA[概括的说，leveldb是一个kv型的存储库。主要特点如下： > 单机嵌入式接口 > 持久化存储 > KV系统，提供读写删除操作(kv都是字节序列) > 支持快照功能，使得读操作不受写操作影响 > 磁盘上的数据是有序的，且分级存储，同时在文件保存相应的索引以加速读操作 > 删除是一种特殊类型的写操作 > 提供压缩操作以减少存储空间 > 写入，延迟写入，通过log保证数据不丢失 整体架构如下图所示： 为了支持kv型的读写删除操作，leveldb在内存与磁盘中分别用了一些数据结构/数据文件。从上图可以看出，内存中主要有memtable与immutable memtable，而磁盘上主要有CURRENT、MANIFEST、.log、LOCK、LOG以及分级的.sst等文件。 内存中的memtable与immutable table本质上都是一个skip list，保存当前写入内存而未持久化至磁盘的数据。对于特定的db来讲，一开始只有一个memtable，当memtable写入操作超过阈值(或者对应的.log文件超过阈值时)后，会转化为immutable memtable。同时会产生新的memtable以供后续数据插入。而immutable memtable是只能读不能写的。 磁盘上的LOCK文件锁住当前db。LOG文件是leveldb运行过程中产生的一些日志(注意跟.log文件区分)。.log文件则跟memtable配合，保存未持久化的写操作，以防运行过程中程序异常导致数据丢失。sst文件保存了最终的数据。随着压缩的进行，会产生不同level的sst文件。为了描述这些sst文件，比如level0有哪些文件，对应的key-range范围，则有相应的MANIFEST文件。而CURRENT文件则指向描述db最新状态信息的MANIFEST。因为leveldb支持version与snapshot机制，随着leveldb的运行，会不断的增删sst数据文件，原有的描述文件可能仍在使用，而CURRENT指向的MANIFEST则始终表示最新的描述文件。 再来看看leveldb运行过程中的主要操作。 先来看写入操作(put)。从架构图可以看出，leveldb会先将写入操作写至.log文件，写入成功后再写入memtable。由于写log是追加写，而写memtable是skip list的内存操作，因此leveldb的写是相当快的。 对于读操作(get)来讲，主要分为3步。先查memtable，没有找到则在immutable memtable中继续查找；若还没有找到，则表示对应的key-value数据可能已持久化至磁盘中，因此需要借助MANIFEST文件来找到对应的sst文件，然后再在对应的sst文件中查找。由于MANIFEST文件常驻内存，而MANIFEST保存了不同sst文件的key-range，因此查找对应的文件是很快的。找到对应的文件后，由于sst文件内部是有序的，并且有相应的索引索引不同key-range的value，因此需要先读入这些索引，再在索引中二分查找，再根据索引找到对应的块，然后遍历相应的块查找。这个过程可能需要两次大范围的读文件操作。一次是读索引，一次是读对应的block。为了加快这个操作，leveldb有相应的table cache与block cache，以分别加速sst文件与block的读操作。另外有些数据可能在db中就不存在，为了加速这些数据的查找，每个sst文件都可以添加合适的bloom filter。bloom filter是在载入相应的sst文件后对数据进行过滤的。 最后来看看压缩(compact)操作。leveldb的压缩操作分为minor compact与major compact。所有的压缩操作都是启动一个新线程来完成的。从架构图可以看出，minor compact就是遍历immutable memtable(本质上是一个skip &#8230; <a href="http://www.petermao.com/leveldb/leveldb-1-overview.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>概括的说，leveldb是一个kv型的存储库。主要特点如下：<br />
> 单机嵌入式接口<br />
> 持久化存储<br />
> KV系统，提供读写删除操作(kv都是字节序列)<br />
> 支持快照功能，使得读操作不受写操作影响<br />
> 磁盘上的数据是有序的，且分级存储，同时在文件保存相应的索引以加速读操作<br />
> 删除是一种特殊类型的写操作<br />
> 提供压缩操作以减少存储空间<br />
> 写入，延迟写入，通过log保证数据不丢失</p>
<p>整体架构如下图所示：<span id="more-481"></span></p>
<div id="attachment_597" class="wp-caption alignnone" style="width: 713px"><a href="http://www.petermao.com/wp-content/uploads/2013/10/leveldb整体结构4.png"><img class="size-full wp-image-597" title="leveldb整体结构" src="http://www.petermao.com/wp-content/uploads/2013/10/leveldb整体结构4.png" alt="leveldb整体结构" width="703" height="583" /></a><p class="wp-caption-text">leveldb整体结构</p></div>
<p>为了支持kv型的读写删除操作，leveldb在内存与磁盘中分别用了一些数据结构/数据文件。从上图可以看出，内存中主要有memtable与immutable memtable，而磁盘上主要有CURRENT、MANIFEST、.log、LOCK、LOG以及分级的.sst等文件。</p>
<p>内存中的memtable与immutable table本质上都是一个skip list，保存当前写入内存而未持久化至磁盘的数据。对于特定的db来讲，一开始只有一个memtable，当memtable写入操作超过阈值(或者对应的.log文件超过阈值时)后，会转化为immutable memtable。同时会产生新的memtable以供后续数据插入。而immutable memtable是只能读不能写的。</p>
<p>磁盘上的LOCK文件锁住当前db。LOG文件是leveldb运行过程中产生的一些日志(注意跟.log文件区分)。.log文件则跟memtable配合，保存未持久化的写操作，以防运行过程中程序异常导致数据丢失。sst文件保存了最终的数据。随着压缩的进行，会产生不同level的sst文件。为了描述这些sst文件，比如level0有哪些文件，对应的key-range范围，则有相应的MANIFEST文件。而CURRENT文件则指向描述db最新状态信息的MANIFEST。因为leveldb支持version与snapshot机制，随着leveldb的运行，会不断的增删sst数据文件，原有的描述文件可能仍在使用，而CURRENT指向的MANIFEST则始终表示最新的描述文件。</p>
<p>再来看看leveldb运行过程中的主要操作。<br />
先来看写入操作(put)。从架构图可以看出，leveldb会先将写入操作写至.log文件，写入成功后再写入memtable。由于写log是追加写，而写memtable是skip list的内存操作，因此leveldb的写是相当快的。</p>
<p>对于读操作(get)来讲，主要分为3步。先查memtable，没有找到则在immutable memtable中继续查找；若还没有找到，则表示对应的key-value数据可能已持久化至磁盘中，因此需要借助MANIFEST文件来找到对应的sst文件，然后再在对应的sst文件中查找。由于MANIFEST文件常驻内存，而MANIFEST保存了不同sst文件的key-range，因此查找对应的文件是很快的。找到对应的文件后，由于sst文件内部是有序的，并且有相应的索引索引不同key-range的value，因此需要先读入这些索引，再在索引中二分查找，再根据索引找到对应的块，然后遍历相应的块查找。这个过程可能需要两次大范围的读文件操作。一次是读索引，一次是读对应的block。为了加快这个操作，leveldb有相应的table cache与block cache，以分别加速sst文件与block的读操作。另外有些数据可能在db中就不存在，为了加速这些数据的查找，每个sst文件都可以添加合适的bloom filter。bloom filter是在载入相应的sst文件后对数据进行过滤的。</p>
<p>最后来看看压缩(compact)操作。leveldb的压缩操作分为minor compact与major compact。所有的压缩操作都是启动一个新线程来完成的。从架构图可以看出，minor compact就是遍历immutable memtable(本质上是一个skip list)，dump至一个新的level0 sst文件。从这里可以看出，新dump出来的sst文件可能会跟已有的level0级别的sst文件存在key-range上的重叠。而level[1-6]上的sst文件则不会存在这个这个问题，level[1-6]上的文件是由上一级文件进行major compact得到的。当level 0层文件数超过阈值(&gt;=4)，会触发level0与level1层文件合并。而对于大于等于level1以上的level，总文件字节数超过阈值(10M 100M 1G 10G 100G 1T(level=6))则会触发该操作。至于选择哪个文件与下一级别的文件进行compact，则是轮流进行的。另外如果特定level文件的seek次数(也就是访问次数)超过阈值，也会触发对应的major compact操作，此时则会直接选择该文件与下一level进行major compact操作。</p>
<p>后续我们将先分析内存与磁盘上的数据结构/文件格式代码，再分析GET/PUT等操作对应代码。在此之前，我们看看leveldb源代码的目录结构。<br />
doc/: 包括了一些文档，<br />
&gt; benchmark.html             性能测试文档<br />
&gt; impl.html                                  内部实现文档<br />
&gt; index.html                              使用文档<br />
&gt; log_format.html             .log文件格式<br />
&gt; table_format.html      .sst文件格式</p>
<p>helpers/:提供了一个简单的内存文件操作接口，相当于实现了一个Env接口。Env接口对应了leveldb运行的底层平台。<br />
include/leveldb/:对外接口，使用时include该目录下的头文件即可。<br />
&gt; cache.h                 cache接口，用户可实现合适的cache接口，以加速读操作<br />
&gt; c.h                       leveldb是用c++编写的，这里提供了c的对外封装<br />
&gt; comparator.h<br />
db.h<br />
env.h<br />
filter_policy.h<br />
iterator.h<br />
options.h<br />
slice.h<br />
status.h<br />
table_builder.h<br />
table.h<br />
write_batch.h</p>
<p>util/:<br />
&gt; arena<br />
&gt; bloom<br />
&gt; cache<br />
&gt; coding<br />
&gt; comparator<br />
&gt; crc32c<br />
&gt; env<br />
&gt; env_posix<br />
&gt; filter_policy<br />
&gt; hash<br />
&gt; histogram<br />
&gt; logging<br />
&gt; mutexlock<br />
&gt; options<br />
&gt; posix_logger<br />
&gt; random<br />
&gt; status<br />
&gt; testharness<br />
&gt; testutil</p>
<p>port/:<br />
&gt; atomic_pointer<br />
&gt; port_example<br />
&gt; port<br />
&gt; port_posix<br />
&gt; README<br />
&gt; thread_annotations</p>
<p>table/: sst table相关操作的封装<br />
&gt; block_builder<br />
&gt; block<br />
&gt; filter_block<br />
&gt; format<br />
&gt; iterator<br />
&gt; iterator_wrapper<br />
&gt; merger<br />
&gt; table_builder<br />
&gt; table<br />
&gt; two_level_iterator</p>
<p>db/:leveldb主要操作的实现部分<br />
&gt; builder<br />
&gt; c<br />
&gt; db_bench<br />
&gt; dbformat<br />
&gt; db_impl<br />
&gt; db_iter<br />
&gt; filename<br />
&gt; log_format<br />
&gt; log_reader<br />
&gt; log_writer<br />
&gt; memtable<br />
&gt; repair<br />
&gt; skiplist<br />
&gt; snapshot<br />
&gt; table_cache<br />
&gt; version_edit<br />
&gt; version_set<br />
&gt; write_batch<br />
&gt; write_batch_internal</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-1-overview.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>leveldb注释0&#8211;start</title>
		<link>http://www.petermao.com/leveldb/leveldb-0-start.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=leveldb-0-start</link>
		<comments>http://www.petermao.com/leveldb/leveldb-0-start.html#comments</comments>
		<pubDate>Fri, 05 Jul 2013 01:23:24 +0000</pubDate>
		<dc:creator>petermao</dc:creator>
				<category><![CDATA[leveldb]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.petermao.com/?p=566</guid>
		<description><![CDATA[因为工作需要，曾对leveldb进行过测试与实际使用，而对leveldb的源代码进行学习，则纯粹是出于一个码农对美好世界进行探究的好奇。接下来将尽可能从源代码上给出leveldb代码的详尽注释，这里先列出自己在阅读前后的主要参考。 0 官方文档 http://leveldb.googlecode.com/svn/trunk/ 源代码，主要使用了1.7.0版本 https://leveldb.googlecode.com/files/leveldb-1.7.0.tar.gz http://leveldb.googlecode.com/svn/trunk/doc/index.html 官方使用手册，比较详细 http://leveldb.googlecode.com/svn/trunk/doc/benchmark.html 官方的性能测试 http://leveldb.googlecode.com/svn/trunk/doc/impl.html 比较粗略的介绍了leveldb的主要实现细节 http://leveldb.googlecode.com/svn/trunk/doc/table_format.txt http://leveldb.googlecode.com/svn/trunk/doc/log_format.txt sstable文件格式与log文件格式 1 原理 【朗格科技】LevelDb日知录系列 http://www.samecity.com/blog/Index.asp?SortID=12 数据分析与处理之二（Leveldb 实现原理） http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html 比较详尽的介绍了leveldb各模块的工作原理与实现细节，缺点是没有源代码细节。对leveldb最初细节的了解，也起于该文。建议读者可先阅读此文，以对leveldb的原理有最初的印象。另外，作者似乎没有把该系列写完。 leveldb实现解析.pdf 从代码细节上进行了分析，不过对于初接触者恐怕容易陷入过多的细节，缺乏整体性的介绍。建议对原理有了大致了解，且初读代码后，再进行对照学习。 2 性能 关于LevelDB http://blog.sina.com.cn/s/blog_593af2a70100ztjn.html LevelDB 写操作出现停顿的问题分析 http://www.ideawu.net/blog/archives/709.html Performance data for LevelDB, Berkley DB and BangDB &#8230; <a href="http://www.petermao.com/leveldb/leveldb-0-start.html">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>因为工作需要，曾对leveldb进行过测试与实际使用，而对leveldb的源代码进行学习，则纯粹是出于一个码农对美好世界进行探究的好奇。接下来将尽可能从源代码上给出leveldb代码的详尽注释，这里先列出自己在阅读前后的主要参考。<span id="more-566"></span></p>
<p>0 官方文档<br />
<a href="http://leveldb.googlecode.com/svn/trunk/">http://leveldb.googlecode.com/svn/trunk/</a><br />
源代码，主要使用了1.7.0版本<br />
<a href="https://leveldb.googlecode.com/files/leveldb-1.7.0.tar.gz">https://leveldb.googlecode.com/files/leveldb-1.7.0.tar.gz</a></p>
<p><a href="http://leveldb.googlecode.com/svn/trunk/doc/index.html">http://leveldb.googlecode.com/svn/trunk/doc/index.html</a><br />
官方使用手册，比较详细</p>
<p><a href="http://leveldb.googlecode.com/svn/trunk/doc/benchmark.html">http://leveldb.googlecode.com/svn/trunk/doc/benchmark.html</a><br />
官方的性能测试</p>
<p><a href="http://leveldb.googlecode.com/svn/trunk/doc/impl.html">http://leveldb.googlecode.com/svn/trunk/doc/impl.html</a><br />
比较粗略的介绍了leveldb的主要实现细节</p>
<p><a href="http://leveldb.googlecode.com/svn/trunk/doc/table_format.txt">http://leveldb.googlecode.com/svn/trunk/doc/table_format.txt</a><br />
<a href="http://leveldb.googlecode.com/svn/trunk/doc/log_format.txt">http://leveldb.googlecode.com/svn/trunk/doc/log_format.txt</a><br />
sstable文件格式与log文件格式</p>
<p>1 原理<br />
【朗格科技】LevelDb日知录系列   <a href="http://www.samecity.com/blog/Index.asp?SortID=12">http://www.samecity.com/blog/Index.asp?SortID=12</a><br />
数据分析与处理之二（Leveldb 实现原理）<br />
<a href="http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html">http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html</a><br />
比较详尽的介绍了leveldb各模块的工作原理与实现细节，缺点是没有源代码细节。对leveldb最初细节的了解，也起于该文。建议读者可先阅读此文，以对leveldb的原理有最初的印象。另外，作者似乎没有把该系列写完。</p>
<p>leveldb实现解析.pdf<br />
从代码细节上进行了分析，不过对于初接触者恐怕容易陷入过多的细节，缺乏整体性的介绍。建议对原理有了大致了解，且初读代码后，再进行对照学习。</p>
<p>2 性能<br />
关于LevelDB <a href="http://blog.sina.com.cn/s/blog_593af2a70100ztjn.html">http://blog.sina.com.cn/s/blog_593af2a70100ztjn.html</a></p>
<p>LevelDB 写操作出现停顿的问题分析 <a href="http://www.ideawu.net/blog/archives/709.html">http://www.ideawu.net/blog/archives/709.html</a></p>
<p>Performance data for LevelDB, Berkley DB and BangDB for Random Operations<br />
<a href="http://highscalability.com/blog/2012/11/29/performance-data-for-leveldb-berkley-db-and-bangdb-for-rando.html">http://highscalability.com/blog/2012/11/29/performance-data-for-leveldb-berkley-db-and-bangdb-for-rando.html</a></p>
<p>leveldb与kyoto cabinet性能测试<br />
<a href="http://blog.creapptives.com/post/8330476086/leveldb-vs-kyoto-cabinet-my-findings">http://blog.creapptives.com/post/8330476086/leveldb-vs-kyoto-cabinet-my-findings</a></p>
<p>3 相关实现<br />
go实现  <a href="https://code.google.com/p/leveldb-go">https://code.google.com/p/leveldb-go</a></p>
<p>Tair ldb(LevelDB)原理与应用案例.pdf</p>
<p>leveldb+redis  https://github.com/qiye/redis-storage<br />
ssdb https://github.com/ideawu/ssdb<br />
sophia https://github.com/pmwkaa/sophia</p>
]]></content:encoded>
			<wfw:commentRss>http://www.petermao.com/leveldb/leveldb-0-start.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
