Skip to content

KeyValueStore 键值存储

RockyLOMO edited this page Jul 14, 2021 · 5 revisions

设计思路

  • 思路跟主流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();
    }