本文翻译自:http://docs.ceph.com/docs/hammer/architecture/
一些名词的翻译方式:
scalable
high availability:高可用
map:图
cluster map:集群运行图
monitor:监视器
acting set:运行集
up set:在线集
hyperscale:超大规模
data scrub:数据清洗
peering:互联
rebalance:重平衡
full write:一次完整地写
tiering:层
cache tiering:缓存层
ceph体系结构图:
ceph集群
根据ceph的相关论文,ceph基于RADOS来提供无限扩展的能力,一个ceph存储集群包含两种类型的守护进程:
监视器(Monitor)
OSD守护进程( OSD Daemon,以下称osd)
- 数据的存储
ceph集群从ceph客户端接收数据(客户端可以是一个ceph的块设备,ceph的对象存储网关,ceph的文件系统或者是基于librados写的程序),并以对象的形式存储起来。每个对象就像文件系统中的一个文件,osd负责在存储设备上的读写操作。
osd在一个扁平的命名空间内以对象的形式保存数据。每个对象都包含标识符(identifier),二进制数据(binary data)和以name/value对组成的元数据(metadata)。元数据的语义完全由ceph客户端来决定,比如,CephFS就用元数据来存储文件的各种属性。
注意:对象id在整个集群中是唯一的
- 可扩展和高可用
在传统架构中客户端需要与一个中心组件进行通信,这使得系统存在单点问题,并且可能成为性能和可扩展性的瓶颈。Ceph则没有这种中心式的网关,客户端直接与osd进行通信。osd会将对象复制到其他节点上来保证数据的安全和实现高可用,同时,ceph还有一套监控集群来实现高可用。ceph是通过一种叫crush的算法来实现去中心化的。
- Crush简介
ceph客户端和osd都使用crush算法来计算对象存储的位置,而不是通过一个中心式的表来查询。crush提供的这种数据管理机制更好,更方便大规模地扩展集群,因为定位对象的工作都交给各个客户端和osd了。crush使用智能数据复制的方式来确保弹性,更加适合超大规模集群。下面几节我们详细介绍一下crush算法的一些细节:
- 集群运行图
ceph客户端和osd都知道集群的拓扑结构,该结构由下面5张图组成,统称为“集群运行图”。
- monitor map:包含了集群的fsid,位置,地址名称和端口,也包含了当前的版本,创建时间,最后修改时间。要查看监视器图,可以使用命令:ceph osd dump
- osd map:包含了集群的fsid,创建和最后修改时间,pool列表,副本数量,pg数量,osd列表和他们的状态(up,in)。要查看osd图,可以用命令:ceph osd dump
- pg map:包含pg版本,时间戳,最后一次osd图的版本,full radio,每个pg上的详情如pg id,up set,acting set,pg状态(active + clean),每个pool的数据使用统计信息。
- crush map:包含了存储设备列表,失败域层级结构(device,host,rack,row,room等),存储数据时遍历这些层级的规则。要查看crush图,可以使用命令:ceph osd getcrushmap -o {filename},然后使用命令反编译一下:crushtool -d {comp-crushmap-filename} -o {decomp-rushmap-filename},然后可以使用cat命令查看反编译后的图。
- mds map:包含当前的mds运行图版本,创建时间,最后修改时间,同时也包含了保存元数据的pool,元数据服务器列表,哪些元数据服务器是up状态和哪些是in状态。要查看mds图,可以使用命令:ceph mds dump
高可用监视器
ceph客户端读写数据前必须从监视器获取一份最新的集群运行图。集群可以只有一台监视器,只不过这样会存在单点问题。
为了使集群高可用,ceph支持使用一套监视器集群,使用Paxos协议来使监视器之间对于当前集群状态达成一致。
高可用认证
为了认证用户身份和防止中间人攻击,ceph通过cephx认证系统来认证用户和守护进程。
注意:cephx协议并不对数据进行加解密。
cephx使用共享密钥进行认证,所以客户端和监视器集群都有一份密钥。
要使用cephx,管理员需要先设置用户(users)。可以通过client.admin用户执行命令:cephauth get-or-create-key来生成用户名和密钥,这时监视器会保有一份密钥,如下图所示:
cephx协议的认证方式跟 Kerberos 很像,流程如下:
客户端向监视器发起认证请求,监视器返回一份授权过的数据,有点像Kerberos里的ticket,包含一个会话key。key使用用户的密钥加过密,所以只有该用户才能解密这个key来访问监视器。接着客户端使用会话key请求监视器,监视器这时会返回一个加密过的ticket,如下图所示:
客户端解密这个ticket,对发送到osd和元数据服务器的数据进行签名。osd,元数据服务器和监视器共享一份密钥,所以都可以校验客户端的请求。就像Kerberos,cephx ticket有过期时间。
使用这种方式,只要用户的session key没有泄露就不会被攻击者伪造或修改消息。不过,这种认证只保护ceph客户端到服务器这段路程,如果用户从远程访问客户端,则从用户到客户端这段路程是不受保护的。
智能进程使超大规模成为可能
ceph没有中心节点,osd和客户端都知道集群的所有信息,每个osd都知道集群中所有其他osd的存在,这样osd就可以直接与其他osd和监视器通信。另外,客户端也可以直接与osd通信。
这种方式带来的好处有以下几点:
- osd和客户端直连:网络支持的并发连接数是有限的,如果存在中心节点则在集群规模变大时中心节点的连接数容易超过限制。去中心化则使用ceph客户端可以直接与osd相连接,这样提高了整体的性能和整个系统的并发量。
- osd成员和状态:osd加入集群后会报告它自身的状态,在最底层,用up,down来表示osd是否正常运行,down,in这两种状态表示osd存在问题。如果osd没有在运行(比如:崩掉了),那它就无法告之监视器它处于down状态,而监视器会定时ping osd以确保它们还在运行。不过,ceph还允许osd授权邻近的其它osd来判断其是否处于down状态,并更新集群运行图并报告给监视器,这样监视器可以以轻量级状态运行。
- 数据清洗:作为维护数据一致性和清洁度的一部分,osd可以把pg里面的对象元数据和另一个osd上面的pg里的副本相比较来清洗pg里面的对象。清洗任务用来捕获osd上的bug或文件系统错误。osd还可以一个比特位一个比特位对比的方式来执行深度清洗,以找出轻度清洗时没有发现的硬盘的坏扇区。
- 复制:osd采用crush算法来计算对象副本应该存储的位置。在写数据的过程中,客户端使用crush算法来计算对象应该存在哪,将对象映射到一个pool和pg,然后通过crush运行图来定位主osd。客户端将对象写入到主osd上的pg,然后,主osd通过crush运行图来定位第二和第三副本所在osd,将对象复制到这些osd上的pg里面,最后再回应客户端对象保存成功。示意图如下:
动态集群管理
ceph设计的关键点是自主自治,下面就介绍下crush是怎么做到让现代云存储服务实现保存数据,重新平衡集群以及是如何从失败状态中恢复的。
关于Pool
ceph有一个概念叫池(Pool),是对对象的逻辑分区。客户端从监视器获取一份集群运行图,然后往池里面写入对象。池的大小或者说是副本数量,crush规则集和pg的数量共同决定了ceph将怎样存储数据:
池的集合至少有以下几个参数:
- 对象的所有权/访问权
- pg的数量
- 用到的crush规则集
PG和OSD的映射关系
每个池都有若干个pg,crush动态地映射pg和osd的关系,当客户端要写入对象时,crush算法会把每个一对象映到到一个pg。这种将对象映射到pg的方式为osd和客户端之间建立了一层间接的关联关系。ceph集群必须支持节点的增加或减少并重新平衡对象的分布,如果客户端知道osd具体保存有哪些对象就会导致它们之间有强耦合的关联。相反,crush算法通过将每个对象映射到一个pg,每个pg映射到一个或多个osd,使得当有新osd加入或上线时ceph就可以方便的动态的重新平衡数据的分布。
客户端通过集群运行图,利用crush算法可以计算出某个具体对象应该保存在哪个osd上:
计算PG的id
当客户端从监视器获取到最新的集群运行图时,它就知道了所有监视器,osd和元数据服务器的信息,但是这时它并不知道对象存储的位置。对象存储的位置需要通过计算得出。
只需要输入对象id和池的信息,客户端使用对象名称,一个哈希值,池中pg的数量和池的名称就能计算出pg,以下是计算pgid的步骤:
- 客户端输入池id和对象id
- ceph计算出对象id的哈希值
- 使用哈希值对pg的数量取模定位到一个pg的id(如58)
- 通过池的名称得到池的id(如“livepool" = 4)
- 将池的id放在pg的id前面(如4.58)
计算对象存储的位置比通过一系列会话去查询要快得多,crush算法允许客户端来计算对象应该存储在哪,这样客户端可以直接连接保存对象的主osd。
互联和子集
osd相互之间除了会有心跳机制,还会进行互联,即所有osd会对每个pg中的所有对象的状态进行协商达成一致。实际上,osd会向监视器报告互联过程中出现的失败,互联这个功能通常会自己修复失败,但是也有可能一直不能修复,关于这一点请参见疑难解答部分。
ceph设计成至少保存对象的两个副本,这是对数据安全的最低保证。为了高可用目标,一个ceph集群只有保存两个以上对象的副本时才能以degraed状态运行。
我们没有对osd进行1,2,3这样的命名,而是使用了primary,secondary之类。按照约定,primary是运行集(Acting Set)中的第一个osd,为上面的每一个pg负责与其他osd进行互联协商,并且负责接受客户端写对象的请求。
对于那些负责同一个pg的osd集合我们称之为运行集(Acting Set)。
运行集中的osd并不总是up状态,当一个运行集是up状态时,它就变成了在线集(Up Set)的一部分。在线集是一个重要的特性,因为ceph可以在一个osd出问题时将pg重新映射到其他osd节点。
重平衡
当ceph集群中新增了一个osd节点的时候,集群运行图会进行更新,这样一来就改变了对象存储的位置,集群会进行重平衡,将一部分pg转移位置。但并不是所有pg都会进行迁移,即使会发生重平衡,crush算法仍然是比较稳定的,很多pg仍然会留在原来的位置,每个osd都将腾出一些空间,也不会对新加入的节点造成负载问题。下图是一次重平衡过程:
数据一致性
通过对比pg和它的副本里的对象元数据,osd可以在pg里清洗对象,这一点在前面有讲过。
纠删码
如果纠删码池使用K+M块数据块来存储对象,那它会将对象分成K块数据块和M块编码块。池的大小配置成K+M的话就可以让每块数据保存到运行集中的一个osd上,块的序号保存在对象的属性里。
如果一个纠删码池采用了5个osd(K+M=5),那它最多可以接受丢失2个osd(M=2)
读取和写入校验码数据块
当一个包含ABCDEFGHI的对象NYAN写入到池中时,纠删码函数会把数据内容切分成3个数据块:ABC,DEF,GHI。如果数据内容的长度不是K的整数倍将会进行补齐,函数还会创建两个校验码块:YXY和GQC。每个块保存在运行集中的一个osd上。数据块以对象的形式保存,并且对象名称相同(NYAN)。数据块的序号则保存在对象的属性中(shard_t)。
如果块2和5丢失了,从纠删码的池中读取对象时,解码函数会读取1,3,4三块数据:BC,GHI,YXY,然后重构出对象的原始内容:ABCDEFGHI。如下图所示:
写过程被中断
在纠删码池中,在线集(up set)中的主osd(primary osd)负责接收所有写请求,负责将数据编码成K+M块然后发送给其他osd,同时也负责维护一份权威版本的pg日志
下图示例了一个以K=2,M=1的方式创建的pg,分布在三个osd上面(osd1,osd2,osd3):有一个对象编码后分块保存在这些osd上面:d1v1,d2v2,c1v1,每个osd上面的pg日志都是相同的。
osd1是主osd,负责接收整个写操作,这个操作将替换掉整个原有对象而不只是对象的一部分,即Version2将替换掉Version1。osd1将对象编码成三个块:d1v2保存在osd1,d2v2保存在osd2,c1v2保存在osd3。每一块都会发送到相应的osd,包括主osd。当一个osd接收到指令要求写入数据块时,也会创建一份pg日志来记录本次变更。比如只要osd3保存好了c1v2,它就会在日志中增加一项entry(epoch:1, version:2)。因为osd都是异步工作的,有些块可能还在传输过程中时另一些可能已经落盘保存好了。如下图:
如果一切顺利进行,每个osd都会确认数据块写入成功,然后日志中'last_complete'指针就可以从1,1指向1,2。如下图:
最后,用来保存之前的那个旧的数据块的文件就要以移除掉了,最终如下图所示:
但是也有可能发生意外,发果osd1下线了而d2v2仍然在传输中,那这个对象的第2个版本就只写了一部分:osd3有一块数据但仍然不足以还原完整对象。这时就丢掉了两个数据块:d1v2,d2v2,而K=2,M=1要求至少有两个数据块可用才能重建第三块。假设osd4变成新的主osd,并从日志中查找到last_complete指针指向(1,1),然后将这个指针作为新的权威日志的头部。如下图所示:
osd3就会发现它上面日志的entry(1,2)与osd4上面的权威日志不匹配,所以它会丢弃c1v2数据块。数据清洗任务将会通过解码函数重建出d1v1块并保存到osd4上面。如下图所示:
缓存层
缓存层可以让客户端体验更好的io性能,你可以使用一组相对快但比较贵的存储设备用来用于缓存层,而使用另一组相对较慢但更便宜的设备来做为比较经济的存储层。缓存代理负责什么时候将缓存中的对象刷新到后端存储,所以缓存层和后端存储层对于客户端来说是透明的。如下图:
扩展ceph
你可以通过创建共享对象类(Ceph Classes)来扩展ceph。ceph从osd class dir动态加载.so文件。如果你想自己实现一个类,你可以设计新的对象方法来调用本地方法,或者其他的类方法。
在进行写操作时,ceph类会调用本地方法或类方法,对输入的数据执行一系列操作然后产生一个写事务的结果。
在进行读操作时,ceph类会调用本地方法或类方法,对输出的数据执行一系列操作然后将数据返回给客户端。
ceph类示例:如果ceph作为内存管理系统的存储,可以对输入的图片进行大小和比例的剪裁,嵌入一个不可见的版权说明或水印来保护版权,然后将修改过的图片保存到ceph。
ceph集群总结
ceph存储集群就像有机体一样是动态的,很多存储系统不能很好地利用cpu和内存,而ceph可以。从心跳,互联,重平衡集群或者从失败中恢复,ceph利用了osd的计算能力来替客户端做了很多工作。当涉及到硬件推荐和网络配置时,可以回顾一下前面的概念来理解ceph是怎么利用计算机资源的。
ceph协议
ceph客户端使用原生协议来与ceph集群通信。ceph将这些方法打包成librados库,这样你就可以使用它来创建自己的客户端,下图是它的基本结构:
原生协议和librados
现代的应用程序需要一个有异步通信的能力的简单的对象存储接口,而ceph正好能提供这样的接口,这些接口提供了直接的,可并行访问对象的能力:
- 对池的操作
- 快照和写时复制
- 读写对象-包括读写完整对象,字节范围,追加或截断。
- 创建和更新对象属性 XATTRs
- 创建和更新 key/value对
- 混合多种操作和双重确认语义
- 对象分类
对象监视和通知
客户端可以向主osd注册一个对象的监听器并维持一个会话,客户端也可以发送通知消息和载荷给所有watcher,并可以接收watcher的消息。通过这些方式,客户端可以使用任何对象作为同步或通信channel。
数据条带化
存储设备都存在吞吐量限制,这就影响了存储性能和伸缩性。所以一般的存储系统都支持条带化,即将数据分成若干连续块分别存储到不同的设备,这样来提高吞吐量和性能。常用的条带方式有RAID,ceph的条带化像RAID0,或是一个条带卷。ceph的条带化达到了raid0的吞吐量、n路raid镜像的可靠性和快速恢复能力。
ceph提供了三种客户端类型:block device,Filesystem,ObjectStorage,客户端需要自己负责将用户数据转换成多个对象存储到ceph集群里。
最简单的条带化方式是一个对象一个对象地写入条带数据,客户端持续往对象写入条带单元的数据直到达到对象所允许的最大值,然后创建新的对象来继续保存剩下的条带单元数据。这种方式对于小的块存储,s3或Swift对象和cephfs的文件是有效率的,但是还没有最大化的利用ceph的分布式pg的能力,所以也不能提升多大的性能。
如果你需要存储大的块,对象或文件等,采用多对象集合条带化的方式可以得到可观的性能提升。客户端将条带单元大小的数据并发写入相应的条带对象。因为每个对象对应到不同的pg从而对应到不同的osd,所以每次并发写操作都能达到最大的写速度。写入到单个磁盘会受限于磁头的速度,而将写操作分布到多个磁盘可以减少每个磁盘的总寻址次数,从而可以结合多个磁盘的吞吐量达到较快的写速度。
下图展示了客户端数据在对象集(object set 1)中的条带化过程,对象集中有四个对象,条带单元数据按顺序写入到这四个对象,每写一轮客户端都会判断一下对象集是否已满,没满则继续从第一个对象开始写数据,如果满了则创建另一个对象集(object set 2),然后按同样的方式写入数据。
有三个重要变量决定了ceph如何条带化数据:
- 对象大小: ceph集群中有对象的最大大小限制,这个大小必须能容纳多个条带单元的数据,并且应该是条带单元的整数倍。
- 条带带宽:条带有一个单元大小的配置,客户端将数据按单元大小进行分块。
- 条带数量:客户端每次按照条带数量来决定将多少个条带单元写入到多少个对象中。这些对象称之为对象集,当数据写入到最后一个对象后,客户端又从集合中第一个对象开始写。
重要:在上生产环境前对你的条带配置进行性能测试,一旦条带化数据写入到对象后就再也不能修改这些参数了。
ceph客户端
ceph客户端包含几种接口:
- 块存储
- 对象存储
- 文件系统
下图是一个高层架构视图:
ceph对象存储
对象存储radosgw是一个网关服务,它提供了一套restful风格的api来存取对象和元数据。它是基于ceph集群,有自己的数据格式,维护自己的用户数据库,认证和访问控制。rgw使用了统一的命名空间,所以你可以使用openstack swift兼容api或者亚马逊s3兼容api。比如,你可以通过s3接口写入数据,然后通过swift接口读取数据。
ceph块设备
ceph块设备将一个块设备映象条带化成多个对象,每个对象都分散到一个pg中,而pg又分散到不同的osd上。
这种精简的,可快照的ceph块设备对于虚拟化和云计算是很有吸引力。在虚拟机场景,人们经常使用Qemu/KVM中的rbd网络存储驱动部署ceph块设备,其中宿主机采用librbd向客户机提供块设备服务。许多云计算堆栈使用libvirt和管理程序集成。你可以采用精简的的ceph块设备搭配Qume和libvirt来支持openstack和cloudstack,构成一套解决方案。
ceph文件系统
ceph文件系统在ceph集群的上面提供了一套posix兼容的接口,cephFS里的文件映射为ceph集群里的一个对象。ceph客户端将cephfs挂载成一个内核对象或作为面向用户的文件系统(FUSE)。如下图:
cephFS服务包括了与ceph集群部署在一起的mds(ceph metadata server),mds的目的是在高可用的元数据服务器上保存所有文件系统元数据(目录,文件拥有者,访问模式等),这些元数据在内存里也有一份。mds的意义在于对那些简单的文件系统相关操作(如ls,cd)不必要去浪费osd的资源。将元数据和数据分开意味着ceph文件系统可以在不浪费集群资源的情况下提供高性能的服务。
cephFS将数据和元数据分开,元数据保存在mds,文件数据保存在一个或多个对象中。cephFS的目标是posix兼容,ceph-mds可以以单进程形式运行,或者它也可以分布到多个物理机上运行,这样也是为了高可用和高伸缩性:
- 高可用:有额外的mds在一旁待命,这样一旦活动的ceph-mds失败就可以取而代之。这是很容易的因为所有数据,包括日志,都是保存在rados上的。这个主备的转换过程由ceph-mon自动完成。
- 伸缩性:可以有多个ceph-mds处于活动状态,它们将文件夹树切分成多个子树,有效地平衡活动服务器的负载。
可以将standy和active组合在一起,比如运行3个活动的ceph-mds来提供伸缩性,一个待命例程来提供高可用。