本系列文章会深刻研究 Ceph 以及 Ceph 和 OpenStack 的集成:php
(1)安装和部署html
(2)Ceph RBD 接口和工具前端
(3)Ceph 物理和逻辑结构node
(4)Ceph 的基础数据结构mysql
(5)Ceph 与 OpenStack 集成的实现linux
(6)QEMU-KVM 和 Ceph RBD 的 缓存机制总结ios
(7)Ceph 的基本操做和常见故障排除方法git
(8)关于Ceph PGsgithub
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 文件系统,它们都是日志文件系统(其特色是文件系统将没提交的数据变化保存到日志文件,以便在系统崩溃或者掉电时恢复数据),三者各有优点和劣势:
- btrfs (B-tree 文件系统) 是个很新的文件系统(Oracel 在2014年8月发布第一个稳定版),它将会支持许多很是高大上的功能,好比 透明压缩( transparent compression)、可写的COW 快照(writable copy-on-write snapshots)、去重(deduplication )和加密(encryption )。所以,Ceph 建议用户在非关键应用上使用该文件系统。 更多的参考包括 (1)(2)(3)。
- xfs 和 btrfs 相比较ext3/4而言,在高伸缩性数据存储方面具备优点。
- Ceph 的这篇文章 明确推荐在生产环境中使用 XFS,在开发、测试、非关键应用上使用 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}
具体能够参考:
- Deploying Ceph with High Performance Networks, Architectures and benchmarks for Block Storage Solutions
- Deploying Ceph with High Performance Networks
- CHAPTER 2. NETWORKING RECOMMENDATIONS
- Ceph with a cluster and public network on IPv6 谈到了 IPV6 的支持。
1.3 RDB Cache (缓存)
1.3.1 常见的 Write Cache 种类
缓存种类 | 说明 | 优劣势 | 适合场景 |
Write-through(直写) | 这种缓存方式在写 I/O 时把数据放入缓存,同时直接写入底层的持久存储,而后再向主机确认写入操做完成。 | 安全地保存数据,从缓存读,减小了读操做的延迟,可是写操做 的延迟没获得优化 | 适合于写较少,可是频繁度的应用 |
Write-back (回写) | 数据直接写入缓存,而后向主机返回写入完成。 | 对频繁写应用减小了写的延迟,可是有数据丢失风险 | 对读写混合型应用有优点,可是须要考虑数据保护 |
缓存的一般位置分类:
- 服务器(主机)上:RAID 卡或者 HBA 卡上作缓存。
- VMM 内:在 Hypervisor 上作缓存。
- 客户机操做系统内:以 Windows 2012 为例,它提供 write-back 缓存机制。
更多资料,能够参考 Cache is vital for application deployment, but which one to choose。
1.3.2 Ceph RBD 缓存
默认状况下,Ceph RBD 是不使用缓存的,读和写直接到 Ceph 集群中的存储,写只有在全部 replica 上写都完成后才给客户端返回写完成。Ceph 在较新的版本上陆续添加了 RBD 缓存支持:
- 从 0.46 版本开始,Ceph 支持 write-back 缓存,你能够在 ceph.conf 文件的 [client] 部分添加 rbd cache = true 来使得 write-back 缓存生效。这时候,写几乎是当即返回,可是数据只有在被 flushed 后才写入到实际存储。
- 从 0.47 版本开始,Ceph 支持 write-through 缓存机制。你只须要再添加配置项 rbd cache max dirty = 0 便可。
- 从 0.60 版本开始,Ceph 支持 rbd cache writethrough until flush 配置项。设置它为 true 时,会使得 write-through 机制变得更加安全,由于老的客户机操做系统(2.6.32 内核版本以前)可能不支持 flush 操做。所以,在设置了该配置项为 true 时,即便用户设置了使用 write-through 机制,Ceph 也会自动使用 write-back 机制,直到它收到第一个 flush 指令后才真正使用 write-through。
可见 RBD 缓存是在客户端作的,见以下图示:
更多信息,能够参考个人另外一篇文章:QEMU-KVM 和 Ceph RBD 的 缓存机制总结
1.3.3 Cache tiering (缓存分层)
Ceph 还支持在集群段作缓存分层。其原理是,在较快的磁盘好比 SSD 上创建一个 cache pool,在创建存储池(storage pool)和它之间的 cache 关系,设置必定的缓存策略,实现相似于在客户端缓存一样的效果。
更多的信息及详细配置,参见 CACHE TIERING 和 Intel 的文章。
1.3.4 RBD Cache 和 Cache Tiering 的区别
从上面的分析能够看出来,二者的区别在于缓存的位置不一样:
- Cache tiering 是 RADOS 层在 OSD 端进行数据缓存,也就是说不管是块存储、对象存储仍是文件存储均可以使用tier来提升读写速度
- RBD Cache是 rbd 层在客户端的缓存,也就是只支持块存储。
Rbd cache是 客户端的缓存,当多个客户端使用同个块设备时,存在客户端数据不一致的问题。举个例子,用户A向块设备写入数据后,数据停留在客户本身的缓存中,没有当即刷新到磁盘,因此其它用户读取不到A写入的数据。可是tier不存在这个问题,由于全部用户的数据都直接写入到 ssd,用户读取数据也是在ssd中读取的,因此不存在客户端数据不一致问题。
通常地,Tier 使用 SSD 作缓存,而 Rbd cache 只能使用内存作缓存。SSD和内存有两个方面的差异,一个是读写速度、另外一个是掉电保护。掉电后内存中的数据就丢失了,而ssd中的数据不会丢失。
2. Ceph 集群的逻辑结构(以RBD为例)
Ceph 集群的逻辑结构由 Pool 和 PG (Placement Group)来定义。
2.1 Pool
一个 Pool 是 Ceph 中的一些对象的逻辑分组,它并不表示一个连续的分区,而只是一个逻辑概念,相似于将二进制数据打了tag同样而后根据tag归类同样。它相似于 LVM 中的 Volume Group,相似于一个命名空间。RBD Image 相似于 LVM 中的 Logical Volume。RBD Image 必须且只能在一个 Pool 中。Pool 由若干个PG组成。其属性包括:
- 全部性和访问权限
- 对象副本数目
- PG 数目
- CRUSH 规则集合
Ceph Pool 有两种类型:
- Replicated pool:拷贝型 pool,经过生成对象的多份拷贝来确保在部分 OSD 丢失的状况下数据不丢失。这种类型的 pool 须要更多的裸存储空间,可是它支持全部的 pool 操做。
- Erasure-coded pool:纠错码型 pool(相似于 Software RAID)。在这种 pool 中,每一个数据对象都被存放在 K+M 个数据块中:对象被分红 K 个数据块和 M 个编码块;pool 的大小被定义成 K+M 块,每一个块存储在一个 OSD 中;块的顺序号做为 object 的属性保存在对象中。可见,这种 pool 用更少的空间实现存储,即节约空间;纠删码实现了高速的计算,但有2个缺点,一个是速度慢,一个是只支持对象的部分操做(好比:不支持局部写)。这篇文章 详细介绍了其原理和细节。
Pool 提供以下的能力:
- Resilience(弹力):即在确保数据不丢失的状况容许必定的 OSD 失败,这个数目取决于对象的拷贝(copy/replica)份数。对拷贝型 pool 来讲,Ceph 中默认的拷贝份数是2,这意味着除了对象自身外,它还有一个另外的备份。你能够本身决定一个 Pool 中的对象的拷贝份数。
- Placement Groups(放置组):Ceph 使用 PG 来组织对象,这是由于对象可能成千上万,所以一个一个对象来组织的成本是很是高的。PG 的值会影响 Ceph 集群的行为和数据的持久性。你能够设置 pool 的 PG 数目。推荐的配置是,每一个 OSD 大概 100 个 PG。
- CRUSH Rules (CRUSH 规则):数据映射的策略。系统默认提供 “replicated_ruleset"。用户能够自定义策略来灵活地设置 object 存放的区域。好比能够指定 pool1中全部objecst放置在机架1上,全部objects的第1个副本放置在机架1上的服务器A上,第2个副本分布在机架1上的服务器B上。 pool2中全部的object分布在机架二、三、4上,全部Object的第1个副本分布在机架2的服务器上,第2个副本分布在机架3的服 器上,第3个副本分布在机架4的服务器上。详细的信息能够参考这些文档 (1)(2)(3)(4)。
- Snapshots(快照):你能够对 pool 作快照。
- Set Ownership:设置 pool 的 owner 的用户 ID。
- Ceph 集群建立后,默认建立了 data,metadata 和 rbd 三个存储池。
2.2 (Placement Group)PG
2.2.1 概念
PG 概念很是复杂,主要有以下几点:
- PG 也是对象的逻辑集合。同一个PG 中的全部对象在相同的 OSD 上被复制。
- PG 聚合一部分对象成为一个组(group),这个组被放在某些OSD上(place),合起来就是 Placemeng Group (放置组)了。
- Epoch:PG map 的版本号,它是一个单调递增的序列。
- Peering:见下文的状态(8)描述。详细过程请参阅 Ceph:pg peering过程分析。
- Acting set:支持一个 PG 的全部 OSD 的有序列表,其中第一个 OSD 是主OSD,其他为次。acting set 是 CRUSH 算法分配的,可是不必定已经生效了。
- Up set:某一个 PG map 历史版本的 acting set。在大多数状况下,acting set 和 up set 是一致的,除非出现了 pg_temp。
- Current Interval or Past Interval:若干个连续的版本号,这些版本中 acting 和 up set 保持不变。
- PG temp:在Ceph 正在往主 OSD 回填数据时,这个主OSD是不能提供数据服务的,这时候,它会向 MON 申请一个临时的 acting set,这就是 PG temp。举个例子,如今 acting set 是[0,1,2],出现了一点事情后,它变为 [3,1,2],此时 osd.3 仍是空的所以它没法提供数据服务所以它还须要等待backfilling过程结束,所以,它会向 MON 申请一个临时的 set 好比 [1,2,3],此时将由 osd.1 提供数据服务。回填过程结束后,该临时 set 会被丢弃,从新由 osd.3 提供服务。
- 主 (primary) OSD:在 acting set 中的首个 OSD,负责接收客户端写入数据;默认状况下,提供数据读服务,可是该行为能够被修改。它还负责 peering 过程,以及在须要的时候申请 PG temp。
- 次 (replica)OSD:在 acting set 中的除了第一个之外的其他 OSD。
- 流浪 (stray) OSD:已经不是 acting set 中了,可是尚未被告知去删除数据 的 OSD。
- PG 的 acting set 是由 CRUSH 算法根据 CRUSH Rules 动态地计算得出的。
2.2.2 特色
其主要特色以下:
- 基本特色
- PG 肯定了 pool 中的对象和 OSD 之间的映射关系。一个 object 只会存在于一个 PG 中,可是多个 object 能够在同一个 PG 内。
- Pool 的 PG 数目是建立 pool 时候指定的,Ceph 官方有推荐的计算方法。其值与 OSD 的总数的关系密切。当Ceph 集群扩展 OSD 增多时,根据须要,能够增长 pool 的 PG 数目。
- 对象的副本数目,也就是被拷贝的次数,是在建立 Pool 时指定的。该分数决定了每一个 PG 会在几个 OSD 上保存对象。若是一个拷贝型 Pool 的size(拷贝份数)为 2,它会包含指定数目的 PG,每一个 PG 使用两个 OSD,其中,第一个为主 OSD (primary),其它的为从 OSD (secondary)。不一样的 PG 可能会共享一个 OSD。
- Ceph 引入 PG 的目的主要是为了减小直接将对象映射到 OSD 的复杂度。
- PG 也是Ceph 集群作清理(scrubbing)的基本单位,也就是说数据清理是一个一个PG来作的。
- PG 和 OSD 之间的映射关系由 CRUSH 决定,而它作决定的依据是 CRUSH 规则(rules)。CRUSH 将全部的存储设备(OSD)组织成一个分层结构,该结构能区分故障域(failure domain),该结构中每一个节点都是一个 CRUSH bucket。详细状况请阅读 CRUSH 相关的文档。
- PG 和 OSD 的关系是动态的:
- 一开始在 PG 被建立的时候,MON 根据 CRUSH 算法计算出 PG 所在的 OSD。这是它们之间的初始关系。
- Ceph 集群中 OSD 的状态是不断变化的,它会在以下状态之间作切换:
- up:守护进程运行中,可以提供IO服务;
- down:守护进程不在运行,没法提供IO服务;
- in:包含数据;
- out:不包含数据
- 部分 PG 和 OSD 的关系会随着 OSD 状态的变化而发生变化。
- 当新的 OSD 被加入集群后,已有OSD上部分PG将可能被挪到新OSD上;此时PG 和 OSD 的关系会发生改变。
- 当已有的某 OSD down 了并变为 out 后,其上的 PG 会被挪到其它已有的 OSD 上。
- 可是大部分的 PG 和 OSD 的关系将会保持不变,在状态变化时,Ceph 尽量只挪动最少的数据。
- 客户端根据 Cluster map 以及 CRUSH Ruleset 使用 CRUSH 算法查找出某个 PG 所在的 OSD 列表(实际上是 up set)。
-
PG-Object-OSD 的关系以下图所示:
- PG 的建立过程(详细过程请参考 PG 的建立过程):
- MON 节点上有PGMonitotor,它发现有 pool 被建立后,判断该 pool 是否有 PG。若是有PG,则一一判断这些 PG 是否已经存在,若是不存在,则开始下面的建立 PG 的过程。
- 建立过程的开始,设置PG 状态为 Creating,并将它加入待建立PG队列 creating_pgs,等待被处理。
- 开始处理后,使用 CRUSH 算法根据当前的 OSD map 找出来 up/acting set,加入 PG map 中以这个 set 中 OSD 为索引的队列 creating_pgs_by_osd。(看起来只会加入到主OSD的队列中)。
- 队列处理函数将该 OSD 上须要建立的 PG 合并,生成消息MOSDPGCreate,经过消息通道发给 OSD。
- OSD 收到消息字为 MSG_OSD_PG_CREATE 的消息,获得消息中待建立的 PG 信息,判断类型,并获取该PG的其它OSD,加入队列 creating_pgs (彷佛是由主 OSD 负责发起建立次 OSD 上的PG),再建立具体的 PG。
- PG 被建立出来之后,开始 Peering 过程。
- PG 值的肯定:建立 pool 时须要肯定其 PG 的数目,在 pool 被建立后也能够调整该数字,该数目会影响到:
- 数据的持久性:考虑pool 的 size 为 3,代表每一个 PG 会将数据存放在 3 个 OSD 上。当一个 OSD down 了后,必定间隔后将开始 recovery 过程,recovery结束前,有部分 PG 的数据将只有两个副本。这时候和须要被恢复的数据的数量有关系,若是该 OSD 上的 PG 过多,则花的时间将越长,风险将越大。若是此时再有一个 OSD down 了,那么将有一部分 PG 的数据只有一个副本,recovery 过程继续。若是再出现第三个 OSD down 了,那么可能会出现部分数据丢失。可见,每一个 OSD 上的PG数目不宜过大,不然,会下降数据的持久性。这也就要求在添加 OSD 后,PG 的数目在须要的时候也须要相应增长。
-
数据的均匀分布性:CRUSH 算法会伪随机地保证 PG 被选中来存放客户端的数据,它还会尽量地保证全部的 PG 均匀分布在全部的 OSD 上。比方说,有10个OSD,可是只有一个 size 为 3 的 pool,它只有一个 PG,那么10个 OSD 中将只有三个 OSD 被用到。可是 CURSH 算法在计算的时候不会考虑到OSD上已有数据的大小。比方说,100万个4K对象共4G均匀地分布在10个OSD上的1000个PG内,那么每一个 OSD 上大概有400M 数据。再加进来一个400M的对象(假设它不会被分割),那么有三块 OSD 上将有 400M + 400M = 800 M 的数据,而其它七块 OSD 上只有 400M 数据。
- 资源消耗:PG 做为一个逻辑实体,它须要消耗必定的资源,包括内存,CPU 和带宽。太多 PG 的话,则占用资源会过多。
- 清理时间:Ceph 的清理工做是以 PG 为单位进行的。若是一个 PG 内的数据太多,则其清理时间会很长。
那如何肯定一个 Pool 中有多少 PG?Ceph 不会本身计算,而是给出了一些参考原则,让 Ceph 用户本身计算:
- 少于 5 个 OSD, 建议设为 128
- 5 到 10 个 OSD,建议设为 512
- 10 到 50 个 OSD,建议设为 4096
- 50 个 OSD 以上,就须要有更多的权衡来肯定 PG 数目
- 你可使用 pgcalc 工具
- PG 的状态也是不断变化的,其主要状态包括:
- Creating 建立中:PG 正在被建立。
- Peering 对等互联:表示一个过程,该过程当中一个 PG 的全部 OSD 都须要互相通讯来就PG 的对象及其元数据的状态达成一致。处于该状态的PG不能响应IO请求。Peering的过程其实就是pg状态从初始状态而后到active+clean的变化过程。一个 OSD 启动以后,上面的pg开始工做,状态为initial,这时进行比对全部osd上的pglog和pg_info,对pg的全部信息进行同步,选举primary osd和replica osd,peering过程结束,而后把peering的结果交给recovering,由recovering过程进行数据的恢复工做。
- Active 活动的:Peering 过程完成后,PG 的状态就是 active 的。此状态下,在主次OSD 上的PG 数据都是可用的。
- Clean 洁净的:此状态下,主次 OSD 都已经被 peered 了,每一个副本都就绪了。
- Down:PG 掉线了,由于存放其某些关键数据(好比 pglog 和 pginfo,它们也是保存在OSD上)的副本 down 了。
- Degraded 降级的:某个 OSD 被发现中止服务 (down)了后,Ceph MON 将该 OSD 上的全部 PG 的状态设置为 degraded,此时该 OSD 的 peer OSD 会继续提供数据服务。这时会有两种结果:一是它会从新起来(好比重启机器时),须要再通过 peering 过程再到clean 状态,并且 Ceph 会发起 recovery (恢复)过程,使该 OSD 上过时的数据被恢复到最新状态;二是 OSD 的 down 状态持续 300 秒后其状态被设置为 out,Ceph 会选择其它的 OSD 加入 acting set,并启动回填(backfilling)数据到新 OSD 的过程,使 PG 副本数恢复到规定的数目。详情能够参考 PG 的数据恢复过程。
- Recovering 恢复中:一个 OSD down 后,其上面的 PG 的内容的版本会比其它OSD上的 PG 副本的版本落后。在它重启以后(好比重启机器时),Ceph 会启动 recovery 过程来使其数据获得更新。
- Backfilling 回填中:一个新 OSD 加入集群后,Ceph 会尝试级将部分其它 OSD 上的 PG 挪到该新 OSD 上,此过程被称为回填。与 recovery 相比,回填(backfill)是在零数据的状况下作全量拷贝,而恢复(recovery)是在已有数据的基础上作增量恢复。
- Remapped 重映射:每当 PG 的 acting set 改变后,就会发生从旧 acting set 到新 acting set 的数据迁移。此过程结束前,旧 acting set 中的主 OSD 将继续提供服务。一旦该过程结束,Ceph 将使用新 acting set 中的主 OSD 来提供服务。
- Stale 过时的:OSD 每隔 0.5 秒向 MON 报告其状态。若是由于任何缘由,主 OSD 报告状态失败了,或者其它OSD已经报告其主 OSD down 了,Ceph MON 将会将它们的 PG 标记为 stale 状态。
- PG 的全部的状态是一个相似树形的结构,每一个状态可能存在子状态,子状态还可能存在子状态,以下图所示:
(来源)
更多的状态请参考 http://docs.ceph.com/docs/master/rados/operations/pg-states/。实际上 PG 的状态能够是以上这些状态的组合,好比:
[root@ceph-mon ~]# ceph -s
cluster c5476875-2a04-41b7-a4e8-421133c69ac8 health HEALTH_WARN 28 pgs backfill #回填,有新的 OSD 被加入了? 79 pgs degraded #降级,有 OSD down 了? 10 pgs recovering #恢复中 42 pgs recovery_wait #等待恢复 80 pgs stuck unclean #有 80个 PG 一直处于 unclean 状态 27 pgs undersized #GP 的副本数小于pool size recovery 4814/27835 objects degraded (17.295%) recovery 2047/27835 objects misplaced (7.354%)
注意,只有当全部的 PG 都是 active + clean 状态时,集群的状态才是 HEALTH_OK 的。
- 清理 scrubbing:Ceph 以 PG 为单位进行数据清理,以保证数据的完整性,它的做用相似于文件系统的 fsck 工具。
- 有两种比较方式:(1)light scrubbing:比较对象的size和属性,通常天天进行 (2)deep scrubbing:读取对象的数据,比较检验码,通常每周进行。
- Ceph 的 OSD 按期启动 scrub 线程来扫描部分对象,经过与其余副本比对来发现是否一致,若是存在不一致,抛出异常提示用户手动解决。管理员也能够手工发起。
- Scrub 以 PG 为单位,对于每个PG,Ceph 分析该 PG 下全部的对象, 产生一个相似于元数据信息摘要的数据结构,如对象大小,属性等,叫scrubmap, 比较主与副scrubmap,来保证是否是有object 丢失或者不匹配。
-
Scrub 方式分红两种, classic vs. chunky。Scrub 流程须要提取对象的校验信息而后跟其余副本的校验信息对比,这期间被校验对象的数据是不能被修改的,因此 write 请求会被 block. 因为 PG 可能包含成千上万 objects, chunk 每一次的比较只取其中一部分 objects 来比较,这样只 block一小部分object的write请求。这是在ceph的Bobtail(v0.56 Jan 1 2013)引入的feature,称为chunky scrub。Classic scrub 没有引入chunk, 会block全部的write请求。
- 该机制对保证数据的完整性很是重要,可是也会消耗大量的集群资源,block 住一部分对象的写入操做,下降集群的性能,特别是当一个OSD服务器上多个OSD同时进行深度清理的时候。这篇文章 Ceph Deep-Scrubbing Impact Study 说当有三个深度清理线程发生时,性能有明显的降低。
2.2.3 PG 设计带来的一些运维问题
引用自原文 Ceph运维告诉你分布式存储的那些“坑”
(1)扩容粒度
Ceph在实践中,扩容受“容错域”制约,一次只能扩一个“容错域”。容错域就是:副本隔离级别,即同一个replica的数据,放在不一样的磁盘/机器/Rack/机房。默认是机器,一般设为机架。
Ceph扩容须要对PGs进行调整。正由于这个调整,致使Ceph受“容错域”制约。
例如:有一个PG,是3副本,Ceph集群有一个配置是PG要向外提供正常服务,至少有2个完整的副本。而当这个数据pool的容错域是host时,同时扩容2台机器,一些PG就有可能把3副本中的2个都映射到2台新机器上去。而这2个副本都是新副本,都没有完整的最新数据。剩下的一个副本,没法知足老机器至少有完整的2副本的要求,也就不能提供正常读写服务了。这就会致使这个PG里的全部对象,中止对外服务。
那在扩容时,一次只扩容一台机器时,是否是就安全了呢?这样就能保证全部PG都至少在老机器有2个完整的副本了。但是,即便是扩容一台机器,也还要面临扩容时老机器中有硬盘坏掉,致使PG的完整副本又降低为1的极端状况发生。
办法是,在开始规划Ceph集群时,设定好更大层次的“容错域”,好比Rack。 能够是真实的Rack,即便没有也能够是逻辑的Rack。这样扩容时,能够扩一个逻辑“容错域”,就能够打破扩一台机器的限制,扩一整个Rack,至少有好几台机器。
(2)扩容是 crushmap 变化带领的系统抖动
Ceph是根据crushmap去放置PG的物理位置的,假若在扩容进行了一半时,又有硬盘坏掉了,那Ceph的crushmap就会改变,Ceph又会从新进行PG的re-hash,不少PG的位置又会从新计算。若是运气比较差,极可能一台机器的扩容进度被迫进行了好久才回到稳定的状态。
这个crushmap改变致使的Ceph重平衡,不仅仅在扩容时,几乎在任什么时候候,对一个大的存储集群都有些头疼。在创建一个新集群时,硬盘都比较新,所以故障率并不高。可是在运行了2-3年的大存储集群,坏盘真的是一个稀松日常的事情,1000台规模的集群一天坏个2-3块盘很正常。crushmap常常变更,对Ceph内部不稳定,影响真的很大。随之而来,多是总体IO的降低(磁盘IO被反复的rebalance占满),甚至是某些数据暂时不可用。
(3)OSD 增长时候的PG数量调整
假设咱们如今有10台机器,每台一块硬盘一共10块盘,有1024个PG,PG都是单副本,那么每一个盘会存100个PG。此时这个设置很是健康,但当咱们集群扩容到1000台机器,每台硬盘就只放一个PG了,这会致使伪随机形成的不平衡现象放大。所以,admin就要面临调整PG数量,这就带来了问题。调PG,基本也就意味着整个集群会进入一种严重不正常的状态。几乎50%的对象,涉及到调整后的PG都须要从新放置物理位置,这会引发服务质量的严重降低。
(4)盘满形成的系统不可访问
在集群总体使用率不高时,都没有问题。而在使用率达到70%后,就须要管理员介入了。由于方差大的盘,颇有可能会触及95%这条红线。admin开始调低容量太高磁盘的reweight,但若是在这一批磁盘被调整reweight没有结束时,又有一些磁盘被写满了,那管理员就必须被迫在Ceph没有达到稳定状态前,又一次reweight太高的磁盘。 这就致使了crushmap的再一次变动,从而致使Ceph离稳定状态愈来愈远。而此时扩容又不及时的话,更是雪上加霜。并且以前的crushmap的中间状态,也会致使一些PG迁移了一半,这些“不完整的”PG并不会被立刻删除,这给原本就紧张的磁盘空间又加剧了负担。关于reweight 致使的 rebalance,可参考 https://ceph.com/geen-categorie/ceph-osd-reweight/。
一块磁盘满了,Ceph为何就不可用了。Ceph还真的就是这样设计的,由于Ceph无法保证新的对象是否落在空盘而不落在满盘,因此Ceph选择在有盘满了时,就拒绝服务。基本上你们的Ceph集群都是在达到50%使用率时,就要开始准备扩容了。
2.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 (Controlled Replication under Scalable Hashing)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} 便可。
CRUSH map 使用分层结构来组织集群中的全部存储设备:
CRUSH rules 主要有三个做用:
- 指定从CRUSH Map 中的哪一个节点开始查找
- 指定使用那个节点做为故障隔离域
- 指定定位副本的搜索模式(广度优先 or 深度优先)
例子:
rule replicated_ruleset #规则集的命名,建立pool时能够指定rule集 { ruleset 0 #rules集的编号,顺序编便可 type replicated #定义pool类型为replicated(还有esurecode模式) min_size 1 #pool中最小指定的副本数量不能小1 max_size 10 #pool中最大指定的副本数量不能大于10 step take default #定义PG查找副本的入口点 step chooseleaf firstn 0 type host #选叶子节点、深度优先、隔离host step emit #结束 }
PG 选择 OSD 的过程(详情可阅读 PG选择osd的过程(crush 算法)):
- 首先要知道在 rules 中指明从 CRUSH map 中哪一个节点开始查找,入口点默认为 default 也就是 root 节点
- 而后隔离域为 host 节点(也就是同一个host下面不能选择两个子节点)。由 default 到3个host的选择过程,这里由default根据节点的bucket类型选择下一个子节点,由子节点再根据自己的类型继续选择,知道选择到host,而后在host下选择一个osd。
所以,Ceph Admin 能够经过配置 CRUSH map 和 rules 来决定数据的存放方式。详细信息,能够参考 The RADOS object store and Ceph filesystem: Part 2。
(5)MDS Map:包含当前全部 MDS 的状态 (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
MDS 只用于Ceph 文件系统该,与 RDB 和对象存储无关。
3. CRUSH 算法(以RBD为例)
3.1 Ceph client 是如何将数据块放到 OSD 的
Ceph 架构中,Ceph 客户端是直接读或者写存放在 OSD上的 RADOS 对象存储中的对象(data object)的,所以,Ceph 须要走完 (Pool, Object) → (Pool, PG) → OSD set → OSD/Disk 完整的链路,才能让 ceph client 知道目标数据 object的具体位置在哪里:
(1)建立 Pool 和它的 PG。根据上述的计算过程,PG 在 Pool 被建立后就会被 MON 在根据 CRUSH 算法计算出来的 PG 应该所在若干的 OSD 上被建立出来了。也就是说,在客户端写入对象的时候,PG 已经被建立好了,PG 和 OSD 的映射关系已是肯定了的。
(2)Ceph 客户端经过哈希算法计算出存放 object 的 PG 的 ID:
- 客户端输入 pool ID 和 object ID (好比 pool = “liverpool” and object-id = “john”)
- ceph 对 object ID 作哈希
- ceph 对该 hash 值取 PG 总数的模,获得 PG 编号 (好比 58)(第2和第3步基本保证了一个 pool 的全部 PG 将会被均匀地使用)
- ceph 对 pool ID 取 hash (好比 “liverpool” = 4)
- ceph 将 pool ID 和 PG ID 组合在一块儿(好比 4.58)获得 PG 的完整ID。
也就是:PG-id = hash(pool-id). hash(objet-id) % PG-number
(3)客户端经过 CRUSH 算法计算出(或者说查找出) object 应该会被保存到 PG 中哪一个 OSD 上。(注意:这里是说”应该“,而不是”将会“,这是由于 PG 和 OSD 之间的关系是已经肯定了的,那客户端须要作的就是须要知道它所选中的这个 PG 到底将会在哪些 OSD 上建立对象。)。这步骤也叫作 CRUSH 查找。
对 Ceph 客户端来讲,只要它得到了 Cluster map,就可使用 CRUSH 算法计算出某个 object 将要所在的 OSD 的 ID,而后直接与它通讯。
- Ceph client 从 MON 获取最新的 cluster map。
- Ceph client 根据上面的第(2)步计算出该 object 将要在的 PG 的 ID。
- Ceph client 再根据 CRUSH 算法计算出 PG 中目标主和次 OSD 的 ID。
也就是:OSD-ids = CURSH(PG-id, cluster-map, cursh-rules)。
(4)客户端写入数据
在客户端使用 rbd 时通常有两种方法:
- 第一种 是 Kernel rbd。就是建立了rbd设备后,把rbd设备map到内核中,造成一个虚拟的块设备,这时这个块设备同其余通用块设备同样,通常的设备文件为/dev/rbd0,后续直接使用这个块设备文件就能够了,能够把 /dev/rbd0 格式化后 mount 到某个目录,也能够直接做为裸设备使用。这时对rbd设备的操做都经过kernel rbd操做方法进行的。
- 第二种是 librbd 方式。就是建立了rbd设备后,这时可使用librbd、librados库进行访问管理块设备。这种方式不会map到内核,直接调用librbd提供的接口,能够实现对rbd设备的访问和管理,可是不会在客户端产生块设备文件。
应用写入rbd块设备的过程(详细步骤请参考 rbd client 端的数据请求处理):
- 应用调用 librbd 接口或者对linux 内核虚拟块设备写入二进制块。下面以 librbd 为例。
- librbd 对二进制块进行分块,默认块大小为 4M,每一块都有名字,成为一个对象
- librbd 调用 librados 将对象写入 Ceph 集群
- librados 向主 OSD 写入分好块的二进制数据块 (先创建TCP/IP链接,而后发送消息给 OSD,OSD 接收后写入其磁盘)
- 主 OSD 负责同时向一个或者多个次 OSD 写入副本。注意这里是写到日志(Journal)就返回,所以,使用SSD做为Journal的话,能够提升响应速度,作到服务器端对客户端的快速同步返回写结果(ack)。
- 当主次OSD都写入完成后,主 OSD 向客户端返回写入成功。
- 当一段时间(也许得几秒钟)后Journal 中的数据向磁盘写入成功后,Ceph经过事件通知客户端数据写入磁盘成功(commit),此时,客户端能够将写缓存中的数据完全清除掉了。
- 默认地,Ceph 客户端会缓存写入的数据直到收到集群的commit通知。若是此阶段内(在写方法返回到收到commit通知之间)OSD 出故障致使数据写入文件系统失败,Ceph 将会容许客户端重作还没有提交的操做(replay)。所以,PG 有个状态叫 replay:“The placement group is waiting for clients to replay operations after an OSD crashed.”。
也就是,文件系统负责文件处理,librdb 负责块处理,librados 负责对象处理,OSD 负责将数据写入在Journal和磁盘中。
关于 RDB 镜像在存储池中是如何被存放的,请阅读 理解 OpenStack + Ceph (4):Ceph 的基础数据结构 [Pool, Image, Snapshot, Clone]。
(5)以存放一个文件为例,下图(来源)说明了完整的计算过程:
(6)一些说明
几个比例关系:
- 文件 :对象 = 1 : n (由客户端实时计算)
- object :PG = n : 1 (有客户端使用哈希算法计算)
- PG :OSD = m : n (由 MON 根据 CRUSH 算法计算)
CRUSH 算法是至关复杂,快速看看的话能够参考 官方文章,或者直接读代码和做者的论文。几个简单的结论或原则:
- 一个 RBD image(好比虚机的一个镜像文件)会分红几个 data objects 保存在 Ceph 对象存储中。
- 一个 Ceph 集群含有多个 pool (使用 ceph osd pool create 命令建立pool)
- 一个 Pool 包含若干个 PG (在建立 pool 时必须指定 pg_num,在须要的时候对已有的pool的 pg_num 也能够进行修改)
- 一个 PG 能够包含多个对象
- 一个 object 只在一个 PG 中
- 一个 PG 映射到一组 OSD,其中第一个 OSD 是主(primary),其他的是从(secondary)
- 许多 PG 能够映射到某个 OSD,一般一个OSD上会有50到100个PG。
使用 这里 的脚本,能够看出在个人测试环境(pool的副本数为1)中,一共有 6 个pool,7 个 OSD,每一个 pool 中有 192 个PG,每一个 OSD 大概在164个 (192 * 6*1/7)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 采用的是经过计算找到对象应该被保存的 OSD 位置,这比经过常见的查询算法获取位置快得多。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的写请求,这保证了副本的一致性。这也带来一些反作用。相比那些只实现了最终一致性的存储系统好比 Swift,Ceph 只有三份拷贝都写入完成后才算写入完成,这在出现磁盘损坏时会出现写延迟增长。
- 在 OSD 上,在收到数据存放指令后,它会产生2~3个磁盘seek操做:
-
- 把写操做记录到 OSD 的 Journal 文件上(Journal是为了保证写操做的原子性)。
- 把写操做更新到 Object 对应的文件上。
- 把写操做记录到 PG Log 文件上。
3.3 条带化(striping)
在 RADOS 层,Ceph 自己没有条带的概念,由于一个object 是做为一个 文件总体性保存的。可是,RBD 能够控制向一个 object 的写入方式,默认是将一个 object 写满再去写下一个object;还能够经过指定 stripe_unit 和 stripe_count,来将 object 分红若干个条带即 strip。
- order:RADOS Object 的大小为 2^[order] bytes。默认的 oder 为 22,这时候对象大小为4MB。最小 4k,最大 32M,默认 4M.
- stripe_unit:条带(stripe unit)的大小。每一个 [stripe_unit] 的连续字节会被连续地保存到同一个对象中,client 写满 stripe unit 大小的数据后,接着去下一个 object 中写下一个 stripe unit 大小的数据。默认为 1,此时一个 stripe 就是一个 object。
- stripe_count:在分别写入了 [stripe_unit] 个字节到 [stripe_count] 个对象后,ceph 又从新从一个新的对象开始写下一个条带,直到该对象达到了它的最大大小。这时候,ceph 转移到下 [stripe_unit] 字节。默认为 object site。
