不啰里啰嗦了,关于leveldb、rocksdb的实现细节有兴趣的朋友自己搜搜,当然我后期会整理下leveldb的大概实现原理,我曾经花过不少心力还研究leveldb、rocksdb的设计及代码实现,受益匪浅的! 时常会回忆起leveldb的Lsm、Memtable、SStable、Manefest、log的设计。 正是因为懂了这些原理后你才更有把握在生产环境中使用rocksdb,也能分析出写快读慢的根本原因,规避和优化使用方法。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新.
我们知道Rocksdb的数据结构其实很简单,就是单纯的KV,结构上是redis string那样的。 但他又跟redis不一样,首先他是可以数据落盘的,他也没有server封装,不管是对于python、golang语言来说,只是个C的扩展API而已。
上次有说过如何在rocksdb里面设计丰富多样的数据结构,方向上尽量跟redis的命令集靠拢的。 到现在为止,我已经在不少线上业务场景中用过rocksdb了,比如爬虫页面去重、缓存、监控历史数据。 准确的说,不全是rocksdb,也有用过它的前身leveldb。 我当初参考了ardb和ssdb的一些数据结构设计,去掉server层直接在python封装了一个支持hash、set、list的库包。 你可能会问了,我们为什么不直接选用成熟的基于rocksdb的网络服务封装,而是自己造轮子。 理由很简单,不管是以前的ssdb,现在360 pika都有针对rocksdb做封装,但对于我这种只有本机访问的场景来说,无意是浪费的,另外网络io也是多少是有消耗的。
再者说,rocksdb封装结构其实很简单的,但是你要考虑搞明白下面几个问题?
如何保持zset做到高效查询?
空间换时间,尽量热区访问。
如何保证多条命令的原子性?
rocksdb本身是可以保证数据的一致性的,rocksdb是带有 Write Ahead Log预写日志的。 对于一组命令来说,我们可以使用 WriteBatch来包装命令集,rocksdb还是会采用Wal方案避免因服务崩溃丢失数据。
如何控制并发?
rocksdb通过fcntl来控制进程锁,运行初始化时,只有一个进程可以拿到fcntl文件锁。 多个线程倒是可以并发访问,但是并发写写操作会被fifo有序排队执行。 简单说,读读不会阻塞,写写之间会有阻塞的。 这个跟mysql的事务是一回事的。
简单说下,rocksdb的查询过程 ?
每次触发边界memtable会变成Immutable Memtable,接着被写入到最上层的sstable 文件里!每个sstable 都是排序好的,在manifest 里面会记录每个sstable 的key max min区间。 我们要明确一点,多个SStable会存在多个数据,冗余数据,过期数据的。当你去试图读取数据时,那么查询的顺序是从上往下,直到扫完所有的数据块。 为了提高效率,不是每个SSTable都会扫描的,最少每个sstable做了bloom 过滤器,你查询的key也会跟Manifest记录的key区间做对比。
zset作为有序集合,我们最少要实现他的几个命令,zadd添加、scard查询个数、zscore查询分值、zrangebyscore范围查询。当然作为zset的鼻祖,redis有更丰富的命令。 推荐大家把python rocksdb的文档看完,有很多概念性的介绍和实例,比如prefix,iterator,snapshot,merge等。
首先放一个 zset的结构实例,我们可以得出通过 zset_k_xxx_c 得到zcard数据,zset_k_xxx_v_member1拿到zscore的score分值,zset_k_xxx_c_score 拿到 member。
# xiaorui.cc # coding:utf-8 ''' cmd key filed value zadd test 100 member1 ''' import rocksdb db = rocksdb.DB("test.db", rocksdb.Options(create_if_missing=True)) batch = rocksdb.WriteBatch() batch.put(b"zset_k_test_c_100", "member1") batch.pub(b"zset_k_test_v_member1", "100") db.write(batch) db.put(b"zset_k_test_c", "1")
没写完。。。。 下面开始写 python rocksdb的具体方法。。。