RocksDB是一个高性能的持久键值存储引擎,由Facebook在2012年创建,基于谷歌的LevelDB代码。RocksDB是基于LSM的,对SSD进行了优化,被多个数据库当做存储引擎使用,并且被使用在流处理、日志队列服务以及索引服务和SSD缓存之上。
引擎架构
MemTable与WAL
RocksDB使用LSM作为主要的存储数据结构,每当数据写入到RocksDB之中,就会被添加到MemTable内存的写缓冲区,以及一个磁盘上的超前写入日志(WAL)。数据会被写入到WAL和MemTable,WAL是MemTable的易失性保护机制。
RocksDB中Memtable的数据结构有三种,分别是skiplist、hash-skiplist、hash-linklist,跳表的好处在于插入的时候可以保证数据的有序,并且支持二分查找、范围查询。插入和搜索的代价都是O(log n)。
在达到指定大小之后现有MemTable和WAL锁定变为不可变,新数据写入新的MemTable和WAL。
SSTable
SSTable是一种数据结构,当MemTable到达一定的上限之后,会flush到硬盘上Sorted String Table (SSTable),并放置在第0层(L0),对应的WAL空间回收;L0大小达到上限时,L0的SSTable经过compaction落到L1;Ln:以此类推完成上述操作。
在每一级中,都会使用二进制搜索;而布隆过滤器会消除SSTable文件中不必要的检索。
Compaction
Campact可以翻译成压缩,也可以被称为压实,其作用主要有:清除无效数据;对数据的排序进行优化。而无效数据的产生于update:标记覆盖与delete:标记删除。使用Compact的好处有:整个文件的批量读写,比较高效;可以并行化操作。
比较几种常见的Compaction策略:
● Leveled:leveldb的方式,SSTable数量逐层以指数增长,读写放大都比较严重,但是空间放大较低;
● Tired/Universal:Cassandra与HBase的方式,空间放大和读放大严重,但是写放大最小;
● FIFO:删除旧的过期文件,执行轻量级压缩,适用于那些in-memory缓存应用。
大家也可以发现,并不存在写放大、读放大和空间放大都很完美的方案;和计算机世界的其他领域一样,数据库也没有银弹。
资源目标的优化
针对于RocksDB的问题,很多团队也对齐进行了资源目标的优化,我们选取了两点进行资源目标优化的讨论,分别是写放大和空间放大。
写放大
写放大主要来源于以下几个层面:
● 固态硬盘本身会带来1.1到3的写入放大;
● 当4kb/8kb/16kb大小的页面被写入不到100字节的变化时,可能会产生100倍的写入放大;
● 不同的Compaction策略有不同的写入放大比率。
针对于以上的写放大,有如下优化建议:
● 在写入速率高时,减少写放大;
● 在写入速率低时,积极压缩;
● Value较大时,可以考虑使用BlobDB分离Key和Value。
空间放大
在使用SSD时,空间利用率比写入放大率更重要,因为闪存的写入周期和写入开销都不受限制。优化空间放大,可以使用动态分层压缩,每一层大小根据上一层实际大小自动调整,而不是每层都是固定的大小,能够更有效地利用空间。
大规模系统的经验
RocksDB应用于不同需求的大规模分布式系统的构建,我们也从一下几点去阐述相关经验和改进思路。
资源管理
大规模的分布式系统会将数据划分为Shard,分布在多个服务器节点上进行存储。同时,一台主机内部可能运行多个RocksDB的实例,需要对资源进行管理,包括全局(每台主机)和局部(每个实例)。
需要管理的资源包括:
● 内存:Write buffer、Block cache
● I/O带宽:Compaction
● 线程:Compaction,最好使用线程池
● 磁盘使用量
● 文件删除频率
并且尽量满足以下要求:每个实例都不能过度使用任何资源;不同实例之间可以设置资源使用的优先级。
WAL
在传统的数据库中,倾向于进行WAL的操作,以确保持久性。在分布式场景中,数据有副本,并且进行一致性的校验。如果使用Paxos进行副本间的数据同步,那么WAL是无用的。同时,可以提供三种不同的WAL策略操作:同步写;缓冲异步写;不写。
限制文件删除
有些文件系统能够识别出SSD,比如XFS。进行文件删除时,文件系统会向SSD发出TRIM指令,通常情况下可以提高SSD的性能与耐用性,但也可能导致性能问题。TRIM除了更新SSD内部内存中的地址映射,还会触发SSD内部的垃圾回收,导致大量的数据移动,提高IO延迟。所以文件删除速率需要被限制,防止Compaction过程中的多个文件同时被删除。
数据格式的兼容性
大规模分布式系统几乎必须提供在线的滚动升级与回退的功能,所以从底层的数据格式设计上就必须保证向前和向后的兼容性。那么可以采用以下的相关策略:
● 所有版本必须能够理解所有以前写入磁盘的格式
● 未来的格式也应该能够被理解
● 可以考虑使用ProtocolBuffer和Thrift中的相关技术
● 配置文件配置项的前后兼容性设计
● 做好版本兼容性测试
复制与备份
RocksDB是一个单节点的存储引擎,所以无论以任何方式进行复制和备份,RocksDB都要进行相关的支持。两种方式可以支持现有的副本复制:
● 逻辑复制:所有的键值对都可以被读出来,写到数据副本中
○ 支持全数据扫描操作,并最小化对正常读写操作的性能影响
○ 逻辑复制的查询结果,不会进行缓存
○ 在目标端支持批量加载,并进行相应的优化
● 物理复制:直接复制SSTable和其他文件。
○ 物理复制的接口应由引擎提供,而不是让用户或者其他应用程序去直接操作数据文件
在逻辑复制的部分是基于既有数据文件的复制,基于文件的复制更适合创建新的副本时的全量复制。而物理复制则为全量复制提供了一个速度更快的选择。
复制的机制还与一致性有关,同时备份也包括逻辑备份和物理备份,与复制的区别是,应用程序需要多个备份。总体而言备份与复制使用的方法与手段相差不多,但从用户易用性的角度出发,存储引擎应当有管理多个备份的功能。
故障的教训
出于性能方面的考虑,用户很可能不使用SSD的数据保护(DIF、DIX),存储介质的损坏需要由存储引擎的块校验检测出来。除此之外,复制、备份、数据恢复过程中也可能引入损坏的数据。
数据损坏应当尽早被发现,以减少停机时间和数据损失。一份数据应当在多个主机上进行复制;当检测到checksum不匹配时,损坏的副本被断开,并由正确的副本接替;同时,需要至少保证有一个正确的副本。多层校验包括:
● 块:每个SSTable块和WAL块都有一个附加的校验和,在数据被创建时产生;每次读取数据时都会进行验证,防止损坏数据暴露给用户;
● 文件:每个SSTable文件有自己的校验和,校验和记录在SSTable的元数据中;
● Handoff:这是一种向文件系统写入数据时同时传下去的校验和,由文件系统的一些机制来进行校验;但是不是所有的文件系统都支持这种校验,所以要提前做好规划。
结语
通过本文,希望大家对RocksDB有更好的认知,同时也可以更好的将RocksDB应用于各种大规模分布式系统。