文件的布局:
[data_len | checksum | key-value | key-value|....]
- data_len: 占4字节, 记录所有key-value所占字节数。
- checksum: 占8字节,记录key-value部分的checksum。
key-value的数据布局:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| delete_flag | external_flag | type | key_len | key_content | value |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1bit | 1bit | 6bits | 1 byte | | |
- delete_flag :标记当前key-value是否删除。
- external_flag: 标记value部分是否写到额外的文件。
注:对于数据量比较大的value,放在主文件会影响其他key-value的访问性能,因此,单独用一个文件来保存该value, 并在主文件中记录其文件名。 - type: value类型,目前支持boolean/int/float/long/double/String/ByteArray以及自定义对象。
- key_len: 记录key的长度,key_len本身占1字节,所有支持key的最大长度为255。
- key_content: key的内容本身,utf8编码。
- value: 基础类型的value, 直接编码(little-end);
其他类型,先记录长度(用varint编码),再记录内容。
String采用UTF-8编码,ByteArray无需编码,自定义对象实现Encoder接口,分别在Encoder的encode/decode方法中序列化和反序列化。
- mmap
为了提高写入性能,FastKV默认采用mmap的方式写入。 - 降级blocking-I/O
当mmap API发生IO异常时,降级到常规的blocking-I/O,同时为了不影响当前线程,会将写入放到异步线程中执行。 - 数据完整性
如果在写入一部分的过程中发生中断(进程或系统),则文件可能会不完整。
故此,需要用一些方法确保数据的完整性。
当用mmap的方式打开时,FastKV采用double-write的方式:数据依次写入A/B两个文件,确保任何时刻总有一个文件完整的;
加载数据时,通过checksum, 标记,数据合法性检验等方法验证数据的正确性。 - 更新策略(增/删/改)
新增:写入到数据的尾部。
删除:delete_flag设置为1。
修改:如果value部分的长度和原来一样,则直接写入原来的位置;
否则,先写入key-value到数据尾部,再标记原来位置的delete_flag为1(删除),最后再更新文件的data_len和checksum。 - gc/truncate
删除key-value时会收集信息(统计删除的个数,以及所在位置,占用空间等)。
GC的触发点有两个:
1、新增key-value时剩余空间不足,且一删除的空间达到阈值,且腾出删除空间后足够写入当前key-value, 则触发GC;
2、删除key-value时,如果删除空间达到阈值,或者删除的key-value个数达到阈值,则触发GC。
GC后如果不用的空间达到设定阈值,则触发truncate(缩小文件大小)。