ceph是⼀种分布式存储系统,可以将多台服务器组成⼀个超⼤集群,把这些机器中的磁盘资源整合到⼀块⼉,形成⼀个⼤的资源池(⽀持PB级别,大厂用得多),然后按需分配给客户端应⽤使⽤。由于ceph源码代码量较大,这里只做简单介绍。
相比于其他存储方案,ceph有以下优势。
ceph可以⼀套存储系统同时提供块设备存储、⽂件系统存储和对象存储三种存储功能。
块设备是i/o设备中的⼀类,是将信息存储在固定⼤⼩的块中,每个块都有⾃⼰的地址,还可以在设备的任意位置读取⼀定⻓度的数据。看不懂?那就暂且认为块设备就是硬盘或虚拟硬盘吧。
查看下Linux环境中的设备:
root@nb:~$ ls /dev/
/dev/sda/ dev/sda1 /dev/sda2 /dev/sdb /dev/sdb1 /dev/hda
/dev/rbd1 /dev/rbd2 …
下面介绍一下块设备
上⾯的/dev/sda、/dev/sdb和/dev/hda都是块设备⽂件。当给计算机连接块设备(硬盘)后,系统检测的有新的块设备,该类型块设备的驱动程序就在/dev/下创建个对应的块设备设备⽂件,⽤户就可以通过设备⽂件使⽤该块设备了。以sd开头的块设备⽂件对应的是SATA接⼝的硬盘,⽽以hd开头的块设备⽂件对应的是IDE接⼝的硬盘。那SATA接⼝的硬盘跟IDE接⼝的硬盘有啥区别?你只需要知道,IDE接⼝硬盘已经很少⻅到了,逐渐被淘汰中,⽽SATA接⼝的硬盘是⽬前的主流。⽽sda和sdb的区别呢?当系统检测到多个SATA硬盘时,会根据检测到的顺序对硬盘设备进⾏字⺟顺序的命名。
rbd(rados block devices)就是由ceph集群提供出来的块设备。可以这样理解,sda和hda都是通过数据线连接到了真实的硬盘,⽽rbd是通过⽹络连接到了ceph集群中的⼀块存储区域,往rbd设备⽂件写⼊数据,最终会被存储到ceph集群的这块区域中。
那么块设备怎么⽤呢?这⾥举个例⼦:
打个⽐⽅,⼀个块设备是⼀个粮仓,数据就是粮⻝。农⺠伯伯可以存粮⻝(写数据)了,需要存100⽄⽟⽶,粮仓(块设备)这么⼤放哪⾥呢,就挨着放(顺序写)吧。⼜需要存1000⽄花⽣,还是挨着放吧。⼜需要存……
后来,农⺠伯伯来提粮⻝(读数据)了,他当时存了1000⽄⼩⻨,哎呀妈呀,粮仓这么⼤,⼩⻨在哪⾥啊?仓库管理员找啊找,然后哭晕在了厕所……
新管理员到任后,想了个法⼦来解决这个问题,⽤油漆把仓库划分成了⽅格状,并且编了号,在仓库⻔⼝的⽅格那挂了个⿊板,当农⺠伯伯来存粮⻝时,管理员在⿊板记录,张三存了1000⽄⼩⻨在xx⽅格处。后来,农⺠伯伯张三来取粮⻝时,仓库管理员根据⼩⿊板的记录很快提取了粮⻝。故事到此为⽌了,没有⽅格和⿊板的仓库(块设备)称为裸设备。由上例可⻅,裸设备对于⽤户使⽤是很不友好的,直接导致了旧仓库管理员的狗带。例⼦中划分⽅格和挂⿊板的过程其实是在块设备上构建⽂件系统的过程,⽂件系统可以帮助块设备对存储空间进⾏条理的组织和管理,于是新管理员通过⽂件系统(格⼦和⿊板)迅速找到了⽤户(农⺠伯伯张三)存储的数据(1000⽄⼩⻨)。
针对多种多样的使⽤场景,衍⽣出了很多的⽂件系统。有的⽂件系统能够提供更好的读性能,有的⽂件系统能提供更好的写性能。我们平时常⽤的⽂件系统如xfs、ext4是读写性能等各⽅⾯⽐较均衡的通⽤⽂件系统。
能否直接使⽤不含有⽂件系统块设备呢?可以的,xfs和ext4等通⽤的⽂件系统旨在满⾜⼤多数⽤户的存储需求,所以在数据存储的各⽅⾯的性能⽐较均衡。然⽽,很多应⽤往往并不需要这种均衡,⽽需要突出某⼀⽅⾯的性能,如⼩⽂件的存储性能。此时,xfs、ext4等通⽤⽂件系统如果不能满⾜应⽤的需求,应⽤往往会在裸设备上实现⾃⼰的数据组织和管理⽅式。简单的说,就是应⽤为了强化某种存储特性⽽实现⾃⼰定制的数据组织和管理⽅式,⽽不使⽤通⽤的⽂件系统。
总结⼀下,块设备可理解成⼀块硬盘,⽤户可以直接使⽤不含⽂件系统的块设备,也可以将其格式化成特定的⽂件系统,由⽂件系统来组织管理存储空间,从⽽为⽤户提供丰富⽽友好的数据操作⽀持。
还记得上⾯说的块设备上的⽂件系统吗,⽤户可以在块设备上创建xfs⽂件系统,也可以创建ext4等其他⽂件系统。Ceph集群实现了⾃⼰的⽂件系统来组织管理集群的存储空间,⽤户可以直接将Ceph集群的⽂件系统挂载到⽤户机上使⽤。
块设备接口与文件系统接口区别
Ceph有了块设备接⼝,在块设备上完全可以构建⼀个⽂件系统,那么Ceph为什么还需要⽂件系统接⼝呢?主要是因为应⽤场景的不同,Ceph的块设备具有优异的读写性能,但不能多处挂载同时读写,⽬前主要⽤在OpenStack上作为虚拟磁盘,⽽Ceph的⽂件系统接⼝读写性能较块设备接⼝差,但具有优异的共享性。
注意两者共享性的区别,上面是块设备,下面是文件系统。
为什么Ceph的块设备接⼝不具有共享性,⽽Ceph的⽂件系统接⼝具有呢?对于Ceph的块设备接⼝,⽂件系统的结构状态是维护在各⽤户机内存中的,假设Ceph块设备同时挂载到了⽤户机1和⽤户机2,当在⽤户机1上的⽂件系统中写⼊数据后,更新了⽤户机1的内存中⽂件系统状态,最终数据存储到了Ceph集群中,但是此时⽤户机2内存中的⽂件系统并不能得知底层Ceph集群数据已经变化⽽维持数据结构不变,因此⽤户⽆法从⽤户机2上读取⽤户机1上新写⼊的数据。
对于Ceph的⽂件系统接⼝,⽂件系统的结构状态是维护在远端Ceph集群中的,Ceph⽂件系统同时挂载到了⽤户机1和⽤户机2,当往⽤户机1的挂载点写⼊数据后,远端Ceph集群中的⽂件系统状态结构随之更新,当从⽤户机2的挂载点访问数据时会去远端Ceph集群取数据,由于远端Ceph集群已更新,所有⽤户机2能够获取最新的数据。
介绍一下文件系统接口操作方法
# 将Ceph的⽂件系统挂载到⽤户机⽬录
# 保证/etc/ceph⽬录下有Ceph集群的配置⽂件ceph.conf和ceph.client.admin.keyring
mkdir -p /mnt/ceph_fuse
ceph-fuse /mnt/ceph_fuse
在/mnt/ceph_fuse下读写数据,都是读写远程Ceph集群。
总结⼀下,Ceph的⽂件系统接⼝弥补了Ceph的块设备接⼝在共享性⽅⾯的不⾜,Ceph的⽂件系统接⼝符合POSIX标准,⽤户可以像使⽤本地存储⽬录⼀样使⽤Ceph的⽂件系统的挂载⽬录。还是不懂?这样理解,⽆需修改你的程序,就可以将程序的底层存储换成空间⽆限并可多处共享读写的Ceph集群⽂件系统。
使⽤⽅式是通过http协议上传下载删除对象(⽂件即对象)。
⽼问题来了,有了块设备接⼝存储和⽂件系统接⼝存储,为什么还整个对象存储呢?往简单了说,Ceph的块设备存储具有优异的存储性能但不具有共享性,⽽Ceph的⽂件系统具有共享性然⽽性能较块设备存储差,为什么不权衡⼀下存储性能和共享性,整个具有共享性⽽存储性能好于⽂件系统存储的存储呢,对象存储就这样出现了。
对象存储为什么性能会⽐⽂件系统好?
主要原因是:对象存储组织数据的⽅式相对简单,只有bucket和对象两个层次(对象存储在bucket中),对对象的操作也相对简单。⽽⽂件系统存储具有复杂的数据组织⽅式,⽬录和⽂件层次可具有⽆限深度,对⽬录和⽂件的操作也复杂的多,因此⽂件系统存储在维护⽂件系统的结构数据时会更加繁杂,从⽽导致⽂件系统的存储性能偏低。此外,由于一个文件可能被划分为多个object并放在多个bucket中,所以写的并发能力增强,写速度增加。对象存储没有修改操作,如果要修改,需要先get修改,再put上传,最后delete删除旧文件,所以适用于写少读多的场景。
Ceph的对象接⼝符合亚⻢逊S3接⼝标准和OpenStack的Swift接⼝标准,这里就不介绍了。
总结⼀下,⽂件系统存储具有复杂的数据组织结构,能够提供给⽤户更加丰富的数据操作接⼝,⽽对象存储精简了数据组织结构,提供给⽤户有限的数据操作接⼝,以换取更好的存储性能。对象接⼝提供了REST API,⾮常适⽤于作为web应⽤的存储。
概括⼀下,块设备速度快,对存储的数据没有进⾏组织管理,但在⼤多数场景下,⽤户数据读写不⽅便(以块设备位置offset + 数据的length来记录数据位置,读写数据)。⽽在块设备上构建了⽂件系统后,⽂件系统帮助块设备组织管理数据,数据存储对⽤户更加友好(以⽂件名来读写数据)。Ceph⽂件系统接⼝解决了“Ceph块设备+本地⽂件系统”不⽀持多客户端共享读写的问题,但由于⽂件系统结构的复杂性导致了存储性能较Ceph块设备差。对象存储接⼝是⼀种折中,保证⼀定的存储性能,同时⽀持多客户端共享读写。
⽀持三种接⼝:
RADOS(Reliable Autonomic Object Store,可靠、⾃动、分布式对象存储)是ceph存储集群的基础,这⼀层本身就是⼀个完整的对象存储系统。Ceph的⾼可靠、⾼可扩展、⾼性能、⾼⾃动化等等特性本质上也都是由这⼀层所提供的,在ceph中,所有数据都以对象的形式存储,并且⽆论什么数据类型,RADOS对象存储都将负责保存这些对象,确保了数据⼀致性和可靠性。
RADOS系统主要由两部分组成,分别是OSD(对象存储设备)和Monitor(监控OSD)。
LIBRADOS基于RADOS之上,它允许应⽤程序通过访问该库来与RADOS系统进⾏交互,⽀持多种编程语⾔,⽐如C、C++、Python等。
基于LIBRADOS层开发的三个接⼝,其作⽤是在librados库的基础上提供抽象层次更⾼、更便于应⽤或客户端使⽤的上层接⼝。
这⾥提到了两个对象,⼀个是RGW中的对象存储;⼀个是Ceph的后端存储的对象,这两个需要区分:
举个例子,使⽤RGW接⼝,存放⼀个1G的⽂件,在⽤户接⼝看到的就是存放了⼀个对象;⽽后通过RGW 分⽚成多个对象后最终存储到磁盘上。RGW为RADOS Gateway的缩写,ceph通过RGW为互联⽹云服务提供商提供对象存储服务。RGW在librados之上向应⽤提供访问ceph集群的RestAPI,⽀持Amazon S3和openstack swift两种接⼝,并没有自己重新搞一套接口的协议。对RGW最直接的理解就是⼀个协议转换层,把从上层应⽤符合S3或Swift协议的请求转换成rados的请求,将数据保存在rados集群中。
先总体介绍一下:
RADOS (Reliable, Autonomic Distributed Object Store) 是Ceph的核⼼之⼀,作为Ceph分布式⽂件系统的⼀个⼦项⽬,特别为Ceph的需求设计,能够在动态变化和异质结构的存储设备机群之上提供⼀种稳定、可扩展、⾼性能的单⼀逻辑对象(Object)存储接⼝和能够实现节点的⾃适应和⾃管理的存储系统。 在传统分布式存储架构中,存储节点往往仅作为被动查询对象来使⽤,随着存储规模的增加,数据⼀致性的管理会出现很多问题。⽽新型的存储架构倾向于将基本的块分配决策和安全保证等操作交给存储节点来做,然后通过提倡客户端和存储节点直接交互来简化数据布局并减⼩io瓶颈。
服务端 RADOS 集群主要由两种节点组成:
集群通过monitor集群操作cluster map来实现集群成员的管理。cluster map 描述了哪些OSD被包含进存储集群以及所有数据在存储集群中的分布。cluster map不仅存储在monitor节点,它被复制到集群中的每⼀个存储节点,以及和集群交互的client。当因为⼀些原因,⽐如设备崩溃、数据迁移等,cluster map的内容需要改变时,cluster map的版本号被增加,map的版本号可以使通信的双⽅确认⾃⼰的map是否是最新的,版本旧的⼀⽅会先将map更新成对⽅的map,然后才会进⾏后续操作。
RADOS也通过 cluster map来实现这些存储半⾃动化的功能,cluster map会被复制到集群中的所有部分(存储节点、控制节点,甚⾄是客户端),并且通过怠惰地传播⼩增量更新⽽更新。Clustermap中存储了整个集群的数据的分布以及成员。通过在每个存储节点存储完整的Cluster map,存储设备可以表现的半⾃动化,通过peer-to-peer的⽅式(⽐如定义协议)来进⾏数据备份、更新,错误检测、数据迁移等等操作。这⽆疑减轻了占少数的monitor cluster(管理节点组成的集群)的负担。
⽆论使⽤哪种存储⽅式(对象,块,⽂件),存储的数据当底层保存时,都会被切分成⼀个个⼤⼩固定的对象(Objects),对象⼤⼩可以由管理员⾃定义调整,RADOS中基本的存储单位就是Objects,⼀般为2MB或4MB(最后⼀个对象⼤⼩有可能不同)。
如上图,⼀个个⽂件(File)被切割成⼤⼩固定的Objects后,将这些对象分配到⼀个PG(Placement Group)中,然后PG会通过多副本的⽅式复制⼏份,随机分派给不同的存储节点(也可指定)。
当新的存储节点(OSD)被加⼊集群后,会在已有数据中随机抽取⼀部分数据迁移到新节点,这种概率平衡的分布⽅式可以保证设备在潜在的⾼负载下正常⼯作,更重要的事,数据的分布过程仅需要做⼏次随机映射,不需要⼤型的集中式分配表,⽅便且快速,不会对集群产⽣⼤的影响。
具体存储过程:⽤户通过客户端存储⼀个⽂件时,在RAODS中,该File(⽂件)会被分割为多个2MB/4MB⼤⼩的Objects(对象)。⽽每个⽂件都会有⼀个⽂件ID,例如A,那么这些对象的ID就是A0,A1,A2等等。然⽽在分布式存储系统中,有成千上万个对象,只是遍历就要花很久时间,所以对象会先通过hash-取模运算,存放到⼀个PG中。PG相当于数据库的索引(PG的数量是固定的,不会随着OSD的增加或者删除⽽改变),这样只需要⾸先定位到PG位置,然后在PG中查询对象即可。之后PG中的对象⼜会根据设置的副本数量进⾏复制,并根据CRUSH算法存储到OSD节点上。
解释几个概念:
再看几个映射关系:
这里再提一下,为什么要引入PG?
因为Object对象的size很⼩,并不会直接存储进OSD中,在⼀个⼤规模的集群中可能会存在⼏百到⼏千万个对象,这么多对象如果遍历寻址,那速度是很缓慢的,并且如果将对象直接通过某种固定映射的哈希算法映射到osd上,那么当这个osd损坏时,对象就⽆法⾃动迁移到其他osd上(因为映射函数不允许)。为了解决这些问题,ceph便引⼊了归置组的概念,即PG。最后PG会根据管理设置的副本数量进⾏副本级别的复制,然后通过CRUSH算法存储到不同的osd上(其实是把PG中的所有对象存储到节点上),第⼀个osd节点即为主节点,其余均为从节点。
如果新加⼊的OSD1取代了原有的 OSD成为 Primary OSD, 由于 OSD1 上未创建 PG , 不存在数据,那么PG 上的 I/O ⽆法进⾏,怎样⼯作的呢?
最后,写个伪代码说明一下总流程
locator = object_name;
obj_hash = hash(locator);
pg = obj_hash % num_pg;
osds_for_pg = crush(pg); // returns a list of osds
每个OSD上分布很多PG, 并且每个PG会⾃动散落在不同的OSD上。如果扩容那么相应的PG会进⾏迁移到新的OSD上,保证PG数量的均衡。由于PG会做备份,所以不会说挂掉一个PG就访问不了object了。
举个例子,每个PG对应⼀个主分区和两个备份分区。现状3个OSD, 4个PG扩容到4个OSD, 4个PG。
PGa -> osd1 2 3,PGb -> osd1 2 3,PBc -> osd1 2 3,PBd -> osd1 2 3
PGa -> osd 2 3 4,PGb -> osd 1 2 4,PBc -> osd 1 3 4,PBd -> osd 1 2 3
最后,再与fastdfs做个对比。
fastdfs是弱一致性,写速度较快,但可靠性低一些;ceph则是强一致性,写速度会慢一点,但是可靠性高。