Ceph中的容量计算与管理(结合cephdf命令讲解)
展开全文
在部署完Ceph集之后,一般地我们可以通过Ceph df这个命令来查看集的容量状态,但是Ceph是如何计算和管理的呢?相信大家都比较好奇。因为用过 ceph df这个命令的人都会有这个疑问,它的输出到底是怎么计算的呢?为什么所有pool的可用空间有时候等于GLOBAL中的可用空间,有时候不等呢? 带着这些疑问我们可以通过分析ceph df的实现,来看看Ceph是如何计算容量和管理容量的。
一般情况下ceph df的输出如下所示:
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函数里。
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->_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);
weight的所有形式
  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,具体请见Replicated
PG::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()。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。