Java非托管堆存储数据
当前随着Nosql的兴起,已经有大量的key-value存储或者缓存的系统,基本没必要重复造轮子,但是对于引入例如redis/memcached这样的系统,则必须使用其协议,不能重用现有的一套东西,所以想着开发一套Java的缓存系统。
一般的方案是通过HashMap这样的结构保存数据, 但是这样一般有两个问题,一是太浪费内存,java对象占用的空间太大。二是随着内存使用的增大,对于GC而已负担太大。所以我实现的方案是将大部分数据保存在非托管堆上,部分索引数据保存在托管堆上。
主要的Feature如下:
- 将数据保存在非托管堆上,减少gc的消耗
- 自动进行碎片整理
- 使用读写锁保证线程安全
主要的实现细节如下:
数据储存
记录格式如下::
+--------------+------------------+
| name | length |
+--------------+------------------+
| magic number | 1byte |
| next | 8byte |
| key size | varint |
| value size | varint |
| key | <data> |
| value | <data> |
| padding | <not used space> |
+--------------+------------------+
- magic number标识了记录是否被释放以及进行越界检查。
- next为long类型,保存了冲突链表。
- key size以及value size为varint编码,节省空间。
- padding主要是由于在找到合适的空闲块的时候,如果有多余的空间没继续进行split。
记录存放在在NonHeap分配的数据段,每个数据段为固定大小,可以在初始化的时候指定。
索引记录
在DB被初始化的时候可以指定hashpower,其用于定于索引buckets的大小,根据预计的缓存量进行分配,如果太小,将导致冲突链增大,将极大的影响性能。
索引buckets为long类型的数组,buckets里的值保存记录在NonHeap数据区的偏移以及整个大小。前2个字节为大小,后2个字节为数据段的index, 最后4个字节为在数据段的偏移。
碎片管理
记录被删除后,将magic number置为删除状态,并按照其size有序的保存在分配在托管堆的TreeSet里。在下次分配记录空间的时候,先通过bestfit的方式查找到最适合的大小,复用原有的空间。
在碎片率到达一定程度后,将对数据段进行碎片整理,将多个碎片合并为一个碎片。
PS: 可以使用类似memcached slab分配器的方式分配内存,这样出现碎片的机率比较小。
项目地址在https://github.com/believe3301/NonHeapDB
-EOF-