前言
对于ceph的FileStore实现而言,ObjectMap是非常重要的。因为除了将数据写入本地文件系统(XFS or EXT4 or btrfs,Whatever)还有属性信息,有少量的属性记录在本地文件系统中,以扩展属性xattr的方式存储。还有一些属性信息记录在key-value DB中。而key-value DB部分即ObjectMap,简称omap。
FileStore雇佣的key-value DB有三个版本,分别是google的LevelDB,以及facebook在LevelDB基础上的改进版 RocksDB,还有KineticStore。早期的ceph使用LevelDB,原因是Ceph项目的早期,RocksDB还没有横空出世。Ceph后期的版本从LevelDB switch to RocksDB。
代码导读
FileStore的omap中存放的都是对象的属性信息,以key-value的形式存在,那么对于不同的属性,如何定义对象的键值key呢?
最直接的想法就是(object id + xattr key),两者结合一起,形成对象的键值key,但是这种方法有一个问题,object id可能会很长,尤其是当单个对象存在很多属性的时候,object id不得不在key值中出现多次,这必然会造成存储空间的浪费。
Ceph的FileStore分成了2步,第一步根据object id生成一个比较短的seq,然后seq + xattr key形成对象的某个属性的键值。
omap不是通过计算从object id 获取seq的,他是首先根据object id,存放一个Header类型的数据结构到LevelDB,其中Header中的一个成员变量为seq。
key : HOBJECT_TO_SEQ +ghobject_key(oid)
value : header
Header的定义如下:
274 struct _Header {
275 uint64_t seq;
276 uint64_t parent;
277 uint64_t num_children;
278
279 coll_t c;
280 ghobject_t oid;
281
282 SequencerPosition spos;
}
对象的属性是如下的key-value pair
key: USER_PREFIX + header_key(header->seq) + XATTR_PREFIX +key value: value(即该key对应value值)
综合上面的讨论,如果要获取某个对象的oid的某个属性的值,需要分成两步走
1 找到Header,从header中取出seq的值
2 根据seq的值生成该属性对应的新的最终的键值,从LevelDB中取出value
这部分的代码逻辑在:
/*获取对象oid的某个或者某几个属性的值*/
int DBObjectMap::get_xattrs(const ghobject_t &oid,
const set<string> &to_get,
map<string, bufferlist> *out)
{
MapHeaderLock hl(this, oid);
/*第一步根据osd找到header*/
Header header = lookup_map_header(hl, oid);
if (!header)
return -ENOENT;
/*根据找到的header中的seq,生成属性的键值,去levelDB中寻找该key对应的value*/
return db->get(xattr_prefix(header), to_get, out);
}
int DBObjectMap::set_xattrs(const ghobject_t &oid,
const map<string, bufferlist> &to_set,
const SequencerPosition *spos)
{
KeyValueDB::Transaction t = db->get_transaction();
MapHeaderLock hl(this, oid);
/*寻找oid对应的header,如若没有,则新建一个header*/
Header header = lookup_create_map_header(hl, oid, t);
if (!header)
return -EINVAL;
if (check_spos(oid, header, spos))
return 0;
/*根据header中的seq,得到真正的键值,然后设置一个或者多个属性*/
t->set(xattr_prefix(header), to_set);
return db->submit_transaction(t);
}
const string DBObjectMap::USER_PREFIX = "_USER_";
const string DBObjectMap::XATTR_PREFIX = "_AXATTR_";
string DBObjectMap::header_key(uint64_t seq)
{
char buf[100];
snprintf(buf, sizeof(buf), "%.*" PRId64, (int)(2*sizeof(seq)), seq);
return string(buf);
}
string DBObjectMap::xattr_prefix(Header header)
{
return USER_PREFIX + header_key(header->seq) + XATTR_PREFIX;
}
介绍了seq,看样子它最作用很重要,它的值是怎么生成的呢?
LevelDB中存放着一个特殊的全局意义的key-value
key: SYS_PREFIX + GLOBAL_STATE_KEY value : state
其中state的定义如下:
struct State {
__u8 v;
uint64_t seq;
State() : v(0), seq(1) {}
explicit State(uint64_t seq) : v(0), seq(seq) {}
void encode(bufferlist &bl) const {
ENCODE_START(2, 1, bl);
::encode(v, bl);
::encode(seq, bl);
ENCODE_FINISH(bl);
}
void decode(bufferlist::iterator &bl) {
DECODE_START(2, bl);
if (struct_v >= 2)
::decode(v, bl);
else
v = 0;
::decode(seq, bl);
DECODE_FINISH(bl);
}
void dump(Formatter *f) const {
f->dump_unsigned("seq", seq);
}
...
}
该结构体只有一个成员变量,即seq,当产生新的Header的时候,会该值会递增,写入LevelDB
1115 DBObjectMap::Header DBObjectMap::_generate_new_header(const ghobject_t &oid,
1116 Header parent)
1117 {
1118 Header header = Header(new _Header(), RemoveOnDelete(this));
1119 header->seq = state.seq++;
1120 if (parent) {
1121 header->parent = parent->seq;
1122 header->spos = parent->spos;
1123 }
1124 header->num_children = 1;
1125 header->oid = oid;
1126 assert(!in_use.count(header->seq));
1127 in_use.insert(header->seq);
1128
1129 write_state();
1130 return header;
1131 }
因为是全局的,为了防止竞争,需要加锁保护。
Header generate_new_header(const ghobject_t &oid, Header parent) {
Mutex::Locker l(header_lock);//加锁保护
return _generate_new_header(oid, parent);
}
DBObjectMap::Header DBObjectMap::lookup_create_map_header(
const MapHeaderLock &hl,
const ghobject_t &oid,
KeyValueDB::Transaction t)
{
Mutex::Locker l(header_lock); // 加锁保护
Header header = _lookup_map_header(hl, oid);
if (!header) {
header = _generate_new_header(oid, Header());
set_map_header(hl, oid, *header, t);
}
return header;
}
理解了上述部分,DBObjectMap部分的其他代码,基本就是流水账了,可以比较容易的理解了,不再赘述。