KeyValueStore 键值存储 - RockyLOMO/rxlib GitHub Wiki
设计思路
- 思路跟主流MQ差不多,directBuffer + mmap读写,利用系统PageCache缓存read-ahead和write-behind,分log和index文件。
- log文件默认按1G容量增长,保证每1G容量在硬盘是顺序连续的空间,单个mmap追加顺序IO写入。多mmap读取(默认1个),如果是机械硬盘多个线程读写会造成随机IO,其次机械硬盘不断移动磁头(消耗约 5ms)至各文件块影响性能。如果是SSD可以根据随机IO读性能调整mmap reader数量。
- index文件按Int.MaxValue和每个index文件的最大容量计算分片数,如果分片数太多访问大量小文件需要执行大量的寻址操作。默认每个index按32M增长。每个key存储12字节,由于hash算法随机性,未作BST、B+T处理,也因此index是顺序读查找,随机IO写,为提升查找性能增加内存缓存。
设计目的
单机轻量级,不引其它jar。尽可能低内存消耗。
@Test
public void kvDb2() {
KeyValueStoreConfig conf = new KeyValueStoreConfig(KvPath);
conf.setLogGrowSize(1024 * 1024 * 4);
conf.setIndexGrowSize(1024 * 1024);
KeyValueStore<Integer, String> kv = new KeyValueStore<>(conf, Serializer.DEFAULT);
int loopCount = 10000;
TestUtil.invokeAsync("kvdb", i -> {
// int k = ThreadLocalRandom.current().nextInt(0, loopCount);
int k = i;
String val = kv.get(k);
if (val == null) {
kv.put(k, val = String.valueOf(k));
}
String newGet = kv.get(k);
if (!val.equals(newGet)) {
log.error("check: {} == {}", val, newGet);
}
assert val.equals(newGet);
}, loopCount);
kv.close();
}
@Test
public void kvDb() {
KeyValueStoreConfig conf = new KeyValueStoreConfig(KvPath);
conf.setLogGrowSize(1024 * 1024 * 4);
conf.setIndexGrowSize(1024 * 1024);
KeyValueStore<Integer, String> kv = new KeyValueStore<>(conf, Serializer.DEFAULT);
kv.clear();
int loopCount = 100, removeK = 99;
TestUtil.invoke("put", i -> {
String val = kv.get(i);
if (val == null) {
val = DateTime.now().toString();
if (i == removeK) {
System.out.println(1);
}
kv.put(i, val);
String newGet = kv.get(i);
while (newGet == null) {
newGet = kv.get(i);
sleep(1000);
}
log.info("put new {} {} -> {}", i, val, newGet);
assert val.equals(newGet);
if (i != removeK) {
assert kv.size() == i + 1;
} else {
assert kv.size() == 100;
System.out.println("x:" + kv.size());
}
}
val += "|";
kv.put(i, val);
String newGet = kv.get(i);
while (newGet == null) {
newGet = kv.get(i);
sleep(1000);
System.out.println("x:" + newGet);
}
log.info("put {} {} -> {}", i, val, newGet);
assert val.equals(newGet);
}, loopCount);
log.info("remove {} {}", removeK, kv.remove(removeK));
assert kv.size() == removeK;
int mk = 1001, mk2 = 1002;
kv.put(mk2, "a");
log.info("remove {} {}", mk2, kv.remove(mk2, "a"));
// kv.put(mk, null);
// String s = kv.get(mk);
// assert s == null;
kv.close();
}