Ceph中的容量计算与管理
转自:https://www.ustack.com/blog/ceph%ef%bc%8drongliang/
在部署完Ceph集群之后,一般地我们可以通过Ceph df这个命令来查看集群的容量状态,但是Ceph是如何计算和管理的呢?相信大家都比较好奇。因为用过 ceph df这个命令的人都会有这个疑问,它的输出到底是怎么计算的呢?为什么所有pool的可用空间有时候等于GLOBAL中的可用空间,有时候不等呢? 带着这些疑问我们可以通过分析ceph df的实现,来看看Ceph是如何计算容量和管理容量的。
一般情况下ceph df的输出如下所示:
1
2
3
4
5
6
7
8
|
[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 |
从上面的输出可以看到,ceph对容量的计算其实是分为两个维度的。一个是GLOBAL维度,一个是POOLS的维度。
GLOBAL 维度中有SIZE,AVAIL,RAW USED,%RAW USED。
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函数里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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' ; } } |
从上面的代码可以知道,主要是两个函数完成了df命令的输出。一个是pgmon()->dump_fs_stats,另一个是pgmon()->dump_pool_stats。
dump_fs_stats 对应GLOBAL这个维度。dump_pool_stats对应POOLS这个维度。
- GLOBAL维度
从PGMonitor::dump_fs_stats开始:
1
2
3
4
5
6
7
8
9
10
11
12
|
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(); } |
可以看到相关字段数值的输出主要依赖pg_map.osd_sum的值,而osd_sum是各个osd_stat的总和。所以我们需要知道单个osd的osd_stat_t是如何计算的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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; } |
从上面我们可以看到update_osd_stat 主要是通过osd->store->statfs(&stbuf),来更新osd_stat的。因为这里使用的是Filestore,所以需要进入FileStore看其是如何statfs的。
1
2
3
4
5
6
7
8
9
10
|
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的函数,添加了一些注释,有助于感兴趣的同学一起分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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->len; ++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; } |
关于作者:
李田清:UnitedStack有云存储工程师,3年OpenStack开发和架构经验,熟悉分布式存储系统。主要关注分布式存储,与云计算领域。致力于将Ceph打造为真正高效,稳定的,能满足客户真实需求的分布式存储。
Ceph中的容量计算与管理的更多相关文章
- CEPH-5:ceph集群基本概念与管理
ceph集群基本概念与管理 ceph集群基本概念 ceph集群整体结构图 名称 作用 osd 全称Object Storage Device,主要功能是存储数据.复制数据.平衡数据.恢复数据等.每个O ...
- 【Hadoop学习】HDFS中的集中化缓存管理
Hadoop版本:2.6.0 本文系从官方文档翻译而来,转载请尊重译者的工作,注明以下链接: http://www.cnblogs.com/zhangningbo/p/4146398.html 概述 ...
- 管道设计CAD系统中重量重心计算
管道设计CAD系统中重量重心计算 eryar@163.com Abstract. 管道设计CAD系统中都有涉及到重量重心计算的功能,这个功能得到的重心数据主要用于托盘式造船时方便根据重心设置吊装配件. ...
- [svc][op]磁盘(结构)容量计算
磁盘结构和容量计算 fdisk -l显示信息详解 [root@www.linuxidc.com ~]# fdisk -l Disk /dev/sda: bytes heads, sectors/tra ...
- Ceph中的序列化
转自:https://www.ustack.com/blog/cephxuliehua/ 作为主要和磁盘.网络打交道的分布式存储系统,序列化是最基础的功能之一,今天我们来看一下Ceph中序列化的设计与 ...
- Ceph中Bufferlist的设计与使用
转自:https://www.ustack.com/blog/bufferlist/ 如果非要在整个Ceph中,找出一个类最重要,我觉得非Bufferlist莫属了,原因很简单,因为Bufferlis ...
- java中hashmap容量的初始化
HashMap使用HashMap(int initialCapacity)对集合进行初始化. 在默认的情况下,HashMap的容量是16.但是如果用户通过构造函数指定了一个数字作为容量,那么Hash会 ...
- 【CDN+】 Spark入门---Handoop 中的MapReduce计算模型
前言 项目中运用了Spark进行Kafka集群下面的数据消费,本文作为一个Spark入门文章/笔记,介绍下Spark基本概念以及MapReduce模型 Spark的基本概念: 官网: http://s ...
- Ceph中的Copyset概念和使用方法
前言 copyset运用好能带来什么好处 降低故障情况下的数据丢失概率(增加可用性) 降低资源占用,从而降低负载 copyset的概念 首先我们要理解copyset的概念,用通俗的话说就是,包含一个数 ...
随机推荐
- 我的Android进阶之旅------>解决Android Studio编译后安装apk报错:The APK file does not exist on disk
1.错误描述 今天用Android Studio编译应用后安装APK的时候,报错了,错误如下所示: The APK file build\outputs\apk\OYP_2.3.4_I2Base_64 ...
- linux执行run文件显示cannot execute binary file
感谢朋友支持本博客,欢迎共同探讨交流,因为能力和时间有限.错误之处在所难免,欢迎指正! 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/qq_21398167 原博文地 ...
- 什么是EJB
学习EJB可以加深对J2EE平台的认识. 百科定义EJB: 被称为java企业bean,服务器端组件,核心应用是部署分布式应用程序.用它部署的系统不限定平台.实际上ejb是一种产品,描述了应用组件要解 ...
- golang SQLite3性能测试
SQLite是个小型的数据库,很简洁,即支持文件也支持内存,比较适合小型的独立项目,在没有网络的时候做一些复杂的关系数据存储和运算. 为了考察性能做10M(1000万)条记录的测试,测试机4CPU.8 ...
- python s13 day04
1.1 all() 和 any( ) all() any() 0,None,"", [], (),{} #布尔值为0的 列举,None ,空列表,空元祖,空. print( ...
- python识别验证码
1.tesseract-ocr安装 tesseract-ocr windows下载地址 http://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr- ...
- curl简介、安装及使用
目录 curl简介 curl安装 curl使用 curl简介 curl是Linux下一个强大的文件传输工具,它利用URL语法在命令行方式下工作,支持文件上传和下载. curl安装 Ubuntu系统键入 ...
- Android 6.0 Kotlin 蓝牙BLE扫描
package com.arci.myapplication import android.os.Bundleimport android.support.design.widget.Snackbar ...
- 筛选最新生成的报告——sort
筛选出最新报告发送,返回报告路径 import os def filePath(path): return os.path.join(os.path.abspath(os.path.dirname(o ...
- nodejs入门-静态文件服务器
本文展示是基于node.js的静态文件服务器,代码参考自这里,主要是练习node http.文件模块的使用,另外,对理解http协议也很有帮助除了实现了基本的路由控制,还实现了MIME类型.304缓存 ...