Ceph中容量计算与管理的示例分析

79次阅读
没有评论

共计 6319 个字符,预计需要花费 16 分钟才能阅读完成。

这篇文章主要介绍 Ceph 中容量计算与管理的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

Ceph 中的容量计算与管理

在部署完 Ceph 集群之后,一般地我们可以通过 Ceph df 这个命令来查看集群的容量状态,但是 Ceph 是如何计算和管理的呢?相信大家都比较好奇。因为用过 ceph df 这个命令的人都会有这个疑问,它的输出到底是怎么计算的呢?为什么所有 pool 的可用空间有时候等于 GLOBAL 中的可用空间,有时候不等呢?带着这些疑问我们可以通过分析 ceph df 的实现,来看看 Ceph 是如何计算容量和管理容量的。

一般情况下 ceph df 的输出如下所示:

ceph-df

[root@study-1 ~]# ceph df
GLOBAL:
 SIZE AVAIL RAW USED %RAW USED 
 196G 99350M 91706M 45.55 
POOLS:
 NAME ID USED %USED MAX AVAIL OBJECTS 
 rbd 1 20480k 0.02 49675M 11 
 x 2 522 0 49675M 11

GLOBAL 维度中有 SIZE,AVAIL,RAW USED,%RAW USED。从上面的输出可以看到,ceph 对容量的计算其实是分为两个维度的。一个是 GLOBAL 维度,一个是 POOLS 的维度。

POOLS 维度中有 USED,%USED,MAX AVAIL,OBJECTS。

我们这里先把注意力放在 RAW USED,和 AVAIL 上。这个两个分析清楚之后,其它的也就迎刃而解了。

这里我们粗略算一下 GLOBAL 中的 RAW USED 为 91706M,明显大于下面 pool 中 USED 20480k*3 + 522bytes* 3 啊。而且各个 pool 的 MAX AVAIL 相加并不等于 GLOBAL 中的 AVAIL。我们需要深入代码分析一下为什么。

分析

Ceph  命令基本上都是首先到 Montior 这里,如何 Monitor 能处理请求,就直接处理,不能就转发。

我们看看 Monitor 是如何处理 ceph df 这个命令的。Monitor 处理命令主要是在 Monitor::hanlde_command 函数里。

handle_command

else if (prefix ==  df) {
 bool verbose = (detail ==  detail 
 if (f)
 f- open_object_section( stats 
 pgmon()- dump_fs_stats(ds, f.get(), verbose);
 if (!f)
 ds    \n 
 pgmon()- dump_pool_stats(ds, f.get(), verbose);
 if (f) { f- close_section();
 f- flush(ds);
 ds    \n 
 }
 }

dump_fs_stats 对应 GLOBAL 这个维度。dump_pool_stats 对应 POOLS 这个维度。从上面的代码可以知道,主要是两个函数完成了 df 命令的输出。一个是 pgmon()- dump_fs_stats,另一个是 pgmon()- dump_pool_stats。

GLOBAL 维度

从 PGMonitor::dump_fs_stats 开始:

dump_fs_stats

void PGMonitor::dump_fs_stats(stringstream  ss, Formatter *f, bool verbose) const
 if (f) {
 f- open_object_section( stats 
 f- dump_int(total_bytes , pg_map.osd_sum.kb * 1024ull);
 f- dump_int(total_used_bytes , pg_map.osd_sum.kb_used * 1024ull);
 f- dump_int(total_avail_bytes , pg_map.osd_sum.kb_avail * 1024ull);
 if (verbose) { f- dump_int( total_objects , pg_map.pg_sum.stats.sum.num_objects);
 }
 f- close_section();
 }

stat_pg_update

void OSDService::update_osd_stat(vector int  hb_peers)
 Mutex::Locker lock(stat_lock);
 osd_stat.hb_in.swap(hb_peers);
 osd_stat.hb_out.clear();
 osd- op_tracker.get_age_ms_histogram(osd_stat.op_queue_age_hist);
 // fill in osd stats too
 struct statfs stbuf;
 int r = osd- store- statfs(stbuf);
 if (r   0) { derr    statfs() failed:     cpp_strerror(r)   dendl;
 return;
 }
 uint64_t bytes = stbuf.f_blocks * stbuf.f_bsize;
 uint64_t used = (stbuf.f_blocks - stbuf.f_bfree) * stbuf.f_bsize;
 uint64_t avail = stbuf.f_bavail * stbuf.f_bsize;
 osd_stat.kb = bytes   10;
 osd_stat.kb_used = used   10;
 osd_stat.kb_avail = avail   10;
 osd- logger- set(l_osd_stat_bytes, bytes);
 osd- logger- set(l_osd_stat_bytes_used, used);
 osd- logger- set(l_osd_stat_bytes_avail, avail);
 check_nearfull_warning(osd_stat);
 dout(20)    update_osd_stat     osd_stat   dendl;

可以看到相关字段数值的输出主要依赖 pg_map.osd_sum 的值,而 osd_sum 是各个 osd_stat 的总和。所以我们需要知道单个 osd 的 osd_stat_t 是如何计算的。

FIleStore::statfs 从上面我们可以看到 update_osd_stat 主要是通过 osd- store- statfs(stbuf),来更新 osd_stat 的。因为这里使用的是 Filestore,所以需要进入 FileStore 看其是如何 statfs 的。

int FileStore::statfs(struct statfs *buf)
 if (::statfs(basedir.c_str(), buf)   0) {
 int r = -errno;
 assert(!m_filestore_fail_eio || r != -EIO);
 assert(r != -ENOENT);
 return r;
 }
 return 0;
}

可以看到上面 FileStore 主要是通过::statfs() 这个系统调用来获取信息的。这里的 basedir.c_str() 就是 data 目录。所以 osd_sum 计算的就是将所有 osd 数据目录的磁盘使用量加起来。回到上面的输出,因为我使用的是一个磁盘上的目录,所以在 statfs 的时候,会把该磁盘上的其它目录也算到 Raw Used 中。回到上面的输出,因为使用两个 OSD,且每个 OSD 都在同一个磁盘下,所以 GLOBAL 是这么算的

同上,就知道 Ceph 如何算 Raw Used,AVAIL 的。

POOLS 维度

从 PGMonitor::dump_pool_stats() 来看,该函数以 pool 为粒度进行循环,通过  pg_map.pg_pool_sum 来获取 pool 的信息。其中 USED,%USED,OBJECTS 是根据 pg_pool_sum 的信息算出来的。而 MAX AVAIL 是单独算出来的。

这里有一张图,可以帮助同学们梳理整个的流程。中间仅取了一些关键节点。有一些省略,如想知道全貌,可以在 PGMonitor::dump_pool_stats 查阅。

通过分析代码我们知道,pool 的使用空间(USED)是通过 osd 来更新的,因为有 update(write,truncate,delete 等)操作的的时候,会更新 ctx- delta_stats,具体请见 ReplicatedPG::do_osd_ops。举例的话,可以从处理 WRITE 的 op 为入手点,当处理 CEPH_OSD_OP_WRITE 类型的 op 的时候,会调用 write_update_size_and_usage()。里面会更新 ctx- delta_stats。当 IO 处理完,也就是 applied 和 commited 之后,会 publish_stats_to_osd()。

这里会将变化的 pg 的 stat_queue_item 入队到 pg_stat_queue 中。然后设置 osd_stat_updated 为 True。入队之后,由 tick_timer 在 C_Tick_WithoutOSDLock 这个 ctx 中通过 send_pg_stats() 将 PG 的状态发送给 Monitor。这样 Monitor 就可以知道 pg 的的变化了。

可用空间,即 MAX AVAIL 的值,计算稍微有点复杂。Ceph 是先计算 Available 的值,然后根据副本策略再计算 MAX AVAIL 的值。Available 的值是在 get_rule_avail() 中计算的。在该函数中通过 get_rule_weight_osd_map() 算出来一个有 weight 的 osd 列表。

注意这里的 weight 一般是小于 1 的,因为它除以了 sum。而 sum 就是 pool 中所有 osd weight 的总和。在拿到 weight 列表后,就会根据 pg_map.osd_stat 中 kb_avail 的值进行除以 weight,选出其中最小的,作为 Available 的值。

这么描述有些抽象了,具体举一个例子。比如这里我们的 pool 中有三个 osd,假设 kb_avail 都是 400G

即,

{osd_0: 0.9, osd_1, 0.8, osd_2: 0.7}。计算出来的 weight 值是 {osd_0: 0.9/2.4,osd_1: 0.8/2.4,osd_2: 0.7/2.4}

这样后面用 osd 的 available 空间除以这里的 weight 值,这里的 Available 的值就是 400G*0.7/2.4。这里附上一个公式,可能更直观一些。

然后根据你的 POOL 的副本策略不同,POOL 的 AVAL 计算方式也不同。如果是 REP 模式,就是直接除以副本数。如果是 EC 模式,则 POOL 的 AVAL 是 Available * k / (m + k)。

所以一般情况下,各个 POOL 的 MAX AVAIL 之和与 GLOBAL 的 AVAIL 是不相等的,但是可以很接近(相差在 G 级别可以忽略为接近)。

总结

分析到这里,我们知道 CEPH 中容量的计算是分维度的,如果是 GLOBAL 维度的话,因为使用的是 osd 的所在磁盘的 statfs 来计算所以还是比较准确的。而另一个维度 POOLS

由于需要考虑到 POOL 的副本策略,CRUSH RULE,OSD WEIGHT,计算起来还是比较复杂的。容量的管理主要是在 OSD 端,且 OSD 会把信息传递给 MON,让 MON 来维护。

计算 osd weight 值比较复杂,这里附上算 weight 的函数,添加了一些注释,有助于感兴趣的同学一起分析。

int CrushWrapper::get_rule_weight_osd_map(unsigned ruleno, map int,float  *pmap)
 if (ruleno  = crush- max_rules)
 return -ENOENT;
 if (crush- rules[ruleno] == NULL)
 return -ENOENT;
 crush_rule *rule = crush- rules[ruleno];
 // build a weight map for each TAKE in the rule, and then merge them
 for (unsigned i=0; i rule-  ++i) {
 map int,float  m;
 float sum = 0;
 if (rule- steps[i].op == CRUSH_RULE_TAKE) {// 如果是 take 的话, 则进入
 int n = rule- steps[i].arg1;
 if (n  = 0) { // n 如果大于等于 0 的话是 osd,否则是 buckets
 m[n] = 1.0; //  如果是 osd 的话,因为这里是直接 take osd,所有有没有权重已经不重要了
 sum = 1.0;
 } else { //  不是 osd,是 buckets 的话
 list int  q;
 q.push_back(n); // buckets  的 id  入队
 //breadth first iterate the OSD tree
 while (!q.empty()) { int bno = q.front(); //  取出 buckets 的 id
 q.pop_front(); //  出队
 crush_bucket *b = crush- buckets[-1-bno]; //  根据序号拿到 buckets
 assert(b); //  这个 buckets 必须是存在的
 for (unsigned j=0; j b- size; ++j) { //  从 buckets 的 items 数组中拿相应的 bucket
 int item_id = b- items[j]; 
 if (item_id  = 0) { //it s an OSD
 float w = crush_get_bucket_item_weight(b, j); //  拿出该 osd 的 weight
 m[item_id] = w; // m  入队
 sum += w; // weight 加和
 } else { //not an OSD, expand the child later
 q.push_back(item_id); //  如果不是 osd,则添加其 item_id, 所以这里是一个树的深度遍历
 }
 }
 }
 }
 }
 for (map int,float ::iterator p = m.begin(); p != m.end(); ++p) { map int,float ::iterator q = pmap- find(p- first);
 //  因为我们这里传入的 pmap 是没有数据的
 //  所以第一次必中, if (q == pmap- end()) { (*pmap)[p- first] = p- second / sum;
 } else {
 //  这里还需要考虑 osd 在不同的 buckets 里的情况
 q- second += p- second / sum;
 }
 }
 }
 return 0;
}

以上是“Ceph 中容量计算与管理的示例分析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计6319字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)