跳转至

RocksJava 是为了给 RocksDB 构建一个高性能,但是易用的 java 驱动的工程。

RocksJava 由 3 层构成:

  • org.rocksdb 包里面的 Java 类,构成 RocksJava API。Java 用户只会直接接触到这一层。
  • C++ 的 JNI 代码,提供 Java API 和 Rock 是 DB 之间的链接。
  • C++ 层的 RocksDB 本身,并且编译成了一个 native 库,被 JNI 层使用。

我们尽力是 RocksJava 的 API 和 RocksDB 的 c++ API 同步,但是他经常会落后。我们高度鼓励社区贡献代码。。。如果你需要某个特定的 API 在 c++ 有但是 Java 没有,提 PR 吧

在这一页,你会学习 RocksDB Java API 的基础。

开始

你可以使用我们发布的预编译好的 Maven 包,或者自己从代码构建 RocksJava。

Maven

我们在 Maven Central 发布了 RocksJava,这样你就可以依赖 jar 包而不是自己构建了 https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.rocksdb%22

我们同时发布了通用 jar 包(rocksdbjni-X.X.X.jar),包含了所有支持的平台的 native 库和 java class 文件,也发布了一些小平台的 jar 包(例如 rocksdbjni-X.X.X-linux65.jar)

在一个支持 Maven 风格依赖的构建系统中,最简单的使用 RocksJava 的方法就是增加一个 RocksJava 的依赖,例如,如果你是用 Maven:

<dependency>
  <groupId>org.rocksdb</groupId>
  <artifactId>rocksdbjni</artifactId>
  <version>5.5.1</version>
</dependency>

给 Windows 用户的备注:如果你正在 MS 的 Windows 上使用 Maven Central 编译的包,他们使用 Microsoft Visual Studio 2015 编译的,如果你没有安装“Microsoft Visual C++ 2015 Redistributable”,那么你需要从 https://www.microsoft.com/en-us/download/details.aspx?id=48145 安装他们,或者你需要自己从源码编译(rocksdb)

从源代码编译

要编译 RocksJava,你首先需要设置你的 JAVA_HOME 环境变量,指向你安装的 java SDK 目录(必须 Java 1.7+)。你必须有预编译好的 RocksDB 的 native 库,参考 INSTALL.md。一旦 JAVA_HOME 正确设置,并且你安装了需要的库,只要通过以下命令就可以构建 rocksdbjava:

$ make -j8 rocksdbjava

这会生成 rocksdbjni.jar 和 librocksdbjni.so(或者如果你是 macOS,librocksdbjni.jnilib),位置在 rocksdb 根目录的 java/target 目录。特别的,rocksdbjni.jar 包含 Java 类,定义了 rocksdb 的 Java API,而 librocksdbjni.so 包含 C++ rocksdb 库和 rocksdbjni.jar 中定义的 java 类的 native 实现。

如果希望运行单元测试:

$ make jtest

清理:

$ make jclean

样例

我们在 这里 提供了一些使用样例,你可以直接参考代码

内存管理

RocksJava 中的许多 Java 对象后面是 C++ 对象,Java 的对象拥有对应的控制权。由于 C++ 没有跟 Java 一样的自动垃圾回收的概念,我们必须在使用完毕之后,显式释放 C++ 对象使用的内存。

任何 RocksJava 中的管理了 C++ 对象的 Java 对象会继承自 org.rocksdb.AbstractNativeReference,当你用完这个对象后,这个父类被用来帮助管理和清理他手上的所有 C++ 对象。有两个机制:

AbstractNativeReference#close()

当用户使用完 RocksJava 的一个对象后,这个方法应该被用户显式调用。如果 C++ 对象被分配而没有被释放,那么他们会在第一次调用这个方法的时候被释放。

为了简化使用,这个方法重载了 java.lang.AutoCloseable#close(),这就允许他使用 ARM (Automatic Resource Management 自动资源管理) 风格的构造方法,例如 java SE 7 的 try-with-resources 声明

AbstractNativeReference#finalize()

当一个对象的所有存储引用都失效,并且对象就要进行垃圾回收的时候,这个方法被 Java 的 Finalizer 线程调用。他最后会委托给 AbstractNativeReference#close()。不过,用户不应该依赖他,而应该认为这个是一个最后的防线。

他保证了 Java 对象手头的 C++ 对象最终会被回收。但是他不能帮助 RocksJava 管理所有内存,因为 native C++ 对象的内存在 C++ 的堆上分配,然后返回给 Java 对象,这些对 Java 的 GC 机制是不可见的,所以 JVM 无法正确计算 GC 的内存压力。 使用完一个对象之后,用户总是应该显式调用 AbstractNativeReference#close()

打开数据库

一个 rocksdb 数据库有一个名字,对应于文件系统上的一个文件夹。该数据库所有的数据都会存储在这个文件夹中。下面的例子展示如何打开一个数据库,如果有需要,自动创建:

import org.rocksdb.RocksDB;
import org.rocksdb.Options;
...
  // a static method that loads the RocksDB C++ library.
  RocksDB.loadLibrary();

  // the Options class contains a set of configurable DB options
  // that determines the behaviour of the database.
  try (final Options options = new Options().setCreateIfMissing(true)) {

    // a factory method that returns a RocksDB instance
    try (final RocksDB db = RocksDB.open(options, "path/to/db")) {

        // do something
    }
  } catch (RocksDBException e) {
    // do some error handling
    ...
  }
...

TIP: 你可能注意到上面的 RocksDBException 类。这个异常类继承了 java.lang.Exception,他包含了 C++ 中的 Status 类,用于描述任何 Rocksdb 的错误

读写

数据库提供 put,remove,和 get 方法用于修改、查询数据库。例如,下面的代码吧存储在 key1 的值移动到 key2 中

byte[] key1;
byte[] key2;
// some initialization for key1 and key2

try {
  final byte[] value = db.get(key1);
  if (value != null) {  // value == null if key1 does not exist in db.
    db.put(key2, value);
  }
  db.remove(key1);
} catch (RocksDBException e) {
  // error handling
}

TIP:调用 RocksDB.put(WriteOptions opt, byte[] key, byte[] value) 和 RocksDB.get(ReadOptions opt, byte[] key),你可以通过 WriteOptions 和 ReadOptions 控制 put 和 get 的行为

TIP:使用 int RocksDB.get(byte[] key, byte[] value) 或者 int RocksDB.get(ReadOptions opt, byte[] key, byte[] value),来避免在 RocksDB.get() 中创建一个 byte 数组,这两个函数的输出会填充到预分配好的输出缓冲区 value 中,返回的 int 表示 value 的实际长度。如果返回的长度大于 value.length,意味着输出缓冲区的大小不够

打开一个带有列族的数据库

一个 rocksdb 数据库可以有多个列族。列族允许你把类似的键值对放在一起,与其他列族独立进行操作。

如果你以前使用过 Rocksdb 但是没有显式使用过列族,你可能惊奇地发现,你的所有操作都发生在一个列族,这个列族名为“default”

在 RocksJava 中使用列族的时候,一个非常重要的注意点就是,在关闭数据库的时候,需要遵从一个非常特别的顺序来析构,保证资源的正确释放。这个顺序可以通过下列代码来说明:

...
    // a static method that loads the RocksDB C++ library.
    RocksDB.loadLibrary();

    try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions().optimizeUniversalStyleCompaction()) {

      // list of column family descriptors, first entry must always be default column family
      final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
          new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts),
          new ColumnFamilyDescriptor("my-first-columnfamily".getBytes(), cfOpts)
      );

      // a list which will hold the handles for the column families once the db is opened
      final List<ColumnFamilyHandle> columnFamilyHandleList =
          new ArrayList<>();

      try (final DBOptions options = new DBOptions()
          .setCreateIfMissing(true)
          .setCreateMissingColumnFamilies(true);
           final RocksDB db = RocksDB.open(options,
               "path/to/do", cfDescriptors,
               columnFamilyHandleList)) {

        try {

          // do something

        } finally {

          // NOTE frees the column family handles before freeing the db
          for (final ColumnFamilyHandle columnFamilyHandle :
              columnFamilyHandleList) {
            columnFamilyHandle.close();
          }
        } // frees the db and the db options
      }
    } // frees the column family options
...