LevelDB源码阅读笔记(1、整体架构)

news/2024/5/17 13:48:54

LevelDB源码阅读笔记(1、整体架构)

LeveDB源码笔记系列:

LevelDB源码阅读笔记(0、下载编译leveldb)

LevelDB源码阅读笔记(1、整体架构)

前言

对LevelDB源码的博客,我准备采用总-分的形式进行记录,我觉得先了解一下LevelDB整体流程,再往后讲解各个基础组件的话,读者会更容易理解这样设计的用意。

性能简介

数据参考自leveldb的README:https://github.com/google/leveldb。

写性能

分析如下:

  1. 顺序写:保证数据按key递增插入到数据库中。因为一个sst中的数据密集,key之间差距不会特别大,所以压缩的时候相邻两层参与压缩的sst不会很多,压缩的压力不会特别大。

  2. 每次写入执行sync同步操作:不测也能知道性能不会高到哪里去。

  3. 随机写入:程序随机生成key值,插到leveldb中,相对于顺序写,这样会导致一个sst中的数据稀疏,key之间间隔过大,压缩的时候参与压缩的sst可能会更多,所以压缩的压力就会相对大点。

  4. 修改或删除数据:结果和随机写入类似,插入和删除会导致一个sst文件的key变的稀疏,导致和随机写差不多的性能。

数据如下:

# 顺序写
fillseq      :       1.765 micros/op;   62.7 MB/s# 每次写入执行sync同步操作
fillsync     :     268.409 micros/op;    0.4 MB/s (10000 ops)# 随机写入
fillrandom   :       2.460 micros/op;   45.0 MB/s# 修改或删除数据
overwrite    :       2.380 micros/op;   46.5 MB/s

读性能

分析如下:

  1. 随机读:具备不确定性,可能需要读n多个sst文件,如果sst文件不在cache中,又会涉及单个sst文件data index block的磁盘寻道和解压缩(可能需要解析n多个不在cache中的sst文件。)。所以性能上不去。

  2. 顺序读:level读数据是创建一个外部归并迭代器,顺序读从一定程度上减少了对一个sst文件的反复解析(因为cache有限,被缓存的sst会被lru算法置换出去),同时也减轻了data_block的反复解压和磁盘寻道的代价。

  3. 反向读:因为leveldb的sst文件data_block它的key是存在前缀压缩的,每个key相当于是一个增量key,想从一个key知道前一个key,必须从重启点重新解析,这个过程涉及到一个while循环,所以反向读性能会比顺序读要差点。

数据如下:

# 随机读
readrandom  : 16.677 micros/op;  (approximately 60,000 reads per second)# 顺序读
readseq     :  0.476 micros/op;  232.3 MB/s# 反向读
readreverse :  0.724 micros/op;  152.9 MB/s

根据leveldb的源码实现,考虑数据量很大,cache不命中的情况下,开销主要在每次读key至少都要进行的2次磁盘寻道(读data index block和data block),同时带来的data block的反复解压也是瓶颈所在,所以增大cache的大小一定程度上能改善leveldb的读性能。如下:

readrandom  : 9.775 micros/op;  (approximately 100,000 reads per second before compaction)
readrandom  : 5.215 micros/op;  (approximately 190,000 reads per second after compaction)

架构简介

图片引用自知乎(https://zhuanlan.zhihu.com/p/206608102)若有侵权,可联系我将其删除,LevelDB架构图如下:

整体架构

LevelDB内存中使用了跳表:mem_(内存跳表)、imm_(只读内存跳表)。

LevelDB在磁盘数据结构上:设计了SSTable只读磁盘数据结构。并且SST是按分层组织的。

使用两种形式的内存跳表:mem_和imm_,mem_相当于是前台的buff供用户读写数据,而imm_为写满了的mem_充当一种临时容器,这样做的好处是能让imm_和后台Compaction线程打交道(置换)。当后台Compaction线程繁忙时,不至于说写满了的mem_没地方放,从而需要阻塞去等待后台Compaction线程的压缩。imm_的角色本质上和Muduo异步日志的AsyncLogging::buffers_是一样的。当然LevelDB的前台缓存:mem_、imm_,后台Compaction线程的设计,是双缓冲技术的实践。

LevelDB的读流程

  1. 先读取内存中的跳表:mem_(内存跳表),找到返回,没找到继续。

  2. 再读内存中的只读跳表:imm_(只读内存跳表),找到返回,没找到继续。

  3. 按层查找sst文件。

    • 对于第一层sst文件,由于第一层sst文件之间是存在重叠的,所以会顺序查找第一层的每个和key有重叠的sst文件。(涉及多个sst文件)。

    • 对于其他层,由于leveldb在压缩的时候就保证了其sst之间是不存在重叠的,所以,其他层的查找就会采用二分的形式去定位可能包含key的sst文件,当然,这种情况下,其他层的key的查找最多涉及一个sst文件。(最多涉及一个sst文件)。

  4. 找到返回OK和对应的value,找不到返回NotFound。

从后面SST以及双层迭代器的源码分析我们就可以看到,因为sst结构设计的精妙,LevelDB对SST的查找也是使用了二分。这里就不过多赘述。

LevelDB的读流程图如下:

ReadProcess

LevelDB的写流程(删除,插入,修改

  1. 如果存在多个线程并发写,将请求写的所有线程放到一个队列里面,仅让位于对头的线程执行下面真正的写操作,其他线程阻塞在条件变量上。

  2. 执行MakeRoomForWrite,确保mem_有足够的空间写。如果第1层的sst文件数达到阈值config::kL0_SlowdownWritesTrigger(默认8个sst),就触发慢写(sleep1000微妙后再来尝试MakeRoomForWrite);如果第1层的sst文件达到阈值config::kL0_StopWritesTrigger(默认12个sst),就触发停止写(阻塞在条件变量上,等待Compaction线程的唤醒);如果mem_满了 && imm_为nullptr,就将mem_转换为imm_,并清空mem_;如果mem_满了 && imm_非nullptr,也即后台Compaction线程繁忙,来不及压缩imm_,就阻塞在条件变量上,等待后台Compaction线程的唤醒。

  3. 做一个BuildBatchGroup的操作:将其他线程请求写的kv对 合并。

  4. 将3合并的数据写到WAL日志中。

  5. 将3合并的数据统一写到mem_中。

  6. 唤醒其他阻塞的线程,并且从队列中移除。同时,必要的话,唤醒下一个队头执行写操作。

在LevelDB中,删除、插入、修改均是向mem_中插入一条包装过的key(InternalKey)。

InternalKey的组成:

InternalKey

  • UserKey:用户提供的key。

  • SequenceNumber:由全局逻辑计时器提供,保证key不重复。

  • ValueType:存在两个值:kTypeDeletion和kTypeValue。

删除:是插入一条ValueType为kTypeDeletion的InternalKey。插入和修改:都是插入一条带kTypeValue的InternalKey。

那么LevelDB是如何确定一个key最新状态是删除还是插入呢?如果对key进行了修改,又如何知道它的最新值能?删除也是插入key的话,怎么保证删除之前的key删除干净呢?

要解决上面的问题,SequenceNumber字段就起到了关键作用。LevelDB整体上会以UserKey升序,当UserKey相同时,会以SequenceNumber降序排列。因为SequenceNumber最大的靠前排,这样就保证了在查询的时候最先获取到一个key的最新状态(是否删除、没删除的话最近一次修改的Value是多少?)。而真正的删除会在压缩阶段进行。

引用陈硕的话,这其实是一种记流水账的思想(追加写),不管是删除、插入还是修改,都是在末尾记录一下,而不会去动以前的记录。这也是LevelDB精华所在。

LevelDB的压缩流程

压缩作为LSMTree的核心组件之一,在LevelDB中由后台专门的一个线程执行。本质上讲压缩就是剔除key的历史记录,只保留key最新的记录。压缩过程中同样重要的有:压缩时机?压缩哪一层的哪一个sst?对sst以什么策略进行分割?如何控制sst和祖父间的关系?为什么LevelDB设置成7层?sst文件大小为什么默认2M?每层的sst文件数量为什么默认是那么多?

这些问题LevelDB都有具体的策略和参数进行控制,目前作者的功力有限,有些地方还未能完全明白用意,所以此处仅简述一下大概的压缩流程。

压缩流程图如下:

CompactionProcess

具体流程如下述:

  1. 确定要压缩的sst文件,假设该sst文件在level层,收集level层和level + 1层和sst文件有重叠的所有sst文件。

  2. 将1中收集到的sst文件建立成一个多路归并迭代器。并将迭代器指向首个元素(最小的)。

  3. 遍历归并迭代器,迭代器的元素按UserKey升序 && UserKey相同的按SequenceNumber降序排列。对于同一个key,由SequenceNumber最大(最新)的ValueType决定该key的状态,然后直接忽略所有其他SequenceNumber过小(状态过时)的key。

    • 对于带TypeDeletion标签的key(假设Internal Key为kD),如果level + 2以及以外的层不可能也包含k,该key就会在本次压缩中直接删除。如果level + 2以及以外的层可能也包含k,kD会保留下来,写到sst文件中,在随后的压缩中再一起删除干净。

    • 对于带kTypeValue标签的key,保留SequenceNumber最大的那个即可,其余的忽略。

  4. 在遍历归并迭代器过程中,会按序建立sst文件,建立sst文件的过程中,还会按设定大小对sst文件进行一个切割,防止单个sst文件过大。单个sst文件与祖父层重叠大小太大也会被切割,重新建立一个新的sst文件。

  5. 将输出的sst文件放到level + 1层。

总结

理想情况下:

  • LevelDB的读操作:最多会涉及2次磁盘IO(读data index block和data block)。

  • LevelDB的写操作(插入、修改、删除):是直接插到内存,不涉及磁盘IO。但在随后的压缩的过程中,可能会涉及一些磁盘IO(在归并迭代器中遍历sst时涉及磁盘读,将压缩的数据输出到磁盘时涉及磁盘顺序写)。


本章完结


http://www.mrgr.cn/p/87008863

相关文章

ROS2笔记4--服务通讯

ROS2中话题通讯可以实现多个ROS节点之间数据的单向传输,不过话题通讯是一种异步通信机制,发布者无法准确知道订阅者是否收到消息。而服务通信是一种基于请求响应的通信模型,在通信双方中客户端发送请求数据到服务端,服务端响应结果给客户端。 从服务实现机制看这种形式是C…

从系统设计到撸代码?我用了这些方法和工具

大家在撸代码之前会进行业务系统流程设计么?你们在设计的时候又用到了哪些工具呢?大家好,我是老猫。今天和大家分享一下程序员日常的绘图思路,以及一些老猫日常使用的绘图工具。 为什么要画图? 我们在进行系统设计的时候,为了更加具象地呈现系统的轮廓以及各个组件或者系…

element-plus 实现 table 列动态显示,宽度调整,顺序调整

效果图实现原理定义一个数组,保存列的属性, 标题,是否显示,宽度等 v-for 循环,动态设置列 弹出界面来修改这个数组,就实现了.

Linux上的uname

2024年4月19日,周五上午 uname 是一个常用的命令行工具,uname 的全称是 “Unix Name”,它是一个 Unix 和类 Unix 操作系统上的命令行工具,用于获取操作系统相关的信息,如内核版本、系统架构、主机名等。 它通常用于查…

GreatSQL 死锁案例分析

1.背景概述 客户业务发生死锁的报错,根据业务程序日志及业务流程,发现造成死锁的原因是:事务1 delete + insert ,事务2 delete + insert 2个事务交替执行导致的死锁;由于GAP锁阻塞了插入意向锁,并且当delete的数据存在时死锁不会发生,当delete的数据不存在时,会发生死…

如何基于香橙派AIpro对视频/图像数据进行预处理

昇腾CANN提供了两种专门用于数据预处理的方式:AIPP和DVPP。本文分享自华为云社区《如何基于香橙派AIpro对视频/图像数据进行预处理》,作者: 昇腾CANN。 受网络结构和训练方式等因素的影响,绝大多数神经网络模型对输入数据都有格式上的限制。在计算机视觉领域,这个限制大多…

yolov7模型输出层预测方法解读

本文从代码的角度分析模型训练阶段输出层的预测包括以下几个方面: 标注数据(下文统称targets)的正样本分配策略,代码实现位于find_3_positive。候选框的生成,会介绍输出层的预测值、GT、grid、 anchor之间的联系损失函…

ROS笔记[2]-获取OpenMV数据并发布到ROS消息

Orangepi(香橙派)通过USB-CDC获取OpenMV数据并使用Python发布到ROS的/openmv主题,实现打印"hello ros"字符串.摘要 Orangepi(香橙派)通过USB-CDC获取OpenMV数据并使用Python发布到ROS的/openmv主题,实现打印"hello ros"字符串. 关键信息python3.8 ROS1:Noe…

vue引入字体icon

这里我用的是阿里图标库 1.2.3.4.在vue的assets文件中增加一个font文件把解压后的文件复制进去,并在mian.js中引入iconfont.css5.1.使用,复制以下代码在页面中使用 <h1>欢迎 <i class="iconfont">&#xe67c;</i></h1> 5.1.2使用,复制一…

万象奥科邀您参加RK3568+AMP混合部署线下实操活动-北京站

4月25日,万象奥科将携手RT-Thread在北京举办线下workshop,带您体验RK3568+OpenAMP实现RT-Thread与Linux同时运行的开发方式,实现在电力、机器人、工业控制、工业互联网、新能源等领域的高效应用。4月25日,万象奥科将携手RT-Thread在北京举办线下workshop,带您体验RK3568+O…

副本和就删码

分类 按照存储的结构存储可以分为集中式存储和分布式存储集中式存储 传统集中式存储采用控制器+硬盘柜的方式,通过冗余的双控制器提供数据管理和读写能力(也有超过2个控制器的多控存储,多见于高端存储),通过控制器自带的硬盘槽位或扩展硬盘柜提供存储空间,如下图。集中式…

多因子模型的因子分组-聚类分析

优质博文&#xff1a;IT-BLOG-CN 之前我们已经介绍了简单、高效的克隆巴赫α系数和科学有效的主成分分析对因子进行分组&#xff0c;我们将继续介绍一种复杂的方法----聚类分析&#xff08;Cluster Analysis&#xff09;。 聚类分析根据多个因子某一方面的相似性进行归类&…

JMeter下载与环境配置

前置 使用JMeter前需要先配置好Java的环境变量,不会的朋友可以阅读:https://www.cnblogs.com/test-gang/p/18144441 JMeter下载 JMeter官网:https://jmeter.apache.org/ 进入官网后,点击左边Download Releases进入Download Releases,页面会展示两种版本Source 是源代码版,…

DbMigrator迁移数据库报错:The ConnectionString property has not been initialized.

问题 执行.DbMigrator时报错:The ConnectionString property has not been initialized.原因 情况一 DbContext中没有指定连接字符串 解决方案情况二 appsettings.json 配置文件的属性没有设置为始终复制 解决方案 右键appsettings.json选择属性>复制到输出目录选择始终复制…

矩阵求导(一)

前言 在大学的微积分课程中,我们学习过关于标量函数的导数。但是当我们求解一个多元函数的极值时,单独一个自变量的偏导数往往不能告诉我们太多信息,于是我们有一种天然的想法是要把每个自变量的偏导数放在一起,看看他们的联合效果如何。这个过程其实是一个向量求导的过程。…

HTTP/HTTPS详解

HTTP/HTTPS详解 1. HTTP1.1 HTTP基础知识1.2 HTTP建立和断开连接 2. HTTPS 1. HTTP 1.1 HTTP基础知识 HTTP是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用 于从WWW服务器传输超文本到本…

他来了他来了,.net开源智能家居之苹果HomeKit的c#原生sdk【Homekit.Net】1.0.0发布,快来打造你的私人智能家居吧

背景介绍 hi 大家好,我是三合,作为一个非著名懒人,每天上完班回到家,瘫在沙发上一动都不想动,去开个灯我都嫌累,此时,智能家居拯救了我,只需要在手机点点点,开关灯,空调,窗帘就都搞定了,一开始我用的是开源的home assistan,俗称HA,搭配上hass-xiaomi-miot以及hap…

hbase-2.2.7分布式搭建

一、下载上传解压 1.在官网或者云镜像网站下载jar包 华为云镜像站&#xff1a;Index of apache-local/hbase/2.2.7 2.上传到linux并解压 tar -zxvf hbase-2.2.7-bin.tar.gz -C /usr/locol/soft 二、配置环境变量 1. vim /etc/profile export HBASE_HOME/usr/local/soft/h…

如何在 Netlify 上手动部署 React 和 TypeScript 项目

在本教程中,我将教你如何使用 Vite 在 Netlify 上手动部署 React 和 TypeScript 项目。我将向你展示一些快速简单的步骤,让你的项目能够立即运行。 要跟着本教程操作,有几个先决条件:一个现有的 React 和 TypeScript 项目,使用 Vite 构建,并且你想要部署它。 Visual Stud…

nexus 配置 docker-ce yum 源

环境说明服务 ip 端口 备注nexus 192.168.80.129 (内网) 8081 内网地址无法访问外网centos7.9192.168.80.133 安装docker服务nginx192.168.80.128 (内网) 192.168.174.126 (外网) 88 19000192.168.174.126 地址可以访问外网创建 Blob Stores创建 Repositoriesnginx 配置 server…