Skip to content

第三章 Ceph架构概述 v1.0 ​

第一部分 这篇先记什么 ​

前面搭单节点 Ceph 的时候,命令是一条条跑通了,但 MON、OSD、PG 和 CRUSH 这几个东西在我脑子里还是混在一起。这篇就先把它们的关系捋一下,等后面看 Ceph 的 C++ 代码时,再回来对照。

这里不打算用几十行 C++ 去重写 Ceph 的 OSD 或 CRUSH。对象到底放在哪,是集群图、CRUSH map、故障域和副本规则一起算出来的结果,一小段代码代替不了。所以下面只记两件事:真实的数据路径,和官方库的接口。

第二部分 几个组件 ​

一、RADOS、MON 和 OSD ​

RADOS 是 Ceph 最底下那层对象存储。客户端要读写数据,会先找 Monitor 拿一份集群图;拿到之后,对象放哪儿其实不用再问谁,自己按对象名、池和 CRUSH 规则算就行,算完直接连对应的 OSD。

这三个东西的分工大概是:

  • MON:管集群图这类一致性信息,不参与每次对象读写,也不帮客户端转发数据。
  • OSD:真正存数据的地方,复制、恢复、心跳都归它管。
  • PG:对象和 OSD 中间的一层映射单位,每个池对应自己的 CRUSH 规则。

二、RBD、RGW 和 CephFS ​

这三种都是在 RADOS 之上包出来的用法,按场景挑一个就行:

  • RBD:块设备接口,虚拟机磁盘和云平台的后端存储常用它。
  • RGW:对象网关,对外提供 S3 或 Swift 兼容的接口。
  • CephFS:文件系统接口,比前两个多一个 MDS,专门处理文件元数据。

第三部分 CRUSH 和数据路径 ​

CRUSH 并不是“从一堆可用 OSD 里挑第一个”那种算法。它是照着 CRUSH map 里的层级、权重、故障域和规则,为一个 PG 算出副本应该落在哪些 OSD 上。好处是客户端拿到集群图后能自己算,不用去问中心目录。

text
对象名 + 池
    -> 计算 PG
    -> 套用池关联的 CRUSH 规则
    -> 主 OSD 和副本 OSD

一次写入,我先是按这个顺序理解的:

  1. 客户端连上 MON,拿到当前集群图。
  2. 客户端用对象名和池算出 PG,再用 CRUSH 找到这一组 acting set。
  3. 客户端把写请求发给其中的主 OSD。
  4. 主 OSD 把数据同步给副本 OSD,等池要求的确认数满足之后,才把结果返回给客户端。

读也是同一个路子,先算出对象在哪,再从合适的 OSD 读回来;至于具体挑哪个副本、恢复期间怎么处理,就交给客户端和集群当时的状态去决定了。

第四部分 librados C++最小调用路径 ​

下面这段代码是按官方 librados 的调用顺序整理的。要跑起来得先装好 Ceph 开发包,本机有可用的 /etc/ceph/ceph.conf,再建一个叫 data 的池,编译时链上 -lrados。

它只演示 RADOS 的对象读写,别把它当成 RBD 或 CRUSH 的实现。

cpp
#include <cstdlib>
#include <iostream>
#include <rados/librados.hpp>

int main() {
    librados::Rados cluster;
    int ret = cluster.init2("client.admin", "ceph", 0);
    if (ret < 0) {
        return EXIT_FAILURE;
    }

    ret = cluster.conf_read_file("/etc/ceph/ceph.conf");
    if (ret < 0) {
        return EXIT_FAILURE;
    }

    ret = cluster.connect();
    if (ret < 0) {
        return EXIT_FAILURE;
    }

    librados::IoCtx ioctx;
    ret = cluster.ioctx_create("data", ioctx);
    if (ret < 0) {
        cluster.shutdown();
        return EXIT_FAILURE;
    }

    librados::bufferlist write_buffer;
    write_buffer.append("Hello, Ceph!");
    ret = ioctx.write_full("hello", write_buffer);
    if (ret < 0) {
        ioctx.close();
        cluster.shutdown();
        return EXIT_FAILURE;
    }

    librados::bufferlist read_buffer;
    ret = ioctx.read("hello", read_buffer, 1024, 0);
    if (ret >= 0) {
        std::cout << read_buffer.c_str() << '\n';
    }

    ioctx.close();
    cluster.shutdown();
    return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

我自己的单节点部署步骤和实际环境都记在 Ceph单节点集群内网搭建 里了;集群架构和 librados 的完整参数,还是以 Ceph 官方文档 为准。