您的当前位置:首页正文

Leveldb读取数据源码分析

2024-11-27 来源:个人技术集锦

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的读取过程如下:

在这里,并不是说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的数据压缩有关,这一节就不说了。

显示全文