ceph设计哲学与一些思考

ceph最终要的设计哲学是:一切都可以被扩展。无论是在上层组件设计,还是底层硬盘的设计上,ceph都要求每个组件都能做到水平横向扩展。当某些资源不够用时,都需要能添加硬件的方式来提升集群的可用资源、性能。

为了践行这个设计哲学,ceph在设计上遵循了2大理念:

  1. 一切皆对象
  2. 一切皆crush

ceph的文件存储、对象存储、块存储的三种存储形态都建立在名为RADOS的对象存储层。上层存储形态中的文件都会被分割为一个个rados对象存储在对象中。ceph并不提供中心元数据服务器来索引所有对象,而是基于对象、存储池、存储桶的名称,配合crush算法来实现对象的定位。每个文件会分割成一个个rados对象,并且通过crush算法,分发到不同的osd上去,从而将上层IO流量均匀地分配到每个集群节点上去。

Crush算法

ceph设计哲学与一些思考_第1张图片

在介绍crush算法之前,还需要介绍两个概念:

1. PoolCeph对PG做的逻辑上的划分。每类存储都有其对应的默认存储池,比如RBD的默认存储池为rbd, RGW的对应存储池为default.rgw.buckets.data, CephFS的对应存储池为cephfs。也就是说,不同的RADOS上层来的数据,最终会落到不同的Pool中,由此来更好的管理数据。

PG(placement group)

一些对象逻辑上的合集,也是Pool最基本组成单位,是实现冗余策略,数据迁移、灾难恢复等功能的基础。可以向上接受、处理客户端请求,转化为能被ObjectStore理解的事务,是一个对象落到OSD上的最后逻辑载体。

因此可以看到,一个文件从客户端写入,到最终落盘,以对象存储为例,会经历以下过程:

rgw object -> rados object -> pool -> pg -> osd

而一个rgw对象被映射成一个rados对象,一个rados对象被映射到PG,一个PG映射到一个OSD中,都需要借助哈希算法,这三次哈希转变,也就是crush算法的核心。

rados对象寻址方式

当一个对象被分割成一个rados对象后,这个rados对象会跟着pg这个逻辑概念,最终被写入到若干个osd中去。那么在osd上,这个对象如何被定位?

这边需要讨论一个概念ObjectStore。ObjectStore是ceph中的底层,介于rados层和硬盘之间,目前用的比较广泛的有2种:FileStore, BlueStore。

# FileStore
使用linux文件系统作为后台,提供了文件日志功能,当rados对象通过message queue写入时,首先会写入这个file journal(wal), 然后再写入文件系统。但是文件系统本身也会做一次file journal,那么就有2次的journal写浪费。(所以就有了后面的bluestore)

ceph设计哲学与一些思考_第2张图片

由于filestore本身是文件系统,那么raodos对象会在其目录结构中存放。filestore的目录结构生成有一定的规律。比如一个rados对象通过哈希计算后,其通过对象名称的哈希逆序去组织目录,可以很方便的找到文件所在。

//我们通过osd map功能来定位一个rados对象所在的pg, 同时发现这个pg所在的osd编号为291, 147, 111。其中osd 291为主副本所在位置
[~]# ceph osd map default.rgw.buckets.data 16f6d89a-ebc2-4b84-93cc-763b1440c57b.236665132.18_123456qwer
osdmap e40580 pool 'default.rgw.buckets.data' (15) object '16f6d89a-ebc2-4b84-93cc-763b1440c57b.236665132.18__shadow_.W2fSlp2edVHI8V075Vmz9UojvQcMhwd_2' -> pg 15.3a9a6a5a (15.2a5a) -> up ([291,147, 111], p291) acting ([291,147, 111], p291)

//通过osd find来找到这个osd 291所在的节点ip
[~]# ceph osd find 291
{
    "osd": 291,
    "ip": "10.125.137.5:6838\/304721",
    "crush_location": {
        "copy-set": "cs01",
        "host": "d-j01-osd-04",
        "host-group": "cs01-hg01",
        "root": "apple"
    }
}

//登录该节点:DIR按照15.2a5a逆序
[~]# ls /var/lib/ceph/osd/ceph-291/current/15.3a21_head/DIR_A/DIR_5/DIR_A/DIR_2/ | grep 16f6d89a
16f6d89a-ebc2-4b84-93cc-763b1440c57b.236665132.18__shadow_.W2fSlp2edVHI8V075Vmz9UojvQcMhwd_2

# BlueStore
为了提高Ceph的写入性能,社区开发了BlueStore这一存储后端,让数据绕开了本地文件系统直接裸盘,从而解决了Journaling of journal问题。BlueStore采用RocksDB来管理对象元数据,因此为了跑RocksDB,BlueStore内部有一个微型用户态的文件系统-BlueFS。得益于RocksDB,Ceph可以很方便的获取和枚举所有的对象, Rados对象在硬盘中的寻址也依赖这个RocksDB。

ceph设计哲学与一些思考_第3张图片

对象存储索引方式

虽然在上文提及,ceph并没有一个中心元数据服务器来记录所有对象的信息,但是ceph仍然为每个存储桶绑定了一个索引,该索引以omap的形式记录了这个存储桶下的对象元数据信息。当RGW有list请求过来时,那么就会去通过这个索引去获取这个存储桶下所有对象的名称以及其元数据。另外,当集群使用多数据中心同步、存储桶审计功能时,都需要依赖这个功能。
而每个存储桶的索引也是一个rados对象,被存放在default.rgw.buckets.index这个存储池中,最终会存放到这个rados对象对应的osd的leveldb这个kv数据库上。不难想象,如果一个存储桶下面有海量对象的话,这个rados对象会非常大,遍历势必会很耗时,因此ceph支持了索引对象分片(bucket sharding),可以将一个存储桶的索引对象分割成多个分片,然后保存到不同osd上。并且如果索引分片不够大,也可以通过resharding来调整,但是reshard会阻塞这个存储桶的业务,并不是很理想。
ceph采用leveldb在我看来也是对其设计哲学的一种践行。leveldb采用lsm算法,来将多个随机写用类似归并的方式合并成一个大的日志文件,然后顺序写入到磁盘中,大大提升写入性能。但是也牺牲了一定的读性能。
更要命的是,ceph的早期leveldb的迭代器设计不好。当有遍历请求来的时候,迭代器会遍历这个leveldb,并且在访问时不断分配内存,直到事务完成,迭代器用完后内存才会释放。因此如果一个存储桶有上千万个对象,那么每次list请求会让这个leveldb所在的osd吃紧大量内存高居不下,会导致osd退出。
为了规避这个问题,需要在硬件、软件、使用上都做好准备:

  • 用ssd盘来加速default.rgw.buckets.index存储池
  • osd所在节点的内存要保证够大
  • 优化ceph的leveldb迭代器实现,在大的事务中,要分段迭代,完成一定数量的遍历后,要记录当前位置,然后释放这个迭代器,释放内存,重启一个新的迭代器,由此来控制内存的使用

缺憾

1.list会比较慢,因为对象存储这边没有目录树的概念,如果要获取某个虚拟目录下的孩子信息,那么需要遍历以这个虚拟目录地址为prefix的所有对象,来后做一遍过滤。
2.rbd目前只支持cow,未来希望还可以实现row

你可能感兴趣的:(ceph设计哲学与一些思考)