上一篇文章讲了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的读取过程如下:
在这里,并不是说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的数据压缩有关,这一节就不说了。