Tair存储引擎之一Leveldb中数据的存储思想

1. Tair ldb简单介绍

1.1 tair非持久化/持久化存储引擎

tair 是淘宝自己开发的一个分布式 key/value 存储引擎. tair 分为持久化和非持久化两种使用方式. 非持久化的 tair 可以看成是一个分布式缓存. 持久化的 tair 将数据存放于磁盘中. 在最新版本的tair trunk中目前实现了以下4种存储引擎。


非持久化:mdb

持久化:fdb、kdb和 ldb

分别基于四种开源的key/value数据库:Memcached、Firebird、Kyoto Cabinet和LevelDB。其中memcached和Firebird是关系型存储数据库,而Kyoto Cabinet和LevelDB是Nosql数据库。

这里我研究的是Google开源的快速轻量级的单机KV存储引擎leveldb的实现方式。

1.2 leveldb简单介绍

leveldb的基本特性:

  • 提供key/value支持,key和value是任意的字节数组
  • 数据按key内部排序
  • 调用者可以提供自定义的比较函数修改排序规则
  • 基本的接口有 Put(key,value), Get(key), Delete(key).
  • 支持批量修改(原子操作)
  • 用户可以创建临时snapshot来获得数据的一个视图
  • 支持正向和反向遍历数据
  • 使用Snappy compression library对数据进行压缩
  • 外部操作(例如文件系统操作)通过虚接口传递使得用户可以自定义与操作系统的交互

限制:

  • 非SQL数据库,因此不支持关系数据模型,也就不支持SQL语句查询以及索引查询
  • 一次只能有一个进程(可能是多线程的)访问数据库
  • 没有内置客户端-服务器的支持,有这个需求的应用程序必须自己实现封装

2.  leveldb中数据的存储结构—LSM

说到存储引擎我立刻想到MySQL的两大常用存储引擎myisam和innodb,关系型数据库的数据存储在逻辑的表空间中,而数据库索引一般都是用的B/B+树系列,包括MySQL及NoSQL中的MongoDB。那为什么在磁盘中要使用B+树来进行文件存储呢?首先磁盘本身是一个顺序读写快,随机读写慢的系统,磁盘的性能主要受限于磁盘的寻道时间,那么如果想高效的从磁盘中找到数据,就需要减少寻道次数,也就是尽量减少磁盘的IO次数,而磁盘IO次数又取决于数据在磁盘上的组织方式。B+树是一种专门针对磁盘存储而优化的N叉排序树,以树节点为单位存储在磁盘中,从根开始查找所需数据所在的节点编号和磁盘位置,将其加载到内存中然后继续查找,直到找到所需的数据。B+树的存储方式使得具有良好的查找、插入和修改的性能,但如果有大量的更新插入删除等综合写入,最后会因为需要循环利用磁盘块而出现较多的随机IO,大量时间消耗在磁盘寻道时间上,如果是一个运行时间很长的B+树,那么几乎所有的请求,都是随机IO。因为磁盘块本身已经不再连续,很难保证可以顺序读取。这是B+树在磁盘结构中最大的问题。

那么如何能够解决这个问题呢?

目前主流的思路有以下几种


(1)放弃部分读性能,使用更加面向顺序写的树的结构来提升写性能。

这个类别里面,从数据结构来说,就我所知并比较流行的是两类,

一类是COLA(Cache-Oblivious Look ahead Array),代表应用是tokuDB。

一类是LSM tree(Log-structured merge Tree)或SSTable,代表的应用有Cassandra、HBase、levelDB 及众多类BigTable存储。

(2)使用ssd,让寻道成为往事。

Leveldb的数据存储方式采用的是LSM(log-structured-merge)的实现方法,简单来说就是将原来的直接维护索引树变为增量写的方式,这样能够保证对磁盘的操作是顺序的。具体的LSM实现可以看”The log-structured merge-tree“这篇论文。Log-Structured的思想最早由 Rosenblum和Ousterhout于1992年在研究日志结构的文件系统时提出。他们将整个磁盘就看做是一个日志,在日志中存放永久性数据及其索引,每次都添加到日志的末尾;通过将很多小文件的存取转换为连续的大批量传输,使得对于文件系统的大多数存取都是顺序性的,从而提高磁盘带宽利用率,故障恢复速度快。
O‘Neil等人受到这种思想的启发,借鉴了Log不断追加(而不是修改)的特点,结合B-tree的数据结构,提出了一种延迟更新,批量写入硬盘的数据结构LSM-tree及其算法。LSM-tree努力地在读和写两方面寻找一个平衡点以最小化系统的存取性能的开销,特别适用于插入频率远大于查询频率的应用场景。

LSM树可以看作是一个N阶合并树。数据写操作(包括插入、修改、删除)都在内存中进行,并且都会创建一个新记录(修改会记录新的数据值,而删除会记录一个删除标志),这些数据在内存中仍然还是一棵排序树,当数据量超过设定的内存阈值后,会将这棵排序树和磁盘上最新的排序树合并。当这棵排序树的数据量也超过设定阈值后,和磁盘上下一级的排序树合并。合并过程中,会用最新更新的数据覆盖旧的数据(或者记录为不同版本)。

在需要进行读操作时,总是从内存中的排序树开始搜索,如果没有找到,就从磁盘上的排序树顺序查找。

在LSM树上进行一次数据更新不需要磁盘访问,在内存即可完成,速度远快于B+树。当数据访问以写操作为主,而读操作则集中在最近写入的数据上时,使用LSM树可以极大程度地减少磁盘的访问次数,加快访问速度。

3. 分析Get/Put/Delete和对应Prefix系列接口的数据在leveldb中存储特点

在了解LSM后,需要知道基于LSM结构的存储引擎的数据有什么存储特点,下面简单总结几点。

(1)leveldb中各个存储文件是分层的,新插入的值放在内存表中,称为memtable(通过skiplist实现),该表写满时变为immutable table,并建立新的memtable接收写操作,而immutable table是不可变更的,会通过Compact过程写入level0,其中的数据被组织成sstable的数据文件,所以,同时最多会存在两个memtable(正在写的memtable和immutable memtable)。level0的文件会通过后台的Compact过程写入level1,level1的文件又会写入level2,依次类推,这是”Merge
Dump“的流程。

(2)leveldb在写操作时只是单纯的在文件末尾增加一条记录而不会改动原来的数据,更新key直接插入一条新的key/value数据(即key已经存在),而删除操作可以看成插入一条value为空的数据,为了区分真实kv数据和删除操作的mock数据,使用ValueType来标识:

enum ValueType {
    kTypeDeletion = 0x0,
    kTypeValue = 0x1
};

除此之外,leveldb每次更新(Put/Delete)操作都拥有一个版本,由SequnceNumber来标识,整个db有一个全局值保存着当前使用到的SequnceNumber。SequnceNumber在leveldb有重要的地位,key的排序,compact以及snapshot都依赖于它。leveldb内部按照key非递减,SequnceNumber非递增,ValueType非递增排序,这样查询时便可以找到key对应的最新值,如果type位kTypeDeletion则不存在。

(3)考虑节约空间,leveldb对key的存储进行前缀压缩后再写入sstable,每个entry中会记录key与前一个key前缀相同的字节(shared_bytes)以及自己独有的字节(unshared_bytes)。读取时,对block进行遍历,每个key根据前一个key以及shared_bytes/unshared_bytes可以构造出来。

4. 简单总结

首先讲述了leveldb的基本特性,然后简单讲解了leveldb存储结构的来源LSM的基本思想和适用场景,最后总结了几点leveldb中存储数据的特点,实际的存储结构较复杂,有内存存储结构和持久化存储结构。下一步继续探索LevelDB的存储过程。

5. 参考资料

(1)tair官网介绍

(2)leveldb官网介绍

(3)日志结构的合并树 The Log-Structured Merge-Tree

(4)从LSM-Tree、COLA-Tree谈到StackOverflow、OSQA

(5)为什么文件存储要选用B 树这样的数据结构?

Tair存储引擎之一Leveldb中数据的存储思想

时间: 2024-10-03 05:02:54

Tair存储引擎之一Leveldb中数据的存储思想的相关文章

存储引擎,MySQL中的数据类型及约束

存储引擎,MySQL中的数据类型及约束 一.存储引擎 1.不同的数据应该有不同的处理机制 2.mysql存储引擎 ? Innodb:默认的存储引擎,查询速度叫myisam慢,但是更安全 ? myissam:mysql老版本用的存储引擎 ? memory:内存引擎(数据全部存在内存中) ? blackhole:无论存什么,都立马消失(黑洞) 数据库的增删改查已经介绍完毕,今天从表的详细操作开始讲解 二.创建表的完整语法 #语法: create table 表名( 字段名1 类型[(宽度) 约束条件

射频识别技术漫谈(17)——射频卡中数据的存储形式

无论什么样的智能卡,不管是接触式的还是非接触式的,存储数据都是一个必须具备的功能.即使是只有一个5字节卡号的ID64格式的卡片也不例外,只不过卡里面的内容在出厂时就被厂家写死了,用户只能读出而不能写入或改变其内容罢了. 数据在存储介质中的存储格式往往和存储介质的容量有很大关系.容量小的存储器如E2PROM,一般以二进制的位(bit)或字节(byte)为单位:容量大的存储介质如硬盘.U盘,一般以文件的形式存储数据,文件有各种类型,文件大小只要别超过物理存储总量,几乎不受限制. 射频卡通常面向特定的

Android笔记——Android中数据的存储方式(二)

我们在实际开发中,有的时候需要储存或者备份比较复杂的数据.这些数据的特点是,内容多.结构大,比如短信备份等.我们知道SharedPreferences和Files(文本文件)储存这种数据会非常的没有效率.如果学过JavaWeb的朋友,首先可能想到的是数据库.当然了数据库是一个方案,那么是否还有其他的解决方案呢?今天我们在讲下Android笔记——Android中数据的存储方式(一) 提到的除了SharedPreferences和Files(文本文件)以外的其他几种数据储存方式:xml文件.SQL

Android笔记——Android中数据的存储方式(三)

Android系统集成了一个轻量级的数据库:SQLite,所以Android对数据库的支持很好,每个应用都可以方便的使用它.SQLite作为一个嵌入式的数据库引擎,专门适用于资源有限的设备上适量数据存取,现在的主流移动设备像Android.iPhone等都使用SQLite作为复杂数据的存储引擎,并且它是以手机内存为储存的. 那么,实际开发项目中有大量数据需要读写,并且需要面临大量用户的并发储存的情况呢.就不应该把数据存放在手机等移动设备的SQLite数据库里,移动设备的储存能力和计算能力都不足以

C/C++中数据的存储

学java时了解到不同的数据在系统中存储的位置不一样,有的存在栈里,有的存在堆里.学C/C++时没注意过这个,最近学数据结构时遇到了问题:在定义一个结构体的指针时,系统如何给它分配的空间?从而让我想去了解C/C++中数据是如何存储的.同时在学递归时就一直听到系统栈这个词,这次可以一并学习. 在CSDN中了解到C语言程序运行时内存的分类方式,有四大类或五大类两种.在其中我要关注的是堆区和栈区.堆区用于临时申请,栈区用于函数中的临时变量的存储.一般来说堆区无限大,栈区有一定大小. 在定义一个结构体的

Mysql各种存储引擎的特性以及如何选择存储引擎

几个常用存储引擎的特点 下面我们重点介绍几种常用的存储引擎并对比各个存储引擎之间的区别和推荐使用方式. 特点 Myisam BDB Memory InnoDB Archive 存储限制 没有 没有 有 64TB 没有 事务安全 支持 支持 锁机制 表锁 页锁 表锁 行锁 行锁 B树索引 支持 支持 支持 支持 哈希索引 支持 支持 全文索引 支持 集群索引 支持 数据缓存 支持 支持 索引缓存 支持 支持 支持 数据可压缩 支持 支持 空间使用 低 低 N/A 高 非常低 内存使用 低 低 中等

详细介绍Mysql各种存储引擎的特性以及如何选择存储引擎

最近业务上有要求,要实现类似oracle 的dblink linux版本 Server version: 5.6.28-0ubuntu0.14.04.1 (Ubuntu) 修改配置文件 /etc/mysql/my.cnf windows 版本 Server version: 5.6.21-log MySQL Community Server (GPL) 修改配置文件my.ini 接着将其开启,在 [mysqld] 下添加一行: federated 重启Mysql,完成. mysql> show

海量非结构化数据存储难题 ,杉岩数据对象存储完美解决

"过去几年,大数据产业更多关注的是如何处理海量.多源和异构的数据,但我们必须承认这些只是冰山一角.目前,结构化数据仅占到全部数据量的20%,其余80%都是以文件形式存在的非结构化和半结构化数据.伴随非结构化数据呈现爆发之势,对象存储市场近两年保持强劲增长,IDC预计,软件定义存储(SDS)市场未来五年复合增长率将达到28.8%." 传统IT架构渐成"过去式" 非结构化数据倒逼存储变革 今天,许多企业已经意识到,结构化数据仅仅是企业所拥有数据的一小部分.与业务信息系统

关于Cocos2d-x中数据的存储提取和类型转换

1.获得存储在UserDefault中的变量,但是获得的变量是一个String类型的值,要用atoi函数转换为整型,但是atoi函数的传递参数是一个char*类型的值,所以用_Score.c_str()把String转化为Char* auto userdefault = UserDefault::getInstance(); auto _Score = userdefault->getStringForKey("RewardScore"); _rewardScore = atoi