分享

理解 OpenStack + Ceph (2):Ceph 的物理和逻辑结构 [Ceph Architecture]

 昵称10504424 2015-09-29

 本系列文章会深入研究 Ceph 以及 Ceph 和 OpenStack 的集成:

(1)安装和部署

(2)Ceph RBD 接口和工具

(3)Ceph 物理和逻辑结构

(4)Ceph 的基础数据结构

(5)Ceph 与 OpenStack 集成的实现

(6)TBD

1. Ceph 集群的物理结构

1.1 Ceph 内部集群

前一篇文章 我们知道,从物理上来讲,一个 Ceph 集群内部其实有几个子集群存在:

(1)MON(Montior)集群:MON 集群有由少量的、数目为奇数个的 Monitor 守护进程(Daemon)组成,它们负责通过维护 Ceph Cluster map 的一个主拷贝(master copy of Cluster map)来维护整 Ceph 集群的全局状态。理论上来讲,一个 MON 就可以完成这个任务,之所以需要一个多个守护进程组成的集群的原因是保证高可靠性。每个 Ceph node 上最多只能有一个 Monitor Daemon。

root@ceph1:~# ps -ef | grep ceph-mon
root       964     1  0 Sep18 ?        00:36:33 /usr/bin/ceph-mon --cluster=ceph -i ceph1 -f

实际上,除了维护 Cluster map 以外,MON 还承担一些别的任务,比如用户校验、日志等。详细的配置可以参考 MON 配置

(2)OSD (Object Storage Device)集群:OSD 集群由一定数目的(从几十个到几万个) OSD Daemon 组成,负责数据存储和复制,向 Ceph client 提供存储资源。每个 OSD 守护进程监视它自己的状态,以及别的 OSD 的状态,并且报告给 Monitor;而且,OSD 进程负责在数据盘上的文件读写操作;它还负责数据拷贝和恢复。在一个服务器上,一个数据盘有一个 OSD Daemon。

root@ceph1:~# ps -ef | grep ceph-osd
root      1204     1  0 Sep18 ?        00:24:39 /usr/bin/ceph-osd --cluster=ceph -i 3 -f
root      2254     1  0 Sep18 ?        00:20:52 /usr/bin/ceph-osd --cluster=ceph -i 6 -f

(3)若干个数据盘:一个Ceph 存储节点上可以有一个或者多个数据盘,每个数据盘上部署有特定的文件系统,比如 xfs,ext4 或者 btrfs,由一个 OSD Daemon 负责照顾其状态以及向其读写数据。

Disk /dev/vda: 21.5 GB, 21474836480 bytes
/dev/vda1               1    41943039    20971519+  ee  GPT
Disk /dev/vdb: 32.2 GB, 32212254720 bytes
/dev/vdb1               1    62914559    31457279+  ee  GPT

    (MON 和 OSD 可以共同在一个节点上,也可以分开)

    关于Ceph 支持的数据盘上的 xfs、ext4 和 btrfs 文件系统,它们都是日志文件系统(其特点是文件系统将没提交的数据变化保存到日志文件,以便在系统崩溃或者掉电时恢复数据),三者各有优势和劣势:

 

(4)要使用 CephFS,还需要 MDS 集群,用于保存 CephFS 的元数据

(5)要使用对象存储接口,还需要 RADOS Gateway, 它对外提供REST接口,兼容S3和Swift的API。

 

1.2 Ceph 网络结构

    Ceph 使用以太网连接内部各存储节点以及连接 client 和集群。Ceph 推荐使用两个网络:

  • 前端(北向)网络( a public (front-side) network)连接客户端和集群
  • 后端/东西向网络 (a cluster (back-side) network)来连接 Ceph 各存储节点

下图(来源)显示了这种网络拓扑结构:

这么做,主要是从性能(OSD 节点之间会有大量的数据拷贝操作)和安全性(两网分离)考虑。你可以在 Ceph 配置文件的 [global] 部分配置两个网络:

public network = {public-network/netmask}
cluster network = {cluster-network/netmask}

具体可以参考:

2. Ceph 集群的逻辑结构(以RBD为例)

 Ceph 集群的逻辑结构由 Pool 和 PG (Placement Group)来定义。

2.1 Pool 和 PG

    Pool 类似于 LVM 中的 Volume Group,类似于一个命名空间。RBD Image 类似于 LVM 中的 Logical Volume。RBD Image 必须且只能在一个 Pool 中。

Ceph Pool 有两种类型:

  1. Replicated pool:拷贝型 pool,通过生成对象的多份拷贝来确保在部分 OSD 丢失的情况下数据不丢失。这种类型的 pool 需要更多的裸存储空间,但是它支持所有的 pool 操作。
  2. Erasure-coded pool:纠错码型 pool(类似于 Software RAID)。在这种 pool 中,每个数据对象都被存放在 K+M 个数据块中:对象被分成 K 个数据块和 M 个编码块;pool 的大小被定义成 K+M 块,每个块存储在一个 OSD 中;块的顺序号作为 object 的属性保存在对象中。可见,这种 pool 用更少的空间实现存储,即节约空间;纠删码实现了高速的计算,但有2个缺点,一个是速度慢,一个是只支持对象的部分操作(比如:不支持局部写)。这篇文章 详细介绍了其原理和细节。

Pool 提供如下的能力:

  1. Resilience(弹力):即在确保数据不丢失的情况允许一定的 OSD 失败,这个数目取决于对象的拷贝(copy/replica)份数。对拷贝型 pool 来说,Ceph 中默认的拷贝份数是2,这意味着除了对象自身外,它还有一个另外的备份。你可以自己决定一个 Pool 中的对象的拷贝份数。
  2. Placement Groups(放置组):Ceph 使用 PG 来组织对象,这是因为对象可能成千上万,因此一个一个对象来组织的成本是非常高的。PG 的值会影响 Ceph 集群的行为和数据的持久性。你可以设置 pool 的 PG 数目。推荐的配置是,每个 OSD 大概 100 个 PG。
  3. CRUSH Rules (CRUSH 规则):数据映射的策略。系统默认提供 “replicated_ruleset"。用户可以自定义策略来灵活地设置 object 存放的区域。比如可以指定 pool1中所有objecst放置在机架1上,所有objects的第1个副本放置在机架1上的服务器A上,第2个副本分布在机架1上的服务器B上。 pool2中所有的object分布在机架2、3、4上,所有Object的第1个副本分布在机架2的服务器上,第2个副本分布在机架3的服 器上,第3个副本分布在机架4的服务器上。详细的信息可以参考这些文档 (1)(2)(3)(4)
  4. Snapshots(快照):你可以对 pool 做快照。
  5. Set Ownership:设置 pool 的 owner 的用户 ID。

2.2 (Placement Group)PG

    PG 映射一个 Pool 到它使用的若干个 OSD。如果一个拷贝型 Pool 的size(拷贝份数)为 2,它会包含指定数目的 PG,每个 PG 使用两个 OSD,其中,第一个为主 OSD (primary),其它的为从 OSD (secondary)。不同的 PG 可能会共享一个 OSD,类似于下图:

那如何确定一个 Pool 中有多少 PG?Ceph 不会自己计算,而是给出了一些参考原则,让 Ceph 用户自己计算。

  • 少于 5 个 OSD, 建议设为  128
  • 5 到 10 个 OSD,建议设为 512
  • 10 到 50 个 OSD,建议设为 4096
  • 50 个 OSD 以上,就需要有更多的权衡来确定 PG 数目
  • 你可以使用 pgcalc 工具

1.3 Ceph 结构和状态地图:Cluster map

    Ceph 要求 ceph 客户端和 OSD 守护进程需要知晓整个集群的拓扑结构,它们可以通过 Monitor 获取 cluster map 来达到这一点。Cluster map 包括:

(1)Monitor Map:MON 集群的状态(包括 the cluster fsid, the position, name address and port of each monitor, 创建时间,最后的更新时间等)。

复制代码
root@ceph1:/osd/data# ceph mon dump
dumped monmap epoch 1
epoch 1
fsid 4387471a-ae2b-47c4-b67e-9004860d0fd0
last_changed 0.000000
created 0.000000
0: 9.115.251.194:6789/0 mon.ceph1
1: 9.115.251.195:6789/0 mon.ceph2
2: 9.115.251.218:6789/0 mon.ceph3
复制代码

(2)OSD Map:当前所有 Pool 的状态和所有 OSD 的状态 (包括 the cluster fsid, map 创建和最后修改时间, pool 列表, replica sizes, PG numbers, a list of OSDs and their status (e.g., up, in) 等)。通过运行 ceph osd dump 获取。

复制代码
root@ceph1:~# ceph osd dump
epoch 76
fsid 4387471a-ae2b-47c4-b67e-9004860d0fd0
created 2015-09-18 02:16:19.504735
modified 2015-09-21 07:58:55.305221
flags
pool 0 'data' replicated size 3 min_size 2 crush_ruleset 0 object_hash rjenkins pg_num 64 pgp_num 64 last_change 1 flags hashpspool crash_replay_interval 45 stripe_width 0
osd.3 up   in  weight 1 up_from 26 up_thru 64 down_at 25 last_clean_interval [7,23) 9.115.251.194:6801/1204 9.115.251.194:6802/1204 9.115.251.194:6803/1204 9.115.251.194:6804/1204 exists,up d55567da-4e2a-40ca-b7c9-5a30240c895a
......
复制代码

(3)PG Map:包含PG 版本(version)、时间戳、最新的 OSD map epoch, full ratios, and 每个 PG 的详细信息比如 PG ID, Up Set, Acting Set, 状态 (e.g., active + clean), pool 的空间使用统计。可以使用命令 ceph pg dump 来获取 PG Map。

这里 有段代码可以以表格形式显示这些映射关系:

复制代码
ceph pg dump | awk '
 /^pg_stat/ { col=1; while($col!="up") {col++}; col++ }
 /^[0-9a-f]+\.[0-9a-f]+/ { match($0,/^[0-9a-f]+/); pool=substr($0, RSTART, RLENGTH); poollist[pool]=0;
 up=$col; i=0; RSTART=0; RLENGTH=0; delete osds; while(match(up,/[0-9]+/)>0) { osds[++i]=substr(up,RSTART,RLENGTH); up = substr(up, RSTART+RLENGTH) }
 for(i in osds) {array[osds[i],pool]++; osdlist[osds[i]];}
}
END {
 printf("\n");
 printf("pool :\t"); for (i in poollist) printf("%s\t",i); printf("| SUM \n");
 for (i in poollist) printf("--------"); printf("----------------\n");
 for (i in osdlist) { printf("osd.%i\t", i); sum=0;
 for (j in poollist) { printf("%i\t", array[i,j]); sum+=array[i,j]; poollist[j]+=array[i,j] }; printf("| %i\n",sum) }
 for (i in poollist) printf("--------"); printf("----------------\n");
 printf("SUM :\t"); for (i in poollist) printf("%s\t",poollist[i]); printf("|\n");
}'
复制代码

(4)CRUSH Map:包含当前磁盘、服务器、机架等层级结构 (Contains a list of storage devices, the failure domain hierarchy (e.g., device, host, rack, row, room, etc.), and rules for traversing the hierarchy when storing data)。 要查看该 map 的话,先运行 ceph osd getcrushmap -o {filename} 命令,然后运行 crushtool -d {comp-crushmap-filename} -o {decomp-crushmap-filename} 命令,在vi 或者 cat {decomp-crushmap-filename} 即可。

(5)MDS Map:包含当前所有 MSD 的状态 (the current MDS map epoch, when the map was created, and the last time it changed. It also contains the pool for storing metadata, a list of metadata servers, and which metadata servers are up and in)。通过执行 ceph mds dump 获取。

复制代码
root@ceph1:~# ceph mds dump
dumped mdsmap epoch 13
epoch   13
flags   0
created 2015-09-18 02:16:19.504427
modified        2015-09-18 08:05:55.438558
4171:   9.115.251.194:6800/962 'ceph1' mds.0.2 up:active seq 7
5673:   9.115.251.195:6800/959 'ceph2' mds.-1.0 up:standby seq 1
复制代码

3. CRUSH 算法(以RBD为例)

3.1 Ceph client 是如何将数据块放到 OSD 的

  Ceph 架构中,Ceph client 是直接读或者写存放在 RADOS 对象存储中的对象(data object)的,因此,Ceph 需要走完 (Pool, Object) → (Pool, PG) → OSD set → Disk 完整的链路,才能让 ceph client 知道目标数据 object:

(1)Ceph client 通过 CRUSH 算法计算出存放 object 的 PG 的 ID:

  1. 客户端输入 pool ID 和 object ID (比如 pool = “liverpool” and object-id = “john”)
  2. ceph 对 object ID 做 hash
  3. ceph 对该 hash 值取 PG 总数的模,得到 PG ID (比如 58)
  4. ceph 或者 pool ID (比如 “liverpool” = 4
  5. ceph 将  pool ID 和 PG ID 组合在一起(比如 4.58)

(2)通过 CRUSH 算法计算出将 object 保存到 PG 中哪个 OSD 上。

  每个 Pool 包含一定数量的 PG。CRUSH 算法动态地将 PG 映射到 OSD。这种映射在 Ceph client 和 OSD Daemon 之间建立了一个间接层。这是因为,Ceph 集群需要能够膨胀(增加 OSD)或者缩小(减少 OSD),以及再平衡(rebalance)。如果 Ceph client 知道 OSD Daemon 上保存了哪个对象,那么两者之间就建立了一个强绑定关系。因此,Ceph 使用 CRUSH 算法,得以动态地将每个 object 映射到 PG,以及将 PG 映射到 OSD。

 (object - PG --- OSD(s) 映射关系)

对 Ceph client 来说,只要它获得了 Cluster map,就可以使用 CRUSH 算法计算出某个 object 所在的 OSD。

(a)Ceph client 从 MON 获取最新的 cluster map,它包含所有的 monitors, OSDs, 和 metadata servers。

(b)Ceph client 根据上面的第(1)步计算出该 object 所在的 PG 的 ID。

(c)Ceph client 再根据 CRUSH 算法计算出 PG 中目标主 OSD 的 ID。

(d)Ceph client 向主 OSD 写入二进制数据块。

以存放一个文件为例,下图(来源)说明了完整的计算过程:

CRUSH 算法是相当复杂,快速看看的话可以参考 官方文章,或者直接读代码和作者的论文。几个简单的结论或原则:

  • 一个 RBD image(比如虚机的一个镜像文件)会分成几个 data objects 保存在 Ceph 对象存储中。
  • 一个 Ceph 集群还有多个 pool
  • 一个 Pool 包含若干个 PG
  • 许多 object 映射到 PG
  • 一个 object 只在一个 PG 中
  • 一个 PG 映射到一组 OSD,其中第一个 OSD 是主(primary),其余的是从(secondary)
  • 许多 PG 可以映射到某个 OSD

使用 这里 的脚本,可以看出在我的环境中,6 个pool,7 个 OSD,每个 pool 中有 192 个PG,每个 OSD 在128+ PG 中:

复制代码
pool :  4       5       0       1       2       3       | SUM
----------------------------------------------------------------
osd.4   20      19      21      23      21      24      | 128
osd.5   38      28      30      28      34      44      | 202
osd.6   30      33      33      32      34      36      | 198
osd.7   23      19      22      21      22      21      | 128
osd.8   26      36      34      36      30      20      | 182
osd.9   21      26      21      20      21      19      | 128
osd.3   34      31      31      32      30      28      | 186
----------------------------------------------------------------
SUM :   192     192     192     192     192     192     |  
复制代码

这张图(来源)也有助于理清其中的关系:

  

总之,Ceph 采用的是通过计算得出对象的位置,这比通过常见的查询算法获取位置快得多。CRUSH 算法是的 ceph 客户端自己计算对象要被保存在哪里(哪个 OSD),也使得客户端可以从主 OSD 上保存或者读取数据。

3.2 RBD  image 保存过程和形式

如下图所示,Ceph 系统中不同层次的组件/用户所看到的数据的形式是不一样的:

  • Ceph 客户端所见的是一个完整的连续的二进制数据块,其大小为创建 RBD image 是设置的大小或者 resize 的大小,客户端可以从头或者从某个位置开始写入二进制数据。
  • librados 负责在 RADOS 中创建对象(object),其大小为 pool 的 order 决定,默认情况下 order = 22 此时 object 大小为 4MB;以及负责将客户端传入的二进制块条带化为若干个条带(stripe)。
  • librados 控制哪个条带由哪个 OSD 写入(条带 ---写入哪个----> object ----位于哪个 ----> OSD)
  • OSD 负责创建在文件系统中创建文件,并将 librados 传入的数据写入数据。

  Ceph client 向一个 RBD image 写入二进制数据(假设 pool 的拷贝份数为 3):

(1)Ceph client 调用 librados 创建一个 RBD image,这时候不会做存储空间分配,而是创建若干元数据对象来保存元数据信息。

(2)Ceph client 调用 librados 开始写数据。librados 计算条带、object 等,然后开始写第一个 stripe 到特定的目标 object。

(3)librados 根据 CRUSH 算法,计算出 object 所对应的主 OSD ID,并将二进制数据发给它。

(4)主 OSD 负责调用文件系统接口将二进制数据写入磁盘上的文件(每个 object 对应一个 file,file 的内容是一个或者多个 stripe)。

(5)主 ODS 完成数据写入后,它使用 CRUSH 算啊计算出第二个OSD(secondary OSD)和第三个OSD(tertiary OSD)的位置,然后向这两个 OSD 拷贝对象。都完成后,它向 ceph client 反馈该 object 保存完毕。

(6)然后写第二个条带,直到全部写入完成。全部完成后,librados 还应该会做元数据更新,比如写入新的 size 等。

完整的过程(来源):

该过程具有强一致性的特点:

  • Ceph 的读写操作采用 Primary-Replica 模型,Client 只向 Object 所对应 OSD set 的 Primary 发起读写请求,这保证了数据的强一致性。
  • 由于每个 Object 都只有一个 Primary OSD,因此对 Object 的更新都是顺序的,不存在同步问题。
  • 当 Primary 收到 Object 的写请求时,它负责把数据发送给其他 Replicas,只要这个数据被保存在所有的OSD上时,Primary 才应答Object的写请求,这保证了副本的一致性。
  • 在 OSD 上,在收到数据存放指令后,它会产生2~3个磁盘seek操作:
    • 把写操作记录到 OSD 的 Journal 文件上(Journal是为了保证写操作的原子性)。
    • 把写操作更新到 Object 对应的文件上。
    • 把写操作记录到 PG Log 文件上。

3.3 条带化(striping)

  一个 RDB image 会被条带化为多个条带(stripe unit),每个 unit 会被保存在 Ceph 分布式对象存储(RADOS) 中的一个对象(object)内,而一个 object 可能会保存一个或者多个条带。这样的话,对 image 的读和写操作可以被分散到集群的多个 OSD 上,通常来讲这样可以防止某个 image 非常大或者非常忙时单个节点称为性能瓶颈。条带(stripe)是 librados 通过 ODS 写入数据的基本单位。
Ceph 的条带化行为(如果划分条带和如何写入条带)受三个参数控制:
  • order:RADOS Object 的大小为 2^[order] bytes。默认的 oder 为 22,这时候对象大小为4MB。
  • stripe_unit:条带(stripe unit)的大小。每个 [stripe_unit] 的连续字节会被连续地保存到同一个对象中,client 写满 stripe unit 大小的数据后,接着去下一个 object 中写下一个 stripe unit 大小的数据。
  • stripe_count:在分别写入了 [stripe_unit] 个字节到 [stripe_count] 个对象后,ceph 又重新从一个新的对象开始写下一个条带,直到该对象达到了它的最大大小。这时候,ceph 转移到下 [stripe_unit] 字节。
以下图为例:
(1)RBD image 会被保存在总共 8 个 RADOS object (计算方式为 client data size 除以 2^[order])中。
(2)stripe_unit 为 object size 的四分之一,也就是说每个 object 可以保存四个 stripe。
(3)stripe_count 为 4,即每个 object set 包含四个 object。这样,client 以 4 为一个循环,向一个 object set 中的每个 object 依次写入 stripe,写到第 16 个 stripe 后,按照同样的方式写第二个 object set。
   默认的情况下,[stripe_unit] 等于 object size;stripe_count 为1。意味着 ceph client 在将第一个 object 写满后再去写下一个 object。要设置其他的 [stripe_unit] 值,需要Ceph v0.53 版本及以后版本对 STRIPINGV2 的支持以及使用 format 2 image 格式。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多