在大部分中心化存储系统中(如GFS)中,通常有一个中心节点管理整个系统元数据的更新、协调节点之间的数据复制,检测并处理节点故障以及后续的数据迁移工作。Ceph认为将所有的管理工作全部交一个节点不利于整个系统的扩展:1)单个节点存储、计算以及网络传输能力有限,容易成为性能瓶颈;2)中心节点本身存在单点故障的风险。如果让数据节点参与管理工作,充分利用数据节点CPU/内存/网络资源,就能提升系统性能改善其扩展性。Ceph将这种拥有部分自主管理能力的数据节点称为智能设备-OSD(Object Storage Device)。
中心化存储系统中,中心节点拥有全局数据分布的信息并且能够基于这些信息进行数据分布的决策。如果让OSD也具有这种能力,那么OSD也必须知道整个系统的数据分布信息。GFS采用细粒度的数据分布设计,即每个Block都拥有的不同的数据分布,这种方式的缺点在于整个系统数据分布信息数据量非常大(即使通过增大Block的大小,也无法将全部的数据分布信息存储在单机之上)。Ceph采用一种更粗粒度的数据分布设计,数据(Object)首先会被映射到一个PG(Placement Group),各个PG被指派到不同的OSDs中(同一个PG中的全部对象都被存储在同样的物理OSDs中)。这种设计减少了数据分布信息数据量大小,方便这些信息在网络上的传输并且只占用OSD极少的内存,这就允许各个OSD自主根据这些信息进行全局统一的数据分布决策。Ceph将这些信息以及系统所有OSD的状态等合起来称为Cluster Map,当OSD或者系统网络发生变化的时候,Cluster Map也需要跟着发生变化。例如当某个OSD故障或者网络连接中断的时候,需要将该OSD上的PGs迁移到其他的OSD上,Cluster Map要进行相应的调整,并且这些调整需要一致的反应到全部的OSD的本地Cluster Map缓存。
Ceph通过一个Monitor集群来维护Cluster Map的主备份,所有的OSD均连接到该集群上去获取对应的拷贝,Monitor集群内部通过Paxos协议来维护Cluster Map的数据一致性。实现上Monitor采用了Lease机制简化Paxos协议,这种简化协议仅允许系统中只有一个Monitor能够进行写入,这个Monitor称为Leader。初始时所有的Monitor先选举出一个Leader,该Leader将协调其他Monitor的Cluster Map的更新。每隔固定周期Leader向其他Monitor发放Lease,持有Lease的Monitor才能够对外提供读服务(否则认为该Monitor上的数据已经过时,不能提供读服务)。当Leader宕机时,其他的Monitor会重新选举Leader。OSDs可以连接到任意一个Monitor上发送写请求,该请求会被转发到Leader上由Leader进行处理后,同步到各个Monitor中。当OSD状态发生变化的时候,Leader运行CRUSH算法重新计算PG的分布变更,并将这些变更同步给其他的Monitor。Monitor集群不会主动去检测OSD的状态,OSD也不会定期向Monitor汇报状态,OSD状态报告主要是通过下面三种方式:1)Client在读取某个OSD内容时发现OSD宕机,并报告给Monitor集群;2)OSD在给其他OSD同步数据或者交换心跳信息时发现OSD宕机,报告给Monitor集群;3)新上线或者恢复的OSD主动向Monitor集群报道自己新的状态。这样设计的原因在于Ceph需要支持PB级的数据存储,系统中可能有成千上万的OSD,正常情况下并不需要这些检测网络信息,保证Monitor集群最小化有利于系统的扩展性。同样Cluster Map的增量变化大部分情况也不是由Monitor集群直接同步给各个OSD,而是各个OSD中相互同步最新的Cluster Map信息。每个Cluster Map都有一个epoch,每次Cluster Map发生变化,epoch都会进行增加。Client和OSD以及OSD和OSD进行各种交互的时候(如Client向OSD发送读写请求,OSD向OSD发送数据备份请求以及心跳消息)都会带上发送者Cluster Map的epoch。epoch较新的一方会自动计算两个epoch之间的增量差异,并将差异发送给epoch较老的一方,后者更新自己的Cluster Map信息。Client或者OSD初始连接集群的时候才会向Monitor拉取全量的Cluster Map信息。
在GFS中客户端每次向系统新写入一个Block都需要NameNode为该Block分配副本存放信息,这当然会加重NameNode的负担并影响客户端写入性能。在Ceph中Object的副本存放信息是由PG来决定的,Ceph的Monitor集群会计算每个PG所对应的OSD,并将这个信息保存在Cluster Map中。只要系统的OSD状态没有发生变更,那么属于同一个PG的Object的副本存放信息就是不变的。Client在读写一个对象的时候,首先根据其对象标识计算对应的pg_id,然后查找Cluster Map中该pg_id对应的active OSD List,并选择其中第一个作为Primary OSD发送读写请求。该OSD收到写请求的时候先检查比对Client的自己的epoch,如果一致则查找pg_id对应的active OSD list,并将副本信息转发给对应的OSD。如果Client的epoch比自己的老,则OSD计算Cluster Map的差异,并将差异返回给Client,让Client将请求发送到正确的OSD上。如果Client的epoch比自己的新,则OSD向Client请求最新的Cluster Map。Client根据自己所有的Cluster Map信息计算差异并返回给OSD。OSD在获取最新的Cluster Map之后会了解的自己角色的变化(可能之前该OSD并不存储该PG,由于其他OSD故障等状态变化,Monitor集群将新的PG分配给了自己),并根据相应的角色采取不同的策略。如果该OSD是PG的Replica,则该OSD向该PG当前的Primary发送请求获取对应的Replica内容(这在Chain或者Splan复制模式比较有可能,因为读取发生在active list的最末尾的OSD上,在Primary-Copy模式下读取发生在Primary上)。如果该OSD是该PG的Primary(说明PG的Primary发生了变化),那么该OSD会阻塞这次Client的IO请求,直到该PG正确的恢复了该对象的数据。恢复过程分为两个阶段:Peering和Recovery。在Peering阶段中,每个OSD副本会向PG当前的Primary发送Notify消息,该消息PG最新的更新,PG log以及最近的epoch。这个阶段中Primary OSD将会了解到所有的PG的信息,包括Object的最新版本以及对应OSD。在Recovery阶段当前的Primary会从对应的OSD中拉取缺失的Object,并将最新的内容推送给其他缺失的OSD中。正常情况下,为了避免Client读到老的Replica(这种情况可能发生在网络分区使得Client和连接的OSD与其他大部分OSD断开),要求同一个PG的OSD之间要互相周期性发送心跳消息。如果提供读服务的OSD一定时间内没有收到其他OSD的心跳消息,那么它认为自己数据过时,将会阻塞Client的请求。这些心跳信息还承担故障检测的功能,当某个OSD能够确定另外一个OSD宕机后(不是简单的二者之间网络不可达),它会向Monitor请求将该OSD状态标志为down,monitor收到请求后会转发到Leader进行处理。
【参考资料】
[1] Weil S A, Leung A W, Brandt S A, et al. Rados: a scalable, reliable storage service for petabyte-scale storage clusters[C]//Proceedings of the 2nd international workshop on Petascale data storage: held in conjunction with Supercomputing’07. ACM, 2007: 35-44.
[2] http://docs.ceph.com/docs/master/architecture/