Leveldb读取数据源码分析
Posted 叶长风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leveldb读取数据源码分析相关的知识,希望对你有一定的参考价值。
Leveldb读取数据源码分析
上一篇文章讲了Snapshot源码,在这一篇文章中原本是要讲version、versionSet这些,但是想想还是从Leveldb的读取和存储开始讲起,Leveldb的存储比较简单,主要麻烦的还是读取这一块,需要判断从memTable、memTable、level0、level层级中分开寻找,如果从上一级中找到了数据,那么下一级就不再继续寻找了,因此寻找要麻烦许多。
获取数据接口
从内存中获取
我们直接看Leveldb的get接口,如下:
mutex.lock();
try
SnapshotImpl snapshot = getSnapshot(options);
lookupKey = new LookupKey(Slices.wrappedBuffer(key), snapshot.getLastSequence());
// First look in the memtable, then in the immutable memtable (if any).
LookupResult lookupResult = memTable.get(lookupKey);
if (lookupResult != null)
Slice value = lookupResult.getValue();
if (value == null)
return null;
return value.getBytes();
if (immutableMemTable != null)
lookupResult = immutableMemTable.get(lookupKey);
if (lookupResult != null)
Slice value = lookupResult.getValue();
if (value == null)
return null;
return value.getBytes();
finally
mutex.unlock();
首先从内存中取,memTable实际上就是一个跳表的结构可快速定位到数据并返回,Get方法如下:
public LookupResult get(LookupKey key)
requireNonNull(key, "key is null");
InternalKey internalKey = key.getInternalKey();
Entry<InternalKey, Slice> entry = table.ceilingEntry(internalKey);
if (entry == null)
return null;
InternalKey entryKey = entry.getKey();
if (entryKey.getUserKey().equals(key.getUserKey()))
if (entryKey.getValueType() == ValueType.DELETION)
return LookupResult.deleted(key);
else
return LookupResult.ok(key, entry.getValue());
return null;
SkipList的原理就不再讲述了,有空可以自己看看,在这就是从skipList中拿到数据然后返回,而上文中提到的第一步从memTable中取数据,如果没有拿到数据则从immutableMemTable中取数据,immutableMemTable与memTable相同,只是一个可变一个不可变,我们在以后讲Leveldb的数据压缩时再来讲immutableMemTable。
从sstable中获取数据
当我们从内存中没有获取到数据时,就需要从sstable中获取数据,sstable的读取过程如下:
查找sstable file的元数据信息(索引信息),根据索引查找感兴趣的key位于哪个block,进而读出block,然后从block中读出key。
在这里,并不是说Leveldb中的每一层级的Level的读取过程都是一样的,在Leveldb中Level0与其他level并不相同,level0中sstable的key的范围可能会有重叠,因此一个key可能会出现在多个sstable中,因此需要进行一些特别的处理,但在其他level中就不会出现,这就是minor compaction和major compaction的区别,以后讲文件压缩的时候再来讲这两者的区别,下面首先来说level0的查找。
首先level层级查找如下:
public LookupResult get(LookupKey key)
// We can search level-by-level since entries never hop across
// levels. Therefore we are guaranteed that if we find data
// in an smaller level, later levels are irrelevant.
ReadStats readStats = new ReadStats();
LookupResult lookupResult = level0.get(key, readStats);
if (lookupResult == null)
for (Level level : levels)
lookupResult = level.get(key, readStats);
if (lookupResult != null)
break;
updateStats(readStats.getSeekFileLevel(), readStats.getSeekFile());
return lookupResult;
level0单独寻找,level0寻找方法如下:
public LookupResult get(LookupKey key, ReadStats readStats)
if (files.isEmpty())
return null;
List<FileMetaData> fileMetaDataList = new ArrayList<>(files.size());
for (FileMetaData fileMetaData : files)
if (internalKeyComparator.getUserComparator().compare(key.getUserKey(), fileMetaData.getSmallest().getUserKey()) >= 0 &&
internalKeyComparator.getUserComparator().compare(key.getUserKey(), fileMetaData.getLargest().getUserKey()) <= 0)
fileMetaDataList.add(fileMetaData);
Collections.sort(fileMetaDataList, NEWEST_FIRST);
readStats.clear();
for (FileMetaData fileMetaData : fileMetaDataList)
// open the iterator
InternalTableIterator iterator = tableCache.newIterator(fileMetaData);
// seek to the key
iterator.seek(key.getInternalKey());
if (iterator.hasNext())
// parse the key in the block
Entry<InternalKey, Slice> entry = iterator.next();
InternalKey internalKey = entry.getKey();
checkState(internalKey != null, "Corrupt key for %s", key.getUserKey().toString(UTF_8));
// if this is a value key (not a delete) and the keys match, return the value
if (key.getUserKey().equals(internalKey.getUserKey()))
if (internalKey.getValueType() == ValueType.DELETION)
return LookupResult.deleted(key);
else if (internalKey.getValueType() == VALUE)
return LookupResult.ok(key, entry.getValue());
if (readStats.getSeekFile() == null)
// We have had more than one seek for this read. Charge the first file.
readStats.setSeekFile(fileMetaData);
readStats.setSeekFileLevel(0);
return null;
在level0中,可能会出现多个sstable文件参与读取,因此需要文件全部筛选出来,并且通过文件的修改时间进行排序,如下:
for (FileMetaData fileMetaData : files)
if (internalKeyComparator.getUserComparator().compare(key.getUserKey(), fileMetaData.getSmallest().getUserKey()) >= 0 &&
internalKeyComparator.getUserComparator().compare(key.getUserKey(), fileMetaData.getLargest().getUserKey()) <= 0)
fileMetaDataList.add(fileMetaData);
Collections.sort(fileMetaDataList, NEWEST_FIRST);
然后遍历这些文件,找到最近一次的修改记录,并将其返回,这就是level0的查找。
其他level中查找,代码如下:
// Binary search to find earliest index whose largest key >= ikey.
int index = ceilingEntryIndex(Lists.transform(files, FileMetaData::getLargest), key.getInternalKey(), internalKeyComparator);
// did we find any files that could contain the key?
if (index >= files.size())
return null;
// check if the smallest user key in the file is less than the target user key
FileMetaData fileMetaData = files.get(index);
if (internalKeyComparator.getUserComparator().compare(key.getUserKey(), fileMetaData.getSmallest().getUserKey()) < 0)
return null;
// search this file
fileMetaDataList.add(fileMetaData);
通过二分法找到符合条件的FileMetaData,然后最后通过加载索引缓存查找到数据,至于为什么只需要通过二分法找到FileMeta,然后将当前filemeta加载到缓存查看key就能定位到当前的key这个放到下一节内容中讲,这个和Leveldb的数据压缩有关,这一节就不说了。
以上是关于Leveldb读取数据源码分析的主要内容,如果未能解决你的问题,请参考以下文章