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 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++对象。有两个机制:
当用户使用完RocksJava的一个对象后,这个方法应该被用户显式调用。如果C++对象被分配而没有被释放,那么他们会在第一次调用这个方法的时候被释放。
为了简化使用,这个方法重载了java.lang.AutoCloseable#close(),这就允许他使用ARM (Automatic Resource Management自动资源管理)风格的构造方法,例如java SE 7的 try-with-resources声明
当一个对象的所有存储引用都失效,并且对象就要进行垃圾回收的时候,这个方法被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
...