【IT168技术】前两天分享了淘宝核心系统研发团队的资料《leveldb实现解析.pdf》文章对leveldb的内部实现和运行机制都做了深入的分析。而下面文章来自同一位作者,为大家详细描述了淘宝开源的Tair在结合leveldb作为存储引擎上遇到的挑战和实战经验。
文章来源:rdc.taobao.com
Tair是淘宝开源的分布式KV缓存系统,内部将功能模块化,抽离出底层存储细节,可以接入不同的存储引擎。leveldb是Google开源的单机存储引擎,目前,已经作为Tair的持久化存储引擎ldb上线使用,这里对接入leveldb所做的处理以及修改进行介绍。
Tair首先是一个分布式的框架,有一系列策略满足CAP(数据备份,迁移复制等)。另外,还有针对应用场景的功能特性(namespace,数据过期时间,原子计数等)。接入leveldb时主要对KV操作之外的功能做相应的处理。leveldb本身的实现介绍参见leveldb实现解析。
1.修改配置
leveldb中有一系列参数会与读写的效率有关,将相关的配置以及编译常量统一修改成可运行时配置参数,测试选取非常好的配置值。
2. 实例个数控制
首先确定在一个tair server上要启几个leveldb实例?
Tair中以桶来组织数据,如果按照一个桶一个leveldb实例,在做迁移复制的时候会很方便,但考虑如果在一块磁盘上起多个实例,那么整体看来,多个顺序写变成了随机写,每个实例的compact进程会加剧整个磁盘的随机IO,所以并不采用每个桶一个实例,而是针对磁盘的数量由相关配置控制实例的个数。
3. 内部key的格式
为实现Tair中的功能逻辑,ldb传入leveldb的user-key格式如下:
①Tair中的数据可以设置过期时间,过期时间保存可以保存在在value的meta中,但考虑能在leveldb内部提前检查,省去解析value的消耗,将过期时间保存在key中,但并不参与排序。
②Tair中的数据组织以及迁移复制都是以桶(bucket)单位,为获得一个桶的数据,添加桶号前缀,保证一个桶的数据都存储在一起。leveldb内部对key做前缀压缩,桶号基本都可以被压缩掉。
③Tair中的数据区分namespace,会将namespace作为客户端传入key的前缀存储。
4. 自定义comparator
为了实现Tair中的功能逻辑,实现自定义的comparator传入leveldb,实现自定义的排序逻辑(传入leveldb的user-key中表示过期时间的前四个字节不参与排序),并为comparator添加两个判断数据是否有效的逻辑接口(ShouldDrop()/ShouldDropMaybe()),修改leveldb内部做遍历以及compact时判断数据是否有效(kTypeValue/kTypeDeletion)的逻辑。
①ShouldDropMaybe(): 用来判断数据是否已经过期。解析key中的expired_time即可。
②ShouldDrop(): 用来判断数据是否属于已经清理掉的数据(bucket已经迁移或者namespace已经被清理)。
③区分过期数据与清理数据的判断,是因为丢弃过期数据必须保证该key是数据的最后出现,否则删除该数据会让该key失去最后的更新状态,而清理数据有gc信息保证,不需要关心数据的状态。
5. 清理数据/自定义的内部compact逻辑
这里把清理数据的操作称为gc。
1) 迁移的bucket以及清理的namespace中的数据
一旦发生bucket迁移或者清理namespace,会把相应的信息保存下来(GcNode)
{
// 迁移走的bucket或者清掉的namespace
int32_t key_;
// 清理发生时的SequnceNumber,用来判断数据的时间点
uint64_t sequence_;
// 清理发生时的FileNumber,用来缩小compact的范围
uint64_t file_number_;
// 清理发生时的时间
uint32_t when_;
};