当使用SeaweedFS系统储存1亿条索引数据时就会出现内存相对较大的问题,自己产生索引的key值时。
索引文件形式
索引文件中每个索引形式如下:
Key|Offset|Size
PS:Key的类型是uint64,Offset的类型是uint32,Size的类型是uint32。即索引大小为16个字节。
所以当索引数量等于1亿时,如果将索引文件直接加载到内存中,其大小约为1.5G左右。由于直接加载到内存的话,不利于索引的查找,因此必须要对索引内存进行管理。SeaweedFS提供了4中索引内存管理方式,如下:
- InMemory
- LevelDb
- BoltDb
- Btree
下面将分别对这四种内存管理方式进行讨论:
InMemory
其内存管理方式是由一系列的紧缩节组成,主要的数据结构如下:
type CompactMap struct { // InMemory使用的内存结构 list []*CompactSection // 多个节组成 } type CompactSection struct { // 每个节的构成 sync.RWMutex // 节对应的读写锁 values []NeedleValue // 节的存储空间 overflow map[Key]NeedleValue // 当values存满或者key小于values最后一个key值时存放在这里 start Key // 本节中最小的那个key end Key // 本节中最大的那个key counter int // values中key值个数 }
InMemory时,索引插入操作如下:
- 首先查找是否存在能够包含当前key值的节,找到,则转2操作;未找到,则转3操作。
- 找到对应的节,则按key值大小顺序插入节内,如果节末key大于将要插入数据的key值或者节的大小超过bitch(默认节大小:100000),则转4操作;否则转5操作。
- 申请新节,并且节与节之间按照start字段的大小排序,然后转2操作继续进行插入节数据操作。
- 将数据存放在节中的overflow结构中。
- 将数据存放在values的末尾,并将counter+1
PS:
- 这种管理方式要求节与节有序,并且每个节之内的所有数据也是有序的。
- 创建新节的条件是,未找到能够包含插入值的节。
缺点:
- 内存占用空间较大,例如两个数据也要开辟10W个数据的使用空间。
- 当数据上传顺序不合理时,会造成比较严重的空间浪费。(上传数据时按照数据中key值从小到大依次上传)
- 没有利用数据之间的关系,进行数据压缩来减少内存的占用率。
数据实测:
1G左右的索引数据,使用InMemory内存管理方式加载到内存后,内存占用大小约为3.6G左右。
BTree
SeaweedFS使用Google的度为32的BTree来实现内存索引管理,其对应的item为NeedValue。
经过实地测试,使用这种索引方式来管理内存时,1亿个索引存放在内存中的大小为5G左右。
缺点:
- 内存占用明显过大。
levelDb
SeaweedFS中使用的是syndtr按照google的leveldb(K/V)数据库编写的GOlang版本。
SeaweedFS中会根据索引文件的时间戳来决定是否更新levelDB对应的文件,如果启动时不存在levelDB所需要的文件,则SeaWeedFs会读取索引文件中的条目来自动生成levelDB文件。
leveldb数据库采用Snappy compression library来压缩数据,从而使数据方便存储。
数据实测:
1亿条索引,所占的内存空间为27M,申请的虚拟内存为900多M。比较符合项目要求。
PS:
SeaweedFS会在加载时统计需要删除的旧数据的大小。在使用leveldb内存管理时,会创建BTree来进行统计。
统计完成后,BTree应当被GC。但是SeaWeedFS中BTree仍然存在,没有被销毁,占用较大内存。这里需要注意!!!
sweedfs存在问题,unmout某个vid后,再mout时会出现leveldb不可用的错误。修改方案在unmout时,调用volume的close方法。
Boltdb
SeaweedFS使用的是bolt(K/V)数据。其加载以及使用方式基本与leveldb数据库相同。注意事项也一致。