Leveldb读取数据源码分析

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leveldb读取数据源码分析相关的知识,希望对你有一定的参考价值。

Leveldb读取数据源码分析

上一篇文章讲了Snapshot源码,在这一篇文章中原本是要讲version、versionSet这些,但是想想还是从Leveldb的读取和存储开始讲起,Leveldb的存储比较简单,主要麻烦的还是读取这一块,需要判断从memTablememTable、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中取数据,immutableMemTablememTable相同,只是一个可变一个不可变,我们在以后讲Leveldb的数据压缩时再来讲immutableMemTable

从sstable中获取数据


当我们从内存中没有获取到数据时,就需要从sstable中获取数据,sstable的读取过程如下:

查找sstable file的元数据信息(索引信息),根据索引查找感兴趣的key位于哪个block,进而读出block,然后从block中读出key。

在这里,并不是说Leveldb中的每一层级的Level的读取过程都是一样的,在Leveldb中Level0与其他level并不相同,level0中sstable的key的范围可能会有重叠,因此一个key可能会出现在多个sstable中,因此需要进行一些特别的处理,但在其他level中就不会出现,这就是minor compactionmajor 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读取数据源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Leveldb数据Compaction源码分析

LSM-Tree - LevelDb 源码解析

[Ethereum] 以太坊源码分析ethdb源码分析

[Ethereum] 以太坊源码分析ethdb源码分析

经典开源代码分析——Leveldb高效存储实现

LevelDB 源码剖析WAL模块:LOG 结构读写流程崩溃恢复