文档章节

高性能nosql ledisdb设计与实现(1)

siddontang
 siddontang
发布于 2014/06/04 17:10
字数 2216
阅读 243
收藏 7
点赞 0
评论 0

ledisdb是一个用go实现的基于leveldb的高性能nosql数据库,它提供多种数据结构的支持,网络交互协议参考redis,你可以很方便的将其作为redis的替代品,用来存储大于内存容量的数据(当然你的硬盘得足够大!)。

同时ledisdb也提供了丰富的api,你可以在你的go项目中方便嵌入,作为你app的主要数据存储方案。

与redis的区别

ledisdb提供了类似redis的几种数据结构,包括kv,hash,list以及zset,(set因为我们用的太少现在不予支持,后续可以考虑加入),但是因为其基于leveldb,考虑到操作硬盘的时间消耗铁定大于内存,所以在一些接口上面会跟redis不同。

最大的不同在于ledisdb对于在redis里面可以操作不同数据类型的命令,譬如(del,expire),是只支持kv操作的。也就是说,对于del命令,ledisdb只支持删除kv,如果你需要删除一个hash,你得使用ledisdb额外提供的hclear命令。

为什么要这么设计,主要是性能考量。leveldb是一个高效的kv数据库,只支持kv操作,所以为了模拟redis中高级的数据结构,我们需要在存储kv数据的时候在key前面加入相关数据结构flag。

譬如对于kv结构的key来说,我们按照如下方式生成leveldb的key:

func (db *DB) encodeKVKey(key []byte) []byte {
    ek := make([]byte, len(key)+2)
    ek[0] = db.index
    ek[1] = kvType
    copy(ek[2:], key)
    return ek
}

kvType就是kv的flag,至于第一个字节的index,后面我们在讨论。

如果我们需要支持del删除任意类型,可能的一个做法就是在另一个地方存储该key对应的实际类型,然后del的时候根据查出来的类型再去做相应处理。这不光损失了效率,也提高了复杂度。

另外,在使用ledisdb的时候还需要明确知道,它只是提供了一些类似redis接口,并不是redis,如果想用redis的全部功能,这个就有点无能为力了。

db select

redis支持select的操作,你可以根据你的业务选择不同的db进行数据的存放。本来ledisdb只打算支持一个db,但是经过再三考虑,我们决定也实现select的功能。

因为在实际场景中,我们不可能使用太多的db,所以select db的index默认范围就是[0-15],也就是我们最多只支持16个db。redis默认也是16个,但是你可以配置更多。不过我们觉得16个完全够用了,到现在为止,我们的业务也仅仅使用了3个db。

要实现多个db,我们开始定了两种方案:

  • 一个db使用一个leveldb,也就是最多ledisdb将打开16个leveldb实例。

  • 只使用一个leveldb,每个key的第一个字节用来标示该db的索引。

这两种方案我们也不知道如何取舍,最后决定采用使用同一个leveldb的方式。可能我们觉得一个leveldb可以更好的进行优化处理吧。

所以我们任何leveldb key的生成第一个字节都是存放的该db的index信息。

KV

kv是最常用的数据结构,因为leveldb本来就是一个kv数据库,所以对于kv类型我们可以很简单的处理。额外的工作就是生成leveldb对应的key,也就是前面提到的encodeKVKey的实现。

Hash

hash可以算是一种两级kv,首先通过key找到一个hash对象,然后再通过field找到或者设置相应的值。

在ledisdb里面,我们需要将key跟field关联成一个key,用来存放或者获取对应的值,也就是key:field这种格式。

这样我们就将两级的kv获取转换成了一次kv操作。

另外,对于hash来说,(后面的list以及zset也一样),我们需要快速的知道它的size,所以我们需要在leveldb里面用另一个key来实时的记录该hash的size。

hash还必须提供keys,values等遍历操作,因为leveldb里面的key默认是按照内存字节升序进行排列的,所以我们只需要找到该hash在leveldb里面的最小key以及最大key,就可以轻松的遍历出来。

在前面我们看到,我们采用的是key:field的方式来存入leveldb的,那么对于该hash来说,它的最小key就是"key:",而最大key则是"key;",所以该hash的field一定在"(key:, key;)"这个区间范围。至于为什么是“;”,因为它比":"大1。所以"key:field"一定小于"key;"。后续zset的遍历也采用的是该种方式,就不在说明了。

List

list只支持从两端push,pop数据,而不支持中间的insert,这样主要是为了简单。我们使用key:sequence的方式来存放list实际的值。

sequence是一个int整形,相关常量定义如下:

listMinSeq     int32 = 1000
listMaxSeq     int32 = 1<<31 - 1000
listInitialSeq int32 = listMinSeq + (listMaxSeq-listMinSeq)/2

也就是说,一个list最多存放1<<31 - 2000条数据,至于为啥是1000,我说随便定得你信不?

对于一个list来说,我们会记录head seq以及tail seq,用来获取当前list开头和结尾的数据。

当第一次push一个list的时候,我们将head seq以及tail seq都设置为listInitialSeq。

当lpush一个value的时候,我们会获取当前的head seq,然后将其减1,新得到的head seq存放对应的value。而对于rpush,则是tail seq + 1。

当lpop的时候,我们会获取当前的head seq,然后将其加1,同时删除以前head seq对应的值。而对于rpop,则是tail seq - 1。

我们在list里面一个meta key来存放该list对应的head seq,tail seq以及size信息。

ZSet

zset可以算是最为复杂的,我们需要使用三套key来实现。

  • 需要用一个key来存储zset的size

  • 需要用一个key:member来存储对应的score

  • 需要用一个key:score:member来实现按照score的排序

这里重点说一下score,在redis里面,score是一个double类型的,但是我们决定在ledisdb里面只使用int64类型,原因一是double还是有浮点精度问题,在不同机器上面可能会有误差(没准是我想多了),另一个则是我不确定double的8字节memcmp是不是也跟实际比较结果一样(没准也是我想多了),其实更可能的原因在于我们觉得int64就够用了,实际上我们项目也只使用了int的score。

因为score是int64的,我们需要将其转成大端序存储(好吧,我假设大家都是小端序的机器),这样通过memcmp比较才会有正确的结果。同时int64有正负的区别,负数最高位为1,所以如果只是单纯的进行binary比较,那么负数一定比正数大,这个我们通过在构建key的时候负数前面加"<",而正数(包括0)加"="来解决。所以我们score这套key的格式就是这样:

key<score:member //<0
key=score:member //>=0

对于zset的range处理,其实就是确定某一个区间之后通过leveldb iterator进行遍历获取,这里我们需要明确知道的事情是leveldb的iterator正向遍历的速度和逆向遍历的速度完全不在一个数量级上面,正向遍历快太多了,所以最好别去使用zset里面带有rev前缀的函数。

总结

总的来说,用leveldb来实现redis那些高级的数据结构还算是比较简单的,同时根据我们的压力测试,发现性能还能接受,除了zset的rev相关函数,其余的都能够跟redis保持在同一个数量级上面,具体可以参考ledisdb里面的性能测试报告以及运行ledis-benchmark自己测试。

后续ledisdb还会持续进行性能优化,同时提供expire以及replication功能的支持,预计6月份我们就会实现。

ledisdb的代码在这里https://github.com/siddontang/ledisdb,希望感兴趣的童鞋共同参与。


© 著作权归作者所有

共有 人打赏支持
siddontang
粉丝 59
博文 41
码字总数 49108
作品 4
珠海
一个参考ssdb,使用go类似的实现redis高性能nosql:ledisdb

起因 ledisdb是一个參考ssdb。採用go实现,底层基于leveldb,相似redis的高性能nosql数据库,提供了kv,list,hash以及zset数据结构的支持。 我们如今的应用极大的依赖redis。但随着我们用户...

mickelfeng ⋅ 06/08 ⋅ 0

告诉你 SQL 数据库与 NoSQL 数据库的区别

简单来说 SQL 数据库和 NoSQL 数据库有着共同的目标:存储数据,但存储的方式不同 一. 表 SQL中的表结构具有严格的数据模式约束: 存储数据很难出错。 NoSQL存储数据更加灵活自由:可能导致数...

Howie_Y ⋅ 06/05 ⋅ 0

企业竞争加剧,数据治理和NoSQL大放异彩

  【IT168 评论】数字化转型是现代企业的大趋势,开发独特的应用程序已经成为推动业务向前发展的核心了。Couchbase 数字化创新调查针对美国、英国、法国和德国450家数字化转型企业进行了调...

it168网站 ⋅ 05/31 ⋅ 0

当规模到亿级,MySQL是一个更好的NoSQL!

MySQL是一个更好的NoSQL数据库。当考虑到NoSQL的使用案例,比如对Key/Value键值存储来讲,MySQL在性能、易用性和稳定性方面更有意义。MySQL毕竟是一款成熟稳定的产品,在互联网上有大量的在线...

张凡 ⋅ 2016/06/23 ⋅ 0

Oracle MySQL Or NoSQL?(转载)

转载: 作者:Sky.Jian (简朝阳) 链接:http://isky000.com/database/oracle-mysql-or-nosql-2 一些英文缩写的含义: Nosql: not only sql OLTP: 联机事务处理 OLAP:联机分析处理 去IOE:摆...

付磊-起扬 ⋅ 2015/09/12 ⋅ 0

大数据和云计算技术周报(第40期):NoSQL特辑

写在第40期 到底什么是NoSQL?公众号一系列前菜,大家可以看看: NoSQL 还是 SQL ?这一篇讲清楚 新数仓系列:Hbase周边生态梳理(1) 新数仓系列:Hbase国内开发者生存现状(2) 新数仓系列:...

znzqhb07nr ⋅ 04/18 ⋅ 0

NoSQL数据库介绍、memcached安装

NoSQL数据库 什么是NoSQL数据库? 非关系型数据库就是NoSQL,关系型数据库代表MySQL; 关系型数据库,是需要吧数据存储到库、表、行、字段中,查询需要根据条件一行行的匹配,需要从磁盘中检...

若白衣 ⋅ 05/21 ⋅ 0

GridDB 社区版本 4.0 发布,高度可扩展 NoSQL 数据库

GridDB 社区版本 4.0 发布了,GridDB 是一款高度可扩展的 NoSQL 数据库,具有高可靠性和高性能等特性,非常适用于物联网和大数据领域。 此版本带来了实用和便利的功能,包括: 容器/集群名称...

雨田桑 ⋅ 06/01 ⋅ 0

21.2 memcached介绍;21.2 memcached介绍;21.3 安装memcached

21.2 memcached介绍 什么是NoSQL? 1. 非关系型数据库就是NoSQL,关系型数据库代表MySQL 2. 对于关系型数据库来说,是需要把数据存储到库、表、行、字段里,查询的时候根据条件一行一行地去匹...

主内安详 ⋅ 05/22 ⋅ 0

面向物联网和大数据的 NoSQL 数据库 - GridDB

GridDB 是一款高度可扩展的 NoSQL 数据库,非常适用于物联网和大数据领域,还具有高可靠性和高性能这些特性。 GridDB 主要特性 专门针对物联网(IoT)的优化:GridDB 的键值容器(Key Containe...

匿名 ⋅ 05/31 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

内存障碍: 软件黑客的硬件视图

此文为笔者近日有幸看到的一则关于计算机底层内存障碍的学术论文,并翻译(机译)而来[自认为翻译的还行],若读者想要英文原版的论文话,给我留言,我发给你。 内存障碍: 软件黑客的硬件视图...

Romane ⋅ 23分钟前 ⋅ 0

SpringCloud 微服务 (七) 服务通信 Feign

壹 继续第(六)篇RestTemplate篇 做到现在,本机上已经有注册中心: eureka, 服务:client、order、product 继续在order中实现通信向product服务,使用Feign方式 下面记录学习和遇到的问题 贰 or...

___大侠 ⋅ 40分钟前 ⋅ 0

001. 深入JVM学习—Java运行流程

1. Java运行流程图 2. Java运行时数据区 3. Java虚拟机栈 栈内存是线程私有的,其生命周期和线程相同; 虚拟机栈描述的是Java方法执行的内存模型:执行一个方法时会产生一个栈帧随后将其保存...

影狼 ⋅ 55分钟前 ⋅ 0

gitee、github上issue标签方案

目录 [TOC] issue生命周期 st=>start: 开始e=>end: 结束op0=>operation: 新建issueop1=>operation: 评审issueop2=>operation: 任务负责人执行任务cond1=>condition: 是否通过?op3=>o......

lovewinner ⋅ 今天 ⋅ 0

浅谈mysql的索引设计原则以及常见索引的区别

索引定义:是一个单独的,存储在磁盘上的数据库结构,其包含着对数据表里所有记录的引用指针. 数据库索引的设计原则: 为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索...

屌丝男神 ⋅ 今天 ⋅ 0

String,StringBuilder,StringBuffer三者的区别

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。 首先说运行速度,或者说是, 1.执行速度 在这方面运行速度快慢为:StringBuilder(线程不安全,可变) > StringBuffer...

时刻在奔跑 ⋅ 今天 ⋅ 0

java以太坊开发 - web3j使用钱包进行转账

首先载入钱包,然后利用账户凭证操作受控交易Transfer进行转账: Web3j web3 = Web3j.build(new HttpService()); // defaults to http://localhost:8545/Credentials credentials = Wallet......

以太坊教程 ⋅ 今天 ⋅ 0

Oracle全文检索配置与实践

Oracle全文检索配置与实践

微小宝 ⋅ 今天 ⋅ 0

mysql的分区和分表

1,什么是mysql分表,分区 什么是分表,从表面意思上看呢,就是把一张表分成N多个小表,具体请看mysql分表的3种方法 什么是分区,分区呢就是把一张表的数据分成N多个区块,这些区块可以在同一...

梦梦阁 ⋅ 今天 ⋅ 0

exception.ZuulException: Forwarding error

错误日志 com.netflix.zuul.exception.ZuulException: Forwarding error Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: xxx timed-out and no fallback available. Ca......

jack_peng ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部