Ceph 分布式存储架构解析与工作原理
2019-04-20 23:11 云物互联 阅读(3346) 评论(0) 编辑 收藏 举报目录
文章目录
- 目录
- Ceph 简介
- Ceph 的架构:分布式服务进程
- Ceph 的架构:核心组件
- Ceph RADOS(Reliable, Autonomic, Distributed Object Store)
- LIBRADOS
- Ceph Reliable Block Storage(RBD)
- Ceph RADOS Gateway(RGW)
- Ceph File System(CephFS)
- Ceph 的架构:内部构件
- Ceph 的设计思想
- Ceph 的数据读写原理
- Ceph 的自管理
- 参考文章
Ceph 简介
Ceph 是一个开源项目,它提供软件定义的、统一的存储解决方案 。Ceph 是一个具有高性能、高度可伸缩性、可大规模扩展并且无单点故障的分布式存储系统 。
- Ceph 是软件定义存储解决方案
- Ceph 是统一存储解决方案
- Ceph 是云存储解决方案
Ceph 官方文档:http://docs.ceph.com/docs/mimic/
Ceph 的架构:分布式服务进程
Ceph Monitor(MON)
Ceph 监视器服务进程,简称 MON。负责监控集群的监控状态,所有的集群节点都向 MON 节点报告状态以及每个状态变化的信息。MON 通过收集这些信息并维护一个 Cluster Map 来完成它的任务。Cluster Map 下属包含每个组件的 Map,例如:MON Map、OSD Map、PG Map、CRUSH Map 和 MDS Map。一个典型的 Cepb 集群中通常存在多个 MON 节点,它们之间启用了仲 裁(Quorum)这种分布式决策机制,应用 Paxos 算法保持各节点 Cluster Map 的一致性。所以集群中 MON 节点数应该是一个奇数,开始仲裁操作时至少要保证有一半以上的 MON 处于可用状态,这样才可以防止传统存储系统中常见的脑裂问题。OSD 只有在一些特殊情况(e.g. 添加新的 OSD、OSD 发现自身或他人存在异常)才会上报自己的信息,平常只会简单的发送心跳。MON 在收到这些上报信息时,则会更新 Cluster Map 并加以扩散。
- MON Map:维护着 MON 节点间端到端的信息。包括 Ceph Cluster ID、MON hostname、IP:Port 以及当前 MON Map 的创建版本和最后一次修改信息,帮助确定集群应该遵循哪个版本。
- OSD Map:维护着 Ceph Cluster ID、OSD 数目、状态、权重、主机、以及 Pool 相关的信息(e.g. Pool name、Pool ID、Pool Type、副本数、PG ID),以及存储了 OSD map 创建版本和最后一次修改信息。
- PG Map :维护着 PG 的版本、时间戳、容量比例、最新的 OSD Map 版本、PG ID、对象数、OSD 的 Up Set、OSD 的 Acting Set、Clean 状态等信息。
- CRUSH Map:维护着集群的存储设备信息、故障域层次结构以及在故障域中如何存储数据的规则定义。
- MDS Map:维护着元数据池 ID、MDS 数量、状态、创建时间和当前的 MDS Map 版本等信息。
Ceph Object Storage Device Daemon(OSD)
Ceph 对象存储设备服务进程,简称 OSD。一个 OSD 守护进程与集群中的一个物理磁盘绑定,OSD 负责将数据以 Object 的形式存储在这些物理磁盘中,并在客户端发起数据请求时提供相同的数据。一般来说,物理磁盘的 总数与 Ceph 集群中负责存储用户数据到每个物理磁盘的 OSD 守护进程数是相等的。OSD 同时还负责处理数据复制、数据恢复、数据再平衡以及通过心跳机制监测其它 OSD 状况并报告给 MON。对于任何 R/W 操作,客户端首先向 MON 请求集群的 Map,然后客户端就可以直接与 OSD 进行 I/O 操作。正是因为客户端能够直接与 OSD 进行操作而不需要额外的数据处理层,才使得 Ceph 的数据事务处理速度如此的快。
Ceph Metadata Server(MDS)[可选]
Ceph 元数据服务器服务进程,简称 MDS。只有在启用了 Ceph 文件存储(CephFS)的集群中才需要启用 MDS,它负责跟踪文件层次结构,存储和管理 CephFS 的元数据。MDS 的元数据也是以 Obejct 的形式存储在 OSD 上。除此之外,MDS 提供了一个带智能缓存层的共享型连续文件系统,可以大大减少 OSD 读写操作频率。
Ceph 的架构:核心组件
Ceph RADOS(Reliable, Autonomic, Distributed Object Store)
可靠、自动、分布式对象存储系统,简称 Ceph 存储集群。Ceph 的所有优秀特性都是由 RADOS 提供的,包括分布式对象存储的数据一致性、高可用性、高可靠性、没有单点故障、向我修复以及自我管理等,Ceph 的数据访问方式(e.g. RBD, CephFS, RADOSGW 和 librados)都建立在 RADOS 之上。Cepb 中的一切都以对象的形式存储,而 RADOS 就负责存储这些对象。对于分布式存储的数据一致性,RADOS 通过执行数据复制、故障检测与恢复,还包括数据在集群节点间的迁移与再平衡来实现。
LIBRADOS
是一个 C 语言库,简称 Ceph 基础库。对 RADOS 的功能进行了抽象和封装,并提供北向 API。通过 librados,应用程序可以直接访问 RADOS 原生功能,以此来提高了应用程序的性能、可靠性和效率。因为 RADOS 本质是一个对象存储系统,所以 librados 提供的 API 都是面向对象存储的。librados 原生接口的优点是它直接与应用代码集成,操作非常方便,但也不会主动对上传的数据进行分片。
librados 支持具有异步通信能力的对象存储接口:
- 存储池操作
- 快照
- 读、写对象
- 创建/删除
- 整个 Object 范围或字节范围
- 追加或截断
- 创建/设置/获取/删除 XATTRs
- 创建/设置/获取/删除 K/V 对
- 复合操作和双重 ACK 语义
Ceph Reliable Block Storage(RBD)
Ceph 块存储,简称 RBD,是基于 librados 之上的块存储服务接口。RBD 的驱动程序已经被集成到 Linux 内核(2.6.39 或更高版本)中,也已经被 QEMU/KVM Hypervisor 支持,它们都能够无缝地访问 Ceph 块设备。Linux 内核 RBD(KRBD)通过 librados 映射 Ceph 块设备,然后 RADOS 将 Ceph 块设备的数据对象以分布式的方式存储在集群节点中。
Ceph RADOS Gateway(RGW)
Ceph 对象网关,简称 RGW,是基于 librados 之上的对象存储服务接口。本质是一个代理,可以将 HTTP 请求转交给 RADOS,同时也可以把 RADOS 请求转换为 HTTP 请求,以此来提供 RESTful 对象存储服务接口,并兼容 S3 和 Swift。
Ceph File System(CephFS)
Ceph 文件系统,简称 CephFS。在 RADOS 层之上提供了一个任意大小且兼容 POSIX 的分布式文件系统。CephFS 依赖 MDS 来管理其元数据(文件层次结果),以此将元数据与原始数据分离,MDS 不直接向客户端提供数据,因此可以避免单点故障,有助于降低软件复杂性并提高可靠性。
Ceph 的架构:内部构件
Ceph Client
Ceph 的客户端是指使用 Ceph 存储服务的一切实体对象。可能是一个 Client 软件、一台主机/虚拟机或者是一个 App。客户端会根据 Ceph 提供的不同接口类型(服务接口、原生接口、C 库)来连接并 Ceph 存储服务器并使用相应的 Ceph 存储服务。
客户端的数据条带化(分片)
当用户使用 RBD、RGW、CephFS 类型客户端接口来储存数据时,会经历一个透明的、将数据转化为 RADOS 统一处理对象的过程,这个过程就称之为数据条带化或分片处理。
熟悉存储系统的你不会对条带化感到陌生,它是一种提升存储性能和吞吐能力的手段,通过将有序的数据分割成多个区段并分别储存到多个存储设备上,最常见的条带化就是 RAID0。而 Ceph 的条带化处理就类似于 RAID0,如果想发挥 Ceph 的并行 I/O 处理能力,就应该充分利用客户端的条带化功能。需要注意的是,librados 原生接口并不具有条带化功能,比如:使用 librados 接口上传 1G 的文件,那么落到存储磁盘上的就是 1G 大小的文件。存储集群中的 Objects 也同样不具备条带化功能,实际上是由上述三种类型的客户端将数据条带化之后再储存到集群 Objects 之中的。
条带化处理过程:
- 将数据切分为条带单元块。
- 将条带单元块写入到 Object 中,直到 Object 达到最大容量(默认为 4 M)。
- 再为额外的条带单元块创建新的 Object 并继续写入数据。
- 循环上述步骤知道数据完全写入。
假设 Object 存储上限为 4M,每一个条带单元块占 1M。此时我们储存一个 8M 大小的文件,那么前 4M 就储存在 Object0 中,后 4M 就创建 Object1 来继续储存。
随着储存文件 Size 的增加,可以通过将客户端数据条带化分割储存到多个 Objects 中,同时由于 Object 映射到不同的 PG 上进而会映射到不同的 OSD 上,这样就能够充分利用每个 OSD 对应的物理磁盘设备的 IO 性能,以此实现每个并行的写操作都以最大化的速率进行。随着条带数的增加对写性能的提升也是相当可观的。如下图,数据被分割储存到两个 Object Set 中,条带单元块储存的顺序为 stripe unit 0~31。
Ceph 有 3 个重要参数会对条带化产生影响:
- order:表示 Object Size。例如:order=22 即为 2**22,即 4M 大小。Object 的大小应该足够大以便与条带单元块相匹配,而且 Object 的大小应该是条带单元块大小的倍数。RedHat 建议的 Object 大小是 16MB。
- stripe_unit:表示条带单元块宽度。客户端将写入 Object 的数据切分为宽度相同的条带单元块(最后走一块的宽度未必一致)。条带宽度应该是 Object 大小的一个分数,比如:Object Size 为 4M,单元块为 1M,那么一个 Object 就能包含 4 个单元块。以便充分利用 Object 的空间。
- stripe_count:表示条带数量。根据条带数量,客户端将一批条带单元块写入到 Object Set 中。
NOTE:由于客户端会指定单个 Pool 进行写入,所以条带化到 Objects 中的所有数据都会被映射在同一个 Pool 包含的 PGs 内。
客户端对 Object 的监视和通知
客户端可以向 Object 注册持久的监听,并保持与 Primary OSD 的会话,这就是客户端对 Object 的监视与通知性。该特性使得监听同一个 Object 的客户端之间可以使用 Object 来作为通信的渠道。
客户端的独占锁(exclusive-lock)
客户端的独占锁特性提供一种可以对 RBD 块设备进行 “排它性的” 锁定,这有助于解决多个客户端对同一个 RBD 块设备进行操作时,多个客户端尝试同时向同一个 Object 写入数据导致的冲突。独占锁特性需要依赖客户端对 Object 的监视与通知特性,在数据写入时,如果一个客户端首先在 Object 上放置了独占锁,则能够被其它客户端在写入数据前检查到,并放弃数据写入。设置了这一特性的话,同一时刻只有一个客户端能够对 RBD 块设备进行修改,常被应用到快照创建、快照删除这种改变块设备内部结构的操作。强制的独占锁功能特性默认是不开启的,需要在创建镜象时显式的通过选项 --image-features
启用。
rbd -p mypool create myimage --size 102400 --image-features 5
# 5 = 4 +1
# 1:启用分层特性
# 4:启用独占锁特性
客户端的 Object 映射索引
客户端可以在数据写入到 RBD Image 时会跟踪这些 Object(建立映射索引),从而让客户端可以在数据读写时就能知道相应的 Objects 是否存在,省去了遍历 OSD 以确定 Objects 是否存在的开销。Object 的映射索引保存在 librbd 客户端的内存中。该特性对一些 RBD Image 操作比较有利:
- 缩容:仅删除存在的尾部 Objects。
- 导出操作:仅导出存在的 Objects。
- 复制操作:仅存在的 Objects。
- 扁平化:仅将存在的 Parent Objects 拷贝到 Clone Image。
- 删除:仅删除存在的 Objects。
- 读取:仅读取存在的 Objects。
Object 映射索引特性默认也是不开启的,同样需要在创建镜象时显式的通过选项 --image-features
启用。
rbd -p mypool create myimage –size 102400 –image-features 13
# 13 = 1 + 4 + 8
# 1:启用分层特性
# 4:启用独占锁特性
# 8:启用 Object 映射索引
CRUSH(Controlled Replication Under Scalable Hashing)
可控的、可扩展的、分布式的副本数据放置算法。本质是一种伪随机数据分布算法,类似一致性哈希,是 Ceph 的智能数据分发机制,管理着 PG 在整个 OSD 集群的分布。CRUSH 的目的很明确, 就是一个 PG 如何与 OSD 建立关系,它是 Ceph 皇冠上的宝石。
动态计算元数据
传统物理存储设备的存储机制都涉及存储原始数据及其元数据,元数据存储了原始数据存储在存储节点和磁盘阵列的地址信息。每一次有新数据写入存储系统时,元数据最先更新,更新的内容就是原始数据将会存放的物理地址,在此之后才是原始数据的写入。Ceph 则抛弃了这种存储机制,使用 CRUSH 算法动态计算用于存储 Object 的 PG,同时也用于计算出 PG 的 OSD Acting Set(Acting Set 即为活跃的 OSD 集合,集合中首编号的 OSD 即为 Primary OSD)。CRUSH 按需计算元数据,而不是存储元数据,所以 Ceph 消除了传统的元数据存储方法中的所有限制,具有更好的容量、高可用性和可扩展性。
基于 CRUSH bucke 的故障域/性能域划分
除此之外,CRUSH 还具有独特的基础设施感知能力,可以识别整个基础设施中的物理组件拓扑(故障域和性能域),包括磁盘、节点、机架(Rack)、行(Row)、开关 、电源电路、房间、数据中心以及存储介质类型等多种 CRUSH bucke 类型,bucket 表明了设备的具体物理位置。这种感知能力使得 CRUSH 可以让客户端进行跨故障域写入数据,以此来保证数据的安全性。CRUSH bucke 包含在 CRUSH Map 中,CRUSH Map 还保存了一系列可定义的规则(CRUSH Rules),告诉 CRUSH 如何为不同的 Pool 复制数据。CRUSH 使得 Ceph 能够自我管理和自我疗愈。当故障区域中的组件故障时,CRUSH 能够感知哪个组件故障了,并自动执行相应的数据迁移、恢复等动作。使用 CRUSH,我们能够设计一个没有单点故障的高度可靠的存储基础设施。CRUSH 也可使客户端能够将数据写入特定类型的存储介质中,例如:SSD、SATA。CRUSH Rules 决定了故障域以及性能域的范围。
Object
对象。是 Ceph 的最小存储单元,每个 Object 都包含了在集群范围内唯一的标识、二进制数据、以及由一组键值对组成的元数据。绑定在一起的原始数据与元数据,并且具有 RADOS 全局唯一的标识符 OID。无论上层应用的是 RBD、CephFS 还是 RADOSGW,最终都会以 Object 的方式存储在 OSD 上。当 RADOS 接收到来向客户端的数据写请求时,它将接收到的数据转换为 Object,然后 OSD 守护进程将数据写入 OSD 文件系统上的一个文件中。
Placement Group(PG)
归置组,简称 PG。PG 是一组 Objects 的逻辑集合。一个 Ceph 存储池可能会存储数百万或更多的数据对象。因为 Ceph 必须处理数据持久化(副本或纠删码数据块)、清理校验、复制、再平衡以及数据恢复,因此以 Object 作为管理对象就会出现扩展性和性能上的瓶颈。Ceph 通过引入 PG 层来解决这个问题。CRUSH 分配每个 Object 到指定的 PG 中,每个 PG 再映射到一组 OSD 中。PG 是保障 Ceph 可伸缩性和性能必不可少的部分。当数据要写入 Ceph 时,首先会将将数据分解成一组 Objects,然后根据 Object 名称、复制级别和系统中的总 PG 数等信息执行散列操作并生成 PG ID,最后依据 PG ID 将 Objects 数据分散到各个 PG 中。没有 PG,在成千上万个 OSD 上管理和跟踪数以百万计的 Objects 的复制和传播是相当困难的。将包含大量 Objects 的 PG 作为管理和复制的对象,可以有效缩减计算资源的损耗。每个 PG 都会消耗一定的计算资源(CPU、RAM),所以存储管理员应该精心计算集群中的 PG 数量。
计算 PG 数
上文中我们提到过,PG 数会在一定程度上影响存储性能,一个 OSD 的 PG 数可以被动态修改,但建议在部署规划时期就能够对 PG 数有一定的把握。常见的,有以下几种 PG 数计算方式:
- 计算集群 PG 总数
PG 总数 = (OSD 总数 x 100) / 最大副本数
结果取最接近 2 的 N 次幂,比如:集群有 160 个 OSD 且副本数为 3,那么根据公式计算得到的 PG 总数是 5333.3,再舍入这个值到最接近的 2 的 N 次幕,最终结果为 8192 个 PG。
- 计算 Pool 下属的 PG 总数
PG 总数= ((OSD 总数 x 100) / 最大副本数) / 池数
结果同样取最接近 2 的 N 次幂。
PG 与 PGP
PG - Placement Group
PGP - Placement Group for Placement purpose
pg_num - number of placement groups mapped to an OSD
When pgnum is increased for any pool, every PG of this pool splits into half, but they all remain mapped to their parent OSD.Until this time, Ceph does not start rebalancing. Now, when you increase the pgpnum value for the same pool, PGs start to migrate from the parent to some other OSD, and cluster rebalancing starts. This is how PGP plays an important role.
顾名思义,PGP 是为了实现定位而设置的 PG。PGP 的数量应该与 PG 总数保持一致。对于一个 Pool 而言,当你增加了 PG 的数量,这个 Pool 的 PG 同时还应该修改 PGP 的数量,让两者保持一致,这样集群才能够触发再平衡动作。
- pg_num 的增加会使旧 PGs 中的 Objects 再平衡到新建 PG 中,旧 PGs 之间不会发现 Object 迁移并且依旧保存原有的 OSD 映射关系。
- pgp_num 的增加会使新 PGs 在平衡到 OSDs 中,而旧 PGs 依旧保存原有的 OSD 映射关系。
- PGP 决定了 PG 的分布。
PG Peering 操作、Acting Set 与 Up Set
OSD 守护进程会为每个 PG 的副本执行 Peering 操作,确保 PG 对应的主从 OSDs 之间的 PG 副本是一致的。这些主从 OSDs 以 Acting Set 的形式组织起来,Acting Set 的第一个元素就是 Primary OSD,保存着 PG 的主副本。其余为 Replica OSDs 中保存的是 PG 的第二/第三副本(假设副本数为 3)。Primary OSD 负责 Replica OSDs 之间的 PG 的 Peering 操作。当 Primary OSD 状态为 down 时,首先会从 Up Set 中移除,然后由第二 OSD 晋升为 Primary OSD,故障 OSD 中的 PG 会以同步到其他 OSD 的方式还原,并将新的 OSD 加入到 Actin Set 和 Up Set 中,以确保整个集群的高可用性。
Pool
存储池。是一个面向管理员的、用来隔离 PGs 和 Objects 的逻辑分区。简单来说,Pool 就是一个管理员自定义的命名空间,不同的 Pool 可以具有完全不同的数据处理方式,例如:Replica Szie(副本数)、PG Num、CRUSH Rules、快照、所属者及其授权等。可以为特定类型的数据创建存储池,比如:块设备、对象网关,亦或仅仅是为了多用户隔离而创建 Pool。
管理员可以为 Pool 设置所有者和访问权限,还支持快照功能。当用户通过 API 来存储数据时,需要指定 Objects 要存储到哪一个 Pool 中。管理员可以对不同的 Pool 设置相关的优化策略,比如 PG 副本数、数据清洗次数、数据块及 Object 的 Size 等等。Pool 提供了一个有组织的存储管理方式。每个 Pool 都会交叉分布在集群节点的 OSDs 上,这样能够提供足够的弹性。除了 PG 副本数,也可以通过指定纠删码的规则集来提供与副本数同等级别的可靠性,而且只消耗更少的空间。
当把数据写人到一个 Pool 时,首先会在 CRUSH Map 找到与 Pool 对应的规则集,这个规则集描述了 Pool 的副本数信息。在部署 Ceph 集群时会创建一些默认的存储池(e.g. data、metadata、rbd)。
Pool 也存在容量的概念,但 Pool Size 只在限制最大容量或者 QoS 的场景中有用,并不是真实的容量,因为 Pools 与 OSDs 之间的隐射是交叉的,Pools 的总容量大于 Ceph Cluster GLOBAL Size。所以 Pools 的总容量是没有意义的。
Ceph 的设计思想
此处不妨小结一下 Ceph 每个内部构件的设计含义及其存在的意义。以 Object 作为 Ceph 的最小存储单元充分地展示了 Ceph 宏大的野心 —— 要成为新一代(或者成为云时代)的存储架构。众所周知,元数据是关于原始数据的信息,它决定了原始数据将往哪里存储,从哪里读取。传统的存储系统通过维护一张集中式的查找表来跟踪它们的元数据,这直接导致了传统存储系统查找性能低(容量有限)、单点故障(可靠性低)、数据一致性(可扩展性低)等问题。为了实现这一目标,Ceph 就必须打破传统存储系统的桎梏,采用更加智能的元数据管理方法,以离散的方式来存储原始数据及其元数据,再以动态计算的方式来定位数据的所在,这就是 Object + CRUSH。在此基础之上,RADOS 的引入就是为了解决分布式运行环境中的数据一致性、高可用性和自我管理等问题;PG 的引入就是为了缩小 RADOS 的管理对象以及 Objects 的遍历寻址空间,进一步提升性能和降低内部实现复杂度,在进行数据迁移时,也是以 PG 作为迁移单位。有了 PG 之后 Ceph 会尽量避免直接操作 Objects;Pool 的引入就是为了抽象出更加友好的资源管理与操作模型,例如:PG 的数量以及 PG 的副本数都是以 Pool 为单位设定的,提供了良好的用户操作体验。在如此可靠、自动、分布式的对象存储系统之上再构建 librados、RBD、RGW、CephFS 等多种更便于应用或客户端使用的上层接口类型,真正实现了下层抽象统一、上层异构兼容的统一存储解决方案。回头再看,Ceph 成功的原因很简单,其 SDS(软件定义存储)基因所带来的 “可编程性” 不正是云时代所渴望的吗?
Ceph 的数据读写原理
数据储存的三个映射过程
Ceph 存储系统中,数据存储分三个映射过程:
Step 1. 将要存储的 File 数据映射(切分)为 RADOS 可以处理的 Objects(1:N):(ino, ono) -> oid
- ino:File 的元数据,File 的唯一标识
- ono:File 切分产生的某个 Object 时的分片编号,默认以 4M 大小切分
- oid:Object 唯一标识,存储了 Obejcts 与 File 的从属关系
Step 2. 将 Objects 映射到 PG(N:1):hash(oid) & mask -> pgid
,Ceph 指定一个静态 HASH 函数将 oid 映射成一个近似均匀分布的伪随机值,然后将这个值与 mask 按位相与,得到 PG ID。
- mask:PG 总数 m - 1,m 为 2 的整数幂
Step 3. 将 PG 隐射到 OSDs(N:M):CRUSH(pgid) -> (osd1,osd2,osd3)
,传入 pgid 到 CRUSH 算法计算得到一组 OSD 数组(长度与 PG 副本数相同)。
File 数据最终分散保存在这些 OSDs 中。
调度算法的伪代码:
locator = object_name
obj_hash = hash(locator)
pg = obj_hash % num_pg
osds_for_pg = crush(pg) # returns a list of osds
primary = osds_for_pg[0]
replicas = osds_for_pg[1:]
客户端的 I/O 定位
当客户端读写数据时,首先需要与 MON 节点建立连接并下载最新的 Cluster Map。客户端读取 Cluster Map 中的 CRUSH Map(包含 CRUSH Rules、CRUSH bucke)和 OSD Map(包含所有 Pool 的状态和所有 OSDs 的状态),Crush Rules 就是数据映射的策略,决定了每个 Object 有多少个副本,这些副本应该如何存储,当把数据写入一个 Pool 时,可以通过 Pool name 匹配到一个 CRUSH Rules。客户端通过这些信息在本地执行 CRUSH 算法获得数据读写的 Primary OSD 的 IP:Port,然后就可以与 OSD 节点直接建立连接来传输数据了。CRUSH 动态计算的动作发生在客户端,不需要有集中式的主节点用于寻址。客户端分摊了这部分工作,进一步减轻了服务端的工作负载。
- 客户端输入 Pool name 以及 Object ID(例如:Pool=“pool1”, Object=”obj1”)。
- CRUSH 获取 Object ID 后对其进行 HASH 编码。
- CRUSH 根据上一步的 HASH 编码与 PG 总数求模后得到 PG ID。(e.g. HASH 编码后为 186,而 PG 总数为 128,则求模得 58,所以这个 Object 会存储在 PG_58 中)。
- CRUSH 计算对应 PG ID 的主 OSD。
- 客户端根据 Pool name 称得到 Pool ID(e.g. pool1=4)。
- 客户端将 PG ID 与 Pool ID拼接(e.g. 4.58)
- 客户端直接与 Activtin Set 集合中的主 OSD 通信,来执行对象的 IO 操作。
主从 OSD 的副本 I/O
首先我们需要了解到 Ceph 的读写操作采用了主从模型,当客户端读写数据时,都只能向 Object 对应的 Primary OSD 发起请求。同时 Ceph 还是一个 强一致性 分布式存储,也就是说保证分布式数据的一致性同步过程并非异步操作。只有当所有的主从 OSD 都写入数据之后才算是一次成功的写入。客户端使用 CRUSH 算法计算 Object 映射的 PG ID 以及 Acting Set 列表。Acting Set 的首元素就是 Primary OSD,剩下的为 Replica OSD。
提升性能的日志 I/O
强一致性的数据同步方式在带来了高度的数据一致性的同时也存在一些缺点,比如:写性能低。为了提高写性能,Ceph 采用了一种普遍的应对方式 —— 日志缓存的机制。
当有突发的写入峰值时,Ceph 会先把一些零散的、随机的 IO 请求保存到缓存中进行合并,然后再统一向内核发起 IO 请求。这种方式有效提升了执行效率,但一旦 OSD 节点崩溃,缓存中的数据也会随之丢失。所以,Ceph OSD 包含两个不同的部分:OSD 日志部分(journal 文件)和 OSD 数据部分。每一个写操作都包含两个步骤:首先将 Object 写入日志部分,然后再将同一个 Object 从日志部分写人数据部分。当 OSD 崩溃后重新启动时,会自动尝试从 journal 恢复因崩溃而丢失的缓存数据。因此,journal 的 IO 是非常密集的,而且由于一个数据要 IO 两次,很大程度上也损耗了硬件的性能。从性能的角度来看,在生产环节中建以使用 SSD 存储来日志。使用 SSD 可以通过减少访问时间和读取延迟实现吞吐量的显著提升。
如果使用 SSD 来存储日志,我们可以在每一个物理 SSD 磁盘上创建一个逻辑分区来作为日志分区,这样每个 SSD 的日志分区就可以映射到一个 OSD 的数据分区,journal 文件的大小默认为 5G。在这种部署方案中,切记不要在同一个 SSD 上存储过多的日志以避免超出它的上限而影响到整体性能。建议在每一个 SSD 磁盘上不应该存储超过 4 个 OSD 的日志。但需要注意的是,使用单一 SSD 磁盘来存储多个日志的另一个坏处是容易发生单点故障,所以也建议使用 RAID1 来避免这种情况。
数据写入 OSD 的 UML 流程
- Client 创建 Cluster Handler。
- Client 读取配置文件。
- Client 连接上 MON,获取 Cluster Map 的副本。
- 根据 CRUSH Map 记录的故障域以及数据分发规则信息,找到 Client 读写 I/O 的目的 Primary OSD(主 OSD)。
- 根据 OSD Map 记录的 Pool 副本数(这里假设副本数为 3),首先将数据写入 Primary OSD。Primary OSD 再复制相同的数据到两个 Replica OSD 中,并等待它们确认写入完成。
- Replica OSD 完成数据写人,它们就会发送一个应答信号给 Primary OSD。
- 最后,Primary OSD 再返问一个应答信号给客户端,以确认完成整个写人操作 。
数据写入新 Primary OSD 的 UML 流程
- Client 连接 MON,获取 Cluster Map 的副本。
- 新 Primary OSD 由于不存在任何 PG,所以主动上报 MON 暂时由 Primary OSD 2 升级为临时 Primary OSD。
- 临时 Primary OSD 把 PG 全量同步给新 Primary OSD。
- Client I/O 直接连接临时 Primary OSD。
- 临时 Primary OSD 接收到数据后同时复制另外两个 OSD,并等待它们确认写入完成。
- 三个 OSD 都同步写入数据后返问一个应答信号给客户端,以确认完成整个写人操作 。
- 如果临时 Primary OSD 与新 Primary OSD 的全量数据完成,则临时 Primary OSD 再次降级为 Replica OSD。
Ceph 的自管理
OSD 的心跳机制
心跳机制是最常见的故障检测机制,Ceph 使用心跳机制来检测 OSD 是否正常运行,以便及时发现故障节点并进入相应的故障处理流程。OSD 的状态(Up/Down)反映了其健康与否,OSD 加入到集群后会定期的上报心跳到 MON,但假如 OSD 已然故障的话就不会在继续上报自己为 Down 的状态了,所以具有关联关系的 OSDs 之间也会互相评判对方的状态,如果发现对方 Down 掉了,则会协助上报到 MON。当然了,MON 也会定期的 Ping 这些 OSD 以此来确定它们的运行情况。Ceph 通过伙伴 OSD 汇报失效节点以及 MON 统计来自 OSD 的心跳检测两种方式判定 OSD 节点是否失效。
OSD 服务进程会监听 Public、Cluster、Front 和 Back 四个端口:
- Public 端口:监听来自 MON 和 Client 的连接。
- Cluster 端口:监听来自 OSD Peer 的连接。
- Front 端口:监听供客户端连接到集群所使用的网卡。
- Back 端口:监听供集群内部使用的网卡。
OSD 心跳机制的特征:
- 及时:伙伴 OSD 可以在秒级发现失效节点汇报至 MON,并在几分钟内由 MON 将失效 OSD 下线。
- 适当的压力:由于有伙伴 OSD 协同汇报,MON 与 OSD 之间的心跳检测更像是一种保险措施,因此 OSD 向 MON 发送心跳的间隔可以长达 600 秒,MON 的检测阈值也可以长达 900 秒。Ceph 将故障检测过程中中心节点的压力分散到所有的 OSD 上,以此提高了中心节点 MON 的可靠性,进而提高整个集群的可扩展性。
- 容忍网络抖动:MON 收到 OSD 对其伙伴 OSD 的汇报后,并没有马上将目标 OSD 下线,而是周期性的等待一下几个条件:
- 目标 OSD 的失效时间大于通过固定量 osd_heartbeat_grace 和历史网络条件动态确定的阈值。
- 来自不同主机的汇报达到 mon_osd_min_down_reporters。
- 满足前两个条件前失效汇报没有被源 OSD 取消。
- 扩散:作为中心节点的 MON 并没有在更新 OSD Map 后尝试广播通知所有的 OSD 和 Client,而是惰性的等待 OSD 和 Client 来获取。以此来减少 MON 的压力并简化交互逻辑。
OSD 之间相互的心跳检测
- 同一个 PG 内 OSDs 之间互相发送 PING/PONG 信息。
- 每隔 6s 检测一次。
- 20s 没有检测到心跳回复,将对方加入 Failure 队列。
OSD 与 MON 间的心跳机制
OSD 扩容时的 PG 迁移与再平衡
将一个新的 OSD 添加到 Ceph 集群时,Cluster Map 也会更新,这一变化会改变 CRUSH 计算时输入的参数,也就间接的改变了 Object 放置的位置。CRUSH 算法是伪随机的,但会均匀的放置数据,所以 CRUSH 会开始执行再平衡操作,并且会让尽量少的数据发生迁移。一般迁移的数据量是集群总数据量与 OSD 数量的比值,例如:在有 50 个 OSD 的集群中,当新增加一个 OSD 时,就只会有 1/50(2%)的数据会被迁移。并且所有旧 OSD 会并行地移动数据,使其能够迅速的完成。在生产环境中对于利用率高(刷写量大速快)的 Ceph 集群,建议先将新的 OSD 权重设为 0,再逐渐增加权重。通过这种方式,能够减少再平衡操作对 Ceph 集群性能有较大的影响。再平衡机制保证了所有磁盘能能够被均匀的使用,以此提升集群性能和保持集群健康。
扩容前:
扩容后:
上图可见,虚线的 PGs 会自动迁移到新的 OSD 上。
数据擦除(Clean)
数据擦除是 Ceph 维护数据一致性以及整洁性的手段。OSD 守护进程会完成 PG 内的 Object 清理工作,它会比较副本间 PGs 内的 Object 元数据信息,捕获异常或文件系统的一些错误,这类 Clean 是 Day 级别的调度策略。OSD 还只会更深层次的比较,对数据本身进行按位比较,这种深层次的比较可以发现驱动盘上坏的扇区,一般是 Week 级别的调度策略。
参考文章
https://mp.weixin.qq.com/s/HbeN6W4SThqdm9euvmMeEg
https://mp.weixin.qq.com/s/g8dP3wW-J2qU_A6SGornXg
https://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&mid=2650634798&idx=1&sn=8a1933b6e3b6e3f9f6a841173d6790fc&chksm=bef904a8898e8dbe8b2ca5000a55c5c6b41bcd80671a20a2ee188e3a65e96dac33d6d228d7fe&scene=21#wechat_redirect
https://mp.weixin.qq.com/s/kb2YByQqNr4MDs-5qh6QdQ
https://mp.weixin.qq.com/s/Yyc0o5QPexjOPm7-PnVyXA