从 RocksDB 看 LSM-Tree 算法设计,2024年最新快手java社招面经

  1. 完成 WAL 写入后,将数据写入到 内存中的 active memtable 中(为了保证有序性,RocksDB 使用跳表数据结构实现 memtable);

  2. 然后等 memtable 数据达到一定规模时,会转变成 immutable memtable,同时生成新的 memtable 提供服务;

  3. 在满足落盘条件后,immutable memtable 会被合并刷入到硬盘的 SST 中;

顺带一提,默认情况下 RocksDB 中的写磁盘行为都是异步写,仅仅把数据写进了操作系统的缓存区就返回了(pageCache),而这些数据被写进磁盘是一个异步的过程。异步写的吞吐率是同步写的一千多倍。异步写的缺点是机器或者操作系统崩溃时可能丢掉最近一批写请求发出的由操作系统缓存的数据,但是 RocksDB 自身崩溃并不会导致数据丢失。而机器或者操作系统崩溃的概率比较低,所以大部分情况下可以认为异步写是安全的

4. Compaction

LSM 树将离散的随机写请求都转换成批量的顺序写请求(WAL + memtable),以此提高写性能。但也带来了一些问题:

  • 读放大(Read Amplification)。按照上面【读操作】的描述,读操作有可能会访问大量的文件;

  • 空间放大(Space Amplification)。因为所有的写入都是顺序写(append-only)的,不是在对数据进行直接更新(in-place update),所以过期数据不会马上被清理掉。

所以维护和减少 SST 文件数量是很有必要的。RocksDB 会根据配置的不同 Compaction 算法策略,进行 Compaction 操作。Compaction 操作会删除过期 或者标记为删除/重复的 key,对数据进行重新合并来提高查询效率。

4.1 Level Style Compaction (default compaction style)

默认情况下,RocksDB 采用 Level Style Compaction 作为 LSM 树的 Compaction 策略。

如果启用 Level Style Compaction,L0 存储着 RocksDB 最新的数据,Lmax 存储着比较老的数据,L0 里可能存着重复 keys,但是其他层文件则不可能存在重复 key。每个 compaction 任务都会选择 Ln 层的一个文件以及与其相邻的 Ln+1 层的多个文件进行合并,删除过期 或者 标记为删除 或者 重复 的 key,然后把合并后的文件放入 Ln+1 层。

compaction

Compaction 虽然减少了读放大(减少 SST 文件数量)和空间放大(清理无效数据),但也因此带来了写放大(Write Amplification)的问题(底层 I/O 被 Compaction 操作消耗,会大于上层请求速递)

RocksDB 还有支持的其他的 Compaction 策略。

4.2 Universal Compaction

  • 只压缩 L0 的所有文件,合并后再放入 L0 层里;

  • 目标是更低的写放大,并且在读放大和空间放大中 trade off;

具体算法本篇不深究;

4.3 FIFO Compaction

  • FIFO 顾名思义就是先进先出,这种模式周期性地删除旧数据。在 FIFO 模式下,所有文件都在 L0,当 SST 文件总大小超过 compaction_options_fifo.max_table_files_size,则删除最老的 SST 文件。对于 FIFO 来说,它的策略非常的简单,所有的 SST 都在 Level 0,如果超过了阈值,就从最老的 SST 开始删除。

  • 这套机制非常适合于存储时序数据。

⭐ LSM 树 设计思想总结


LSM 树的设计思想很有意思。我这里做下总结。

LSM 树将对磁盘的随机写入转化为了磁盘友好型的顺序写(无论机械磁盘还是 SSD,随机读写都要远远慢于顺序读写),从而大大提高了写性能。

那么是怎么转化的呢?核心就是在内存中维护一个有序的内存表(memtable),当内存表大于阈值的时候批量刷入磁盘,生成最新的 SSTable 文件。因为本身 memtable 已经维护了按键排序的键值对,所以这个步骤可以高效地完成。

写入内存表时先将数据写入 WAL 日志,用以在发生故障时,通过重放 WAL 恢复内存中的数据,保证数据库的数据一致性。

在这种追加(append-only)写入模式下,删除数据变成了对数据添加删除标记,更新数据变成了写入新的 value,在同一个时间数据库中会存在同个 key 的新值和旧值。这种影响被称之为 空间放大(Space Amplification)

随着数据的写入,底层的 SSTable 文件也会越来越多。

读请求在这个模式下,变成了先在内存中寻找关键字,如果找不到则在磁盘中按照新-> 旧查找 SSTable 文件。为了优化这种访问模式的读性能,存储引擎通常使用常见的针对读的优化策略,比如使用额外的 Bloom Filter、读 Cache

这种需要多次读取的过程(或者说影响)被称之为读放大(Read Amplification)。很显然,读放大会影响 LSM 树的读性能。

为了优化读性能(读放大),同时优化存储空间(空间放大),LSM 树通过在运行合并和压缩过程减少 SSTable 文件数量,删除无效(被删除或者被覆盖) 的旧值。这一过程被称之为 compaction

但是 compaction 也会一些影响,在数据库的生命周期中每次的数据写入实际上会造成多次的磁盘写入。这种影响被称之为写放大(Write Amplification)。在写入繁重的应用程序中,性能瓶颈可能是数据库可以写入磁盘的速度。在这种情况下,写放大会导致直接的性能代价:存储引擎写入磁盘的次数越多,可用磁盘带宽内的每秒写入次数越少。

这也是我认为 LSM 引擎存储的一个缺点,就是压缩过程有可能会干扰到正在进行的读写请求。尽管存储引擎尝试逐步执行压缩而不影响并发访问,但是磁盘资源有限,所以很容易发生请求需要等待磁盘完成昂贵的压缩操作。对吞吐量和平均响应时间的影响通常很小,但是如果是高百分位情况下(如 P99 RT),有时就会出现查询响应较长的情况。

上述是对 LSM 树的个人总结,一些没有提到实现细节的抽象描述(比如 memtable、compaction),在实际的存储中都有对应的实现,细节可能不同,但是设计思想都是类似。

在本文具体提到的 RocksDB 实现中,写放大、读放大、空间放大,这三者就像 CAP 定理一样,无法同时达到最优。为此 RocksDB 暴露了很多参数来让使用者进行调优,以适应更多的应用场景。这其中很大一部分工作是在写放大、读放大和空间放大这三个放大因子之间做好 trade off。

Elasticsearch Lucene 中的类 LSM 设计思想

ES 底层搜索引擎 Lucene 的 segment 设计思想和 LSM 树非常相似。也运用到了WAL、内存 buffer、分段merge等思想。

一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog。

img

随着当前分片(shard)的 refresh 操作,这些在内存缓冲区的文档被 flush 到一个新的 segment 中,这个 segment 被打开,使其可被搜索,对应的内存缓冲区被清空。

img

随着数据的写入,还会触发 commit 操作,做一次全量提交,然后对应的 Translog 会被删除。(具体过程限于篇幅不赘述)。

segment 被 refresh 或 commit 之前,数据保存在内存中,是不可被搜索的,这也就是为什么 Lucene 被称为提供近实时而非实时查询的原因。

Segment 中写入的文档不可被修改,但可被删除,删除的方式也不是在文件内部原地更改,而是会由另外一个文件保存需要被删除的文档的 DocID,保证数据文件不可被修改。Index的查询需要对多个 Segment 进行查询并对结果进行合并,还需要处理被删除的文档,为了对查询进行优化,Lucene会有策略对多个Segment进行合并,这点与 LSM 对 SSTable 的 compaction 类似。

这种机制避免了随机写,数据写入都是 Batch 和 Append,能达到很高的吞吐量。同时为了提高写入的效率,利用了文件缓存系统和内存来加速写入时的性能,并使用日志来防止数据的丢失。

⭐ LSM 树 vs B+ 树


讲到这里了,想想不如再和我们常见的 B+ 树做一些对比。

设计理念不同

虽然像 LSM 树一样,B+ 树保持按键排序的键值对(这允许高效的键值查找和范围查询),但是两者设计理念完全不同。

  • LSM 树将数据库分解为可变大小的段,通常是几兆字节或更大的大小,并且总是按顺序编写段。

  • 相比之下,B+ 树将数据库分解成固定大小的块或页面,传统上大小为 4KB(有时会更大),并且一次只能读取或写入一个页面。这种设计更接近于底层硬件,因为磁盘也被安排在固定大小的块中。

数据的更新和删除方面

  • B(+) 树可以做到原地更新和删除(in-place update),这种方式对数据库事务支持更加友好,因为一个 key 只会出现一个 Page 页里面;

  • 但由于 LSM 树只能追加写(out-place update),并且在 L0 层的 SSTable 中会重叠,所以对事务支持较弱,只能在 compaction 的时候进行真正地更新和删除。

性能方面

  • LSM 树的优点是支持高吞吐的写(可认为是 O(1)),这个特点在分布式系统上更为看重,当然针对读取普通的 LSM 树结构,读取是 O(n) 的复杂度,在使用索引或者缓存优化后的也可以达到 O(logN)的复杂度。

  • B+ 树的优点是支持高效的读(稳定的 O(logN)),但是在大规模的写请求下(复杂度 O(LogN)),效率会变得比较低,因为随着 insert 的操作,为了维护树结构,节点会不断的分裂和合并。操作磁盘的随机读写概率会变大,故导致性能降低。

通常来说,我们会说,LSM 树写性能会优于 B 树,而 B 树的读性能会优于 LSM 树。但是请不要忽略 LSM 树写放大的影响,在进行性能判定是要更辩证的思考。

参考

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后我们该如何学习?

1、看视频进行系统学习

这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Mybatis 3源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Redis学习笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Spring Boot核心技术-笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

统的面试题,帮助你举一反三:

[外链图片转存中…(img-eRL21LoZ-1712701005506)]

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-1NvwBPte-1712701005506)]

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值