欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 宝鼎售后问题提交 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: keakon的涂鸦馆

引言

一直以来在我的见识中,key/value 数据库就三种选项:

  • 内存可存放:Redis
  • 单机磁盘可存放:RocksDB
  • 高出 TB 级:Cassandra、HBase……
  • 然而在实际项目中利用 RocksDB 时,才发明白一堆问题,折腾许久才搞定。

    利用 RocksDB 的配景

    先先容下我利用 RocksDB 的配景。

    这个项目有许多 key/value 数据(约 100 GB)需要利用,利用时根基是只读的,偶然更新时才会批量导入,且可以忍受短暂的停机导入。我一想 TiKV 和 Pika 等许多 key/value 数据库都选用了 RocksDB,应该是较量靠谱的,于是就选它了。

    接着就发明这对象的编译依赖有点多。我的项目是用 Go 写的,劳务派遣管理系统,而这个玩意需要安装一堆 C 库,而且不能交错编译到其他平台。不单不能在 Mac OS X 上编译 Linux 的版本,甚至 Ubuntu 16.04 上编译的都不能在 CentOS 7 上运行(后者的 GCC 版本较低,动态链接库版本也低)。因为懒得降级 GCC,最后我选择用 docker 来编译了。

    并且我发明数据量小时还挺快,可是数据量大了就越来越慢。平时插入 10 万条数据(约 50 MB)或许 0.2 ~ 0.5 秒,但一段时间后就会一连碰着需要几秒甚至几十秒的环境。

    于是我又开始寻找其他的替代品,诸如 LMDB、BadgerDB 和 TerarkDB 等。在这个进程中我开始相识到它们实现的道理,也算有不少收获。

    传统的干系型数据库大多是利用 B+ 树,这种数据布局可以很快地举办顺序读写,也能以 O(log(N)) 的时间巨大度来举办随机读,但不适合随机写(会导致 B+ 树从头调解均衡,造成写放大)。

    而 LevelDB 引入了 LSM 树,就是为了办理 B+ 树随机写机能低的问题,它把随机写以跳跃表的形式保存在内存中(memtable),积聚到足够的巨细就不再改写它了,并将其写入到磁盘(L0 SST file),这样就只有顺序写了。因为 memtable 和 L0 中的数据大概会反复,并且 key 很分手,所以搜索时需要遍历它们。假如没找到的话,还要向基层查找(关于层数下文会表明),不外 L1 之后的 SST file 都是有序分段的,因此可以用二分查找来找到 key 地址的数据文件,再在这个文件顶用二分查找来找到这个 key。为了低落搜索的价钱,RocksDB 还利用了 Bloom filter 来判定数据是否在某个文件中(有误判,但能显著淘汰需要搜索的文件数)。由此可见,LSM 树对写入做了优化,但低落了随机读的机能,顺序读则和 B+ 树差不多。

    另外,L0 的数据大概会有许多逾期数据(被更新或删除过),因此需要在到达阈值后举办归并(compact),昆山软件开发,去掉这些反复和无效的数据,并写入 L1。而 L1 也大概会有逾期的数据,也需要被归并写入 L2……这就相当于数据要多次写入差异的文件中,也就造成了写放大。而归并不重叠的数据文件是很快的,因此顺序写照旧要比随机写快,但归并可以在其他线程中执行,在不会一连随机写入大量数据的环境下,根基能保持 O(1) 的写入。

    事实上,我碰着的 RocksDB 变慢的问题就是 compact 引起的,默认设置下它只利用一个线程来 compact,假如 compact 跟不上写入的速度,RocksDB 就会低落写入速度,甚至遏制写入。思量到我的电脑有 4 个核,于是我把线程数改成了 4:

    bbto := gorocksdb.NewDefaultBlockBasedTableOptions()
    opts := gorocksdb.NewDefaultOptions()
    opts.SetBlockBasedTableFactory(bbto)
    opts.SetCreateIfMissing(true)
    opts.OptimizeLevelStyleCompaction(1 << 30)
    opts.IncreaseParallelism(4)
    opts.SetMaxBackgroundCompactions(4)
    env := gorocksdb.NewDefaultEnv()
    env.SetBackgroundThreads(4)
    opts.SetEnv(env)
    db, err := gorocksdb.OpenDb(opts, "db")

    修改后就发明插入时间根基不变在 0.2 ~ 0.5 秒之间了,CPU 占用率高出了 400%,磁盘写入速度高出了 800 MB/s……
    别的,RocksDB 还提供了 db.GetProperty("rocksdb.stats") 这个要领来查察状态,需要存眷的数据主要有 W-Amp(写入放大倍数)和 Stalls(因为 compact 而降速)。

    RocksDB 有 3 种 compact 的方法:leveled、universal 和 FIFO。

    Leveled

    Leveled 是从 LevelDB 担任过来的传统形式,也就是当一层的数据文件高出该层的阈值时,就往它的基层来 compact。L0 之间因为大概有反复的数据,因此需要全归并后写入 L1。而 L1 之后的数据文件不会有反复的 key,因此在 key 范畴不重合的环境下,可以并发地向下归并。RocksDB 默认有 L0 ~ L6 这 7 层,L1 容量是 256 MB(发起把 L0 和 L1 巨细设为一样,可以减小写入放大),之后每层都是上一层容量的 10 倍。很显然,层数越高就暗示写入放大倍数越高。

    Universal