RocksDB 入门

1. 基本介绍

RocksDB是Facebook开源的一个k-v存储组件,支持点查找和范围扫描, 并提供ACID保证。实现架构主要参考的是开源的leveldb和hbase的设计思想。目前RocksDB主要用于本地存储引擎。

1.1 基础架构图

RocksDB 入门_第1张图片

Rocksdb中引入了ColumnFamily(列族, CF)的概念,所谓列族也就是一系列kv组成的数据集。所有的读写操作都需要先指定列族。写操作先写WAL,再写memtable,memtable达到一定阈值后切换为Immutable Memtable,只能读不能写。后台Flush线程负责按照时间顺序将Immu Memtable刷盘,生成level0层的有序文件(SST)。后台合并线程负责将上层的SST合并生成下层的SST。Manifest负责记录系统某个时刻SST文件的视图,Current文件记录当前最新的Manifest文件名。 每个ColumnFamily有自己的Memtable, SST文件,所有ColumnFamily共享WAL、Current、Manifest文件。

1.2 架构分析

整个系统的设计思路很好理解,这种设计的优势很明显,主要有以下几点: 1.所有的刷盘操作都采用append方式,这种方式对磁盘和SSD是相当有诱惑力的;

2.写操作写完WAL和Memtable就立即返回,写效率非常高。

3.由于最终的数据是存储在离散的SST中,SST文件的大小可以根据kv的大小自由配置, 因此很适合做变长存储。

但是这种设计也带来了很多其他的问题:

1.为了支持批量和事务以及上电恢复操作,WAL是多个CF共享的,导致了WAL的单线程写模式,不能充分发挥高速设备的性能优势(这是相对介质讲,相对B树等其他结构还是有优 势)。

2.读写操作都需要对Memtable进行互斥访问,在多线程并发写及读写混合的场景下容易形成瓶颈。

3.由于Level0层的文件是按照时间顺序刷盘的,而不是根据key的范围做划分,所以导致各个文件之间范围有重叠,再加上文件自上向下的合并,读的时候有可能需要查找level0层的多个文件及其他层的文件,这也造成了很大的读放大。尤其是当纯随机写入后,读几乎是要查询level0层的所有文件,导致了读操作的低效。

4.针对第三点问题,Rocksdb中依据level0层文件的个数来做前台写流控及后台合并触发,以此来平衡读写的性能。这又导致了性能抖动及不能发挥高速介质性能的问题。

5.合并流程难以控制,容易造成性能抖动及写放大。尤其是写放大问题,在笔者的使用过程中实际测试的写放大经常达到二十倍左右。这是不可接受的,当前我们也没有找到合适的解决办法,只是暂时采用大value分离存储的方式来将写放大尽量控制在小数据。

1.3 适用场景

1.对写性能要求很高,同时有较大内存来缓存SST块以提供快速读的场景;

2.SSD等对写放大比较敏感以及磁盘等对随机写比较敏感的场景;

3.需要变长kv存储的场景;

4.小规模元数据的存取;

1.4 不适用场景

1.大value的场景,需要做kv分离;

2.大规模数据的存取

2. 安装过程

安装环境centos 7.4。具体安装流程参考说明书,说得很清楚。具体的安装流程如下:

  • 安装gcc
yum install gcc
  • 源码安装gflags
$ tar xzf gflags-$version-source.tar.gz
$ cd gflags-$version
$ mkdir build && cd build
$ ccmake ..

  - Press 'c' to configure the build system and 'e' to ignore warnings.
  - Set CMAKE_INSTALL_PREFIX and other CMake variables and options.
  - Continue pressing 'c' until the option 'g' is available.
  - Then press 'g' to generate the configuration files for GNU Make.

$ make
$ make test    (optional)
$ make install (optional)
  • 安装snappy/zlib/bzip2等库
sudo yum install snappy snappy-devel -y
sudo yum install zlib zlib-devel -y
sudo yum install bzip2 bzip2-devel -y
sudo yum install lz4-devel -y
sudo yum install libasan -y
  • 安装zstandard
wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
tar zxvf zstd-1.1.3.tar.gz
cd zstd-1.1.3
make && sudo make install
  • 最后安装rocksdb

这里有很多种编译方式,可以是静态库也可以是动态库。推荐使用静态库。

静态库:make static_lib, 生成:librocksdb.a
动态库:make shared_lib, 生成:librocksdb.so
debug模式:make all, 生成所有的库以及测试用例。

3. 简单用例

主要参考:https://github.com/facebook/rocksdb/blob/master/examples/simple_example.cc

// Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

#include 
#include 

#include "rocksdb/db.h"
#include "rocksdb/slice.h"
#include "rocksdb/options.h"

using namespace rocksdb;

std::string kDBPath = "/tmp/rocksdb_simple_example";

int main() {
  DB* db;
  Options options;
  // Optimize RocksDB. This is the easiest way to get RocksDB to perform well
  options.IncreaseParallelism();
  options.OptimizeLevelStyleCompaction();
  // create the DB if it's not already present
  options.create_if_missing = true;

  // open DB
  Status s = DB::Open(options, kDBPath, &db);
  assert(s.ok());

  // Put key-value
  s = db->Put(WriteOptions(), "key1", "value");
  assert(s.ok());
  std::string value;
  // get value
  s = db->Get(ReadOptions(), "key1", &value);
  assert(s.ok());
  assert(value == "value");

  // atomically apply a set of updates
  {
    WriteBatch batch;
    batch.Delete("key1");
    batch.Put("key2", value);
    s = db->Write(WriteOptions(), &batch);
  }

  s = db->Get(ReadOptions(), "key1", &value);
  assert(s.IsNotFound());

  db->Get(ReadOptions(), "key2", &value);
  assert(value == "value");

  {
    PinnableSlice pinnable_val;
    db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
    assert(pinnable_val == "value");
  }

  {
    std::string string_val;
    // If it cannot pin the value, it copies the value to its internal buffer.
    // The intenral buffer could be set during construction.
    PinnableSlice pinnable_val(&string_val);
    db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
    assert(pinnable_val == "value");
    // If the value is not pinned, the internal buffer must have the value.
    assert(pinnable_val.IsPinned() || string_val == "value");
  }

  PinnableSlice pinnable_val;
  db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val);
  assert(s.IsNotFound());
  // Reset PinnableSlice after each use and before each reuse
  pinnable_val.Reset();
  db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
  assert(pinnable_val == "value");
  pinnable_val.Reset();
  // The Slice pointed by pinnable_val is not valid after this point

  delete db;

  return 0;
}

makefile

simple_example: simple_example.o
        cc -o simple_example simple_example.o ../librocksdb.a -I../include -O2 -std=c++11
simple_example.o: simple_example.cc
         cc -c simple_example.cc ../librocksdb.a -I../include -O2 -std=c++11

4. 常用api合集

api 用途
Open 初始化db(存储文件路径)
Put 添加k-v操作
Get 获取k-v操作
Delete 删除k操作
WriteBatch 批量操作
PinnableSlice 避免内存拷贝
write_options.sync = true 异步操作
  • Iteration
rocksdb::Iterator* it = db->NewIterator(rocksdb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
    cout << it->key().ToString() << ": " << it->value().ToString() << endl;
}
assert(it->status().ok()); // Check for any errors found during the scan
delete it;
  • Snapshots
  rocksdb::ReadOptions options;
  options.snapshot = db->GetSnapshot();
  ... apply some updates to db ...
  rocksdb::Iterator* iter = db->NewIterator(options);
  ... read using iter to view the state when the snapshot was created ...
  delete iter;
  db->ReleaseSnapshot(options.snapshot);
  • Transactions

你可能感兴趣的:(微服务)