1. 简介
在传统分布式存储架构中,存储节点往往仅作为被动查询对象来使用,随着存储规模的增加,数据一致性的管理会出现很多问题。
而新型的存储架构倾向于将基本的块分配决策和安全保证等操作交给存储节点来做,然后通过提倡客户端和存储节点直接交互来简化数据布局并减小io瓶颈。
RADOS就是这样一个可用于PB级规模数据存储集群的可伸缩的、可靠的对象存储服务。它包含两类节点:存储节点、管理节点。它通过利用存储设备的智能性,将诸如一致性数据访问、冗余存储、错误检测、错误恢复分布到包含了上千存储节点的集群中,而不是仅仅依靠少数管理节点来处理。
RADOS中的存储节点被称为OSD(object storage device),它可以仅由很普通的组件来构成,只需要包含CPU、网卡、本地缓存和一个磁盘或者RAID,并将传统的块存储方式替换成面向对象的存储。
在PB级的存储规模下,存储系统一定是动态的:系统会随着新设备的部署和旧设备的淘汰而增长或收缩,系统内的设备会持续地崩溃和恢复,大量的数据被创建或者删除。RADOS通过 cluster map来实现这些,cluster map会被复制到集群中的所有部分(存储节点、控制节点,甚至是客户端),并且通过怠惰地传播小增量更新而更新。cluster map中存储了整个集群的数据的分布以及成员。
通过在每个存储节点存储完整的cluster map,存储设备可以表现的半自动化,通过peer-to-peer的方式(比如定义协议)来进行数据备份、更新,错误检测、数据迁移等等操作。这无疑减轻了占少数的monitor cluster(管理节点组成的集群)的负担。
2. 设计
一个RADOS系统包含大量的OSDs 和 很少的用于管理OSD集群成员的monitors。OSD的组成如简介所说。而monitor是一些独立的进程,以及少量的本地存储,monitor之间通过一致性算法保证数据的一致性。
2.1 Cluster Map
存储节点集群通过monitor集群操作cluster map来实现成员的管理。cluster map 描述了哪些OSD被包含进存储集群以及所有数据在存储集群中的分布。
cluster map不仅存储在monitor节点,它被复制到集群中的每一个存储节点,以及和集群交互的client。
当因为一些原因,比如设备崩溃、数据迁移等,cluster map的内容需要改变时,cluster map的版本号被增加,map的版本号可以使通信的双方确认自己的map是否是最新的,版本旧的一方会先将map更新成对方的map,然后才会进行后续操作。
2.2 Data Placement
首先,如下图,总体说下RADOS的存储层次,RADOS中基本的存储单位是对象,一般为2MB或4MB,当一个文件要存入RADOS时,首先会被切分成大小固定的对象(最后一个对象大小可能不同),然后将对象分配到一个PG(Placement Group)中,然后PG会复制几份,伪随机地派给不同的存储节点。当新的存储节点被加入集群,会在已有数据中随机抽取一部分数据迁移到新节点。这种概率平衡的分布方式可以保证设备在潜在的高负载下正常工作。更重要的是,数据的分布过程仅需要做几次随机映射,不需要大型的集中式分配表。
对于每个层次的详细说明:
- File—— 用户需要存储或者访问的文件。
2.Object—— RADOS的基本存储单元。Object与上面提到的file的区别是,object的最大size由RADOS限定(通常为2MB或4MB),以便实现底层存储的组织管理。因此,当上层应用向RADOS存入size很大的file时,需要将file切分成统一大小的一系列object(最后一个的大小可以不同)进行存储。
PG(Placement Group)—— 对object的存储进行组织和位置映射。具体而言,一个PG负责组织若干个object(可以为数千个甚至更多),但一个object只能被映射到一个PG中,即,PG和object之间是“一对多”映射关系。同时,一个PG会被映射到n个OSD上,而每个OSD上都会承载大量的PG,即,PG和OSD之间是“多对多”映射关系。在实践当中,n至少为2(n代表冗余的份数),如果用于生产环境,则至少为3。一个OSD上的PG则可达到数百个。事实上,PG数量的设置牵扯到数据分布的均匀性问题。
OSD—— 即object storage device,前文已经详细介绍,此处不再展开。唯一需要说明的是,OSD的数量事实上也关系到系统的数据分布均匀性,因此其数量不应太少。在实践当中,至少也应该是数十上百个的量级才有助于Ceph系统的设计发挥其应有的优势。
各层次之间的映射关系:
file -> object
object的最大size是由RADOS配置的,当用户要存储一个file,需要将file切分成几个object。object -> PG
每个object都会被映射到一个PG中,然后以PG为单位进行备份以及进一步映射到具体的OSD上。
至于具体的映射方式,直接引用论文吧,以防歧义(才不是我懒)。
Each object’s PG is determined by a hash of the object name o, the desired level of replication r, and a bit mask m that controls the total number of placement groups in the system. That is, pgid = (r, hash(o)&m), where & is a bit-wise AND and the mask m = 2k −1, constraining the number of PGs by a power of two. As the cluster scales, it is periodically necessary to adjust the total number of placement groups by changing m; this is done gradually to throttle the resulting migration of PGs between devices.
- PG -> OSD
根据用户设置的冗余存储的个数r,PG会最终存储到r个OSD上,这个映射是通过一种伪随机的映射算法 CRUSH 来实现的,这个算法的特点是可以进行配置。具体请看Ceph的另一篇论文。关于如何将数据拷贝到多个OSD上,RADOS有三种方式,具体查阅论文吧,一般的数据一致性思想。
集群维护
前面的介绍中已经提到,由若干个monitor共同负责整个RADOS集群中所有OSD状态的发现与记录,并且共同形成cluster map的master版本,然后扩散至全体OSD以及client。OSD使用cluster map进行数据的维护,而client使用cluster map进行数据的寻址。
monitor并不主动轮询各个OSD的当前状态。相反,OSD需要向monitor上报状态信息。常见的上报有两种情况:一是新的OSD被加入集群,二是某个OSD发现自身或者其他OSD发生异常。在收到这些上报信息后,monitor将更新cluster map信息并加以扩散。其细节将在下文中加以介绍。
Cluster map的实际内容包括:
(1) Epoch,即版本号。cluster map的epoch是一个单调递增序列。epoch越大,则cluster map版本越新。因此,持有不同版本cluster map的OSD或client可以简单地通过比较epoch决定应该遵从谁手中的版本。而monitor手中必定有epoch最大、版本最新的cluster map。当任意两方在通信时发现彼此epoch值不同时,将默认先将cluster map同步至高版本一方的状态,再进行后续操作。
(2)各个OSD的网络地址。
(3)各个OSD的状态。OSD状态的描述分为两个维度:up或者down(表明OSD是否正常工作),in或者out(表明OSD是否在至少一个PG中)。因此,对于任意一个OSD,共有四种可能的状态:
up且in:说明该OSD正常运行,且已经承载至少一个PG的数据。这是一个OSD的标准工作状态;
up且out:说明该OSD正常运行,但并未承载任何PG,其中也没有数据。一个新的OSD刚刚被加入Ceph集群后,便会处于这一状态。而一个出现故障的OSD被修复后,重新加入Ceph集群时,也是处于这一状态;
down且in:说明该OSD发生异常,但仍然承载着至少一个PG,其中仍然存储着数据。这种状态下的OSD刚刚被发现存在异常,可能仍能恢复正常,也可能会彻底无法工作;
down且out:说明该OSD已经彻底发生故障,且已经不再承载任何PG。
(4)CRUSH算法配置参数。表明了Ceph集群的物理层级关系(cluster hierarchy),位置映射规则(placement rules)。
根据cluster map的定义可以看出,其版本变化通常只会由(3)和(4)两项信息的变化触发。而这两者相比,(3)发生变化的概率更高一些。这可以通过下面对OSD工作状态变化过程的介绍加以反映。
一个新的OSD上线后,首先根据配置信息与monitor通信。Monitor将其加入cluster map,并设置为up且out状态,再将最新版本的cluster map发给这个新OSD。
收到monitor发来的cluster map之后,这个新OSD计算出自己所承载的PG(为简化讨论,此处我们假定这个新的OSD开始只承载一个PG),以及和自己承载同一个PG的其他OSD。然后,新OSD将与这些OSD取得联系。如果这个PG目前处于降级状态(即承载该PG的OSD个数少于正常值,如正常应该是3个,此时只有2个或1个。这种情况通常是OSD故障所致),则其他OSD将把这个PG内的所有对象和元数据复制给新OSD。数据复制完成后,新OSD被置为up且in状态。而cluster map内容也将据此更新。这事实上是一个自动化的failure recovery过程。当然,即便没有新的OSD加入,降级的PG也将计算出其他OSD实现failure recovery。
如果该PG目前一切正常,则这个新OSD将替换掉现有OSD中的一个(PG内将重新选出Primary OSD),并承担其数据。在数据复制完成后,新OSD被置为up且in状态,而被替换的OSD将退出该PG(但状态通常仍然为up且in,因为还要承载其他PG)。而cluster map内容也将据此更新。这事实上是一个自动化的数据re-balancing过程。
如果一个OSD发现和自己共同承载一个PG的另一个OSD无法联通,则会将这一情况上报monitor。此外,如果一个OSD deamon发现自身工作状态异常,也将把异常情况主动上报给monitor。在上述情况下,monitor将把出现问题的OSD的状态设为down且in。如果超过某一预订时间期限,该OSD仍然无法恢复正常,则其状态将被设置为down且out。反之,如果该OSD能够恢复正常,则其状态会恢复为up且in。在上述这些状态变化发生之后,monitor都将更新cluster map并进行扩散。这事实上是自动化的failure detection过程。
对于一个RADOS集群而言,即便由数千个甚至更多OSD组成,cluster map的数据结构大小也并不惊人。同时,cluster map的状态更新并不会频繁发生。即便如此,Ceph依然对cluster map信息的扩散机制进行了优化,以便减轻相关计算和通信压力:
首先,cluster map信息是以增量形式扩散的。如果任意一次通信的双方发现其epoch不一致,则版本更新的一方将把二者所拥有的cluster map的差异发送给另外一方。
其次,cluster map信息是以异步且lazy的形式扩散的。也即,monitor并不会在每一次cluster map版本更新后都将新版本广播至全体OSD,而是在有OSD向自己上报信息时,将更新回复给对方。类似的,各个OSD也是在和其他OSD通信时,将更新发送给版本低于自己的对方。
基于上述机制,Ceph避免了由于cluster map版本更新而引起的广播风暴。这虽然是一种异步且lazy的机制,但根据论文中的结论,对于一个由n个OSD组成的Ceph集群,任何一次版本更新能够在O(log(n))时间复杂度内扩散到集群中的任何一个OSD上。
一个可能被问到的问题是:既然这是一种异步和lazy的扩散机制,则在版本扩散过程中,系统必定出现各个OSD看到的cluster map不一致的情况,这是否会导致问题?答案是:不会。事实上,如果一个client和它要访问的PG内部的各个OSD看到的cluster map状态一致,则访问操作就可以正确进行。而如果这个client或者PG中的某个OSD和其他几方的cluster map不一致,则根据Ceph的机制设计,这几方将首先同步cluster map至最新状态,并进行必要的数据re-balancing操作,然后即可继续正常访问。