Ceph中的容量计算与管理

转自:https://www.ustack.com/blog/ceph%ef%bc%8drongliang/

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

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

ceph-df


1

2

3

4

5

6

7

8

[[email protected] ~]# 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函数里。

handle_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开始:

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是如何计算的。

stat_pg_update


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的。

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打造为真正高效,稳定的,能满足客户真实需求的分布式存储。

时间: 2024-10-29 19:05:49

Ceph中的容量计算与管理的相关文章

RK平台RAM和ROM容量计算

RK平台RAM和ROM容量计算 RAM 简介 1 RAM 实际容量的计算 2 RAM 标称容量的计算 ROM 简介 1 ROM究竟指什么 2 Nandflash 实际容量的计算 21 方式一使用sys文件系统计算ROM容量 22 方式二使用proc文件系统计算ROM容量 3 Nandflash 标称容量的计算 再说点什么 RK平台RAM和ROM容量计算 1. RAM 简介 RAM(random access memory),随机存取存储器,又称作"随机存储器",对于Android设备来

Ceph中Bufferlist的设计与使用

转自:https://www.ustack.com/blog/bufferlist/ 如果非要在整个Ceph中,找出一个类最重要,我觉得非Bufferlist莫属了,原因很简单,因为Bufferlist负责管理Ceph中所有的内存.整个Ceph中所有涉及到内存的操作,无论是msg分配内存接收消息,还是OSD构造各类数据结构的持久化表示(encode/decode),再到实际磁盘操作,都将bufferlist作为基础. Ceph中bufferlist的设计还是有些复杂的,其中包含三个主要的内buf

【Hadoop学习】HDFS中的集中化缓存管理

Hadoop版本:2.6.0 本文系从官方文档翻译而来,转载请尊重译者的工作,注明以下链接: http://www.cnblogs.com/zhangningbo/p/4146398.html 概述 HDFS中的集中化缓存管理是一个明确的缓存机制,它允许用户指定要缓存的HDFS路径.NameNode会和保存着所需快数据的所有DataNode通信,并指导他们把块数据缓存在off-heap缓存中. HDFS集中化缓存管理具有许多重大优势: 1.明确的锁定可以阻止频繁使用的数据被从内存中清除.当工作集

管道设计CAD系统中重量重心计算

管道设计CAD系统中重量重心计算 [email protected] Abstract. 管道设计CAD系统中都有涉及到重量重心计算的功能,这个功能得到的重心数据主要用于托盘式造船时方便根据重心设置吊装配件.重量信息主要用于采购订货.本文主要介绍相关软件中重量重心功能,及重量重心计算实现原理.最后结合OpenCASCADE计算管道模型重量重心来验证. Key Words. CoG, CentreOfMass, Piping CAD, Piping Design 1.Introduction 船舶

Linux中的Lvm逻辑卷管理

linux用户安装linux操作系统时遇到一个常见的难以解决的问题就是如何正确的评估各分区大小,以分配合适的磁盘空间,普通的磁盘分区方式在逻辑分区划分好之后就无法改变其大小,分区内存不能满足需要的解决方法. 逻辑卷管理Logical Volume Manager 它是linux环境下对磁盘分区进行管理的一种机制.linux用户安装linux操作系统时遇到一个常见的难以解决的问题就是如何正确的评估各分区大小,以分配合适的磁盘空间,普通的磁盘分区方式在逻辑分区划分好之后就无法改变其大小,当一个逻辑分

ceph分布式存储实战(5)——ceph存储配置(RBD镜像日常管理)

一.在线调整Ceph RBD的容量大小 1.支持调整ceph RBD的容量大小的底层文件系统     自由的增加或者减少RBD的容量,需要底层文件系统的支持,支持的文件系统有     1.XFS     2.EXT     3.Btrfs     4.ZFS 2.将RBD镜像ceph-client1-rbd1原始容量是10G扩容为20G,(在ceph集群中任意一台节点上)执行的命令如下:     rbd resize rbd/ceph-client1-rbd1 --size 20480     

1.8-磁盘结构,容量计算,raid级别

1.8                   磁盘结构,容量计算,raid 内容: 1. 磁盘外部结构 2. 磁盘内部结构 3. 计算磁盘大小 4.常用的raid级别详解 ? 磁盘知识点总览 第1章 磁盘结构 1.1 磁盘外部结构 1.1.1 组成:磁盘接口: SATA  SAS  PCI - E SATA:小的传数据 , 大的传电源 pci-E  速度最快  最好  价格高   和固态硬盘搭配 1.1.2 三种接口使用环境 接口类型 使用环境 转速/容量 SATA ①机械,自己人使用 ②备份 性

在如下8*6的矩阵中,请计算从A移动到B一共有____种走法。要求每次只能向上或向右移动一格,并且不能经过P。

在如下8*6的矩阵中,请计算从A移动到B一共有__种走法.要求每次只能向上或向右移动一格,并且不能经过P. A:456 B:492 C:568 D:626 E:680 F:702 解析: 8*6的矩阵,从左下角A到右上角B,一共需要走12步,其中5步向上,7步向右,因此总的走法一共有C(12,5)=792种,但题目规定不能经过P,因此需要减去经过P点的走法. 经过P的路径分为两部分,从A到P,从P到B. 同理,从A到P的走法:C(6,2)=15: 同理,从P到B的走法:C(6,3)=20: 因此

Python中的简单计算

Python中的简单计算 (1)基本的加减乘除 >>> 2 + 2 4 >>> 50 - 5*6 20 >>> (50 - 5*6) / 4 5.0 >>> 8 / 5  1.6 (2)除法总是会返回一个浮点数,想要返回整数,需要用"//"来表示(floor division),另外,可以用"%"进行取余操作 >>> 17 / 3  # classic division ret