ceph的数据存储之路(10) -----ceph对象存储的ls命令实现及思考

更新:2016-10-19———————————————————————————————————————

前面更新的内容可能略有偏颇,原因是忘记了radow gateway这个东西,这对于对象存储很关键。一般的对象存储系统都不实现文件系统提供open、close、read、write和lseek等接口,对象存储有简单的接口,对应rest API风格,这也是自己近期经历一个rest API项目明白,rest API 类的接口只有简单的 get、put、delete等操作。这些操作不允许你针对一个对象进行部分获取,只能get整个object,修改后再上传到对象存储中。而且对象存储的使用场景特点来看,都是用于比如发布的微信状态、微博等,网站图片啊,一旦上传就不会修改,想要修改只能先删除,再重新发布。

包括以上的原因等,使得对象存储不需要使用目录树来管理对象(文件系统是需要简历目录树的),从一定的角度来看,目前开源swift、ceph都没有使用目录树来管理对象,扁平的object管理模式,一般实在bucket或者pool中存放,ceph还涉及到pg层,所以一般对象存储使用2~3层的模式管理object,这在文件系统可了不得,在文件系统中的一个文件夹下文件数量超过一个值,会让整个文件夹性能下降的非常厉害,超多的文件inode会让内存等撑爆,这也是hdfs等不支持大量的小文件原因,而对象存储ceph中,在一个pool中存储再多的文件数量对性能也不会太大的影响。

有人测试过对象存储和文件系统在同一个文件夹下存储文件,文件个数达到上亿后,文件系统完败,地址忘记了。。。。

ceph对象存储网关会使用swift或s3 API来访问对象,这 个网关会自己维护一个索引,这个索引包含了object的一些信息,当用户使用ls命令时,网关会在自己维护的索引中进行查找,相比去查询文件系统的目录树,提高了很多的性能。ceph网关将自己的索引维护在一个叫做.rgw.buckets.index的pool中(为和普通的pool区分,这里叫做index pool),该index pool中对于其他正常的pool都会生成一个对象.dir.defaukt.xxxxx.x的object中,在该object的omap中会保存 正常pool中object的数据信息,这些信息保存在levelDB中,当ls时,直接从levelDB中获取,根本不需要遍历所有的object,同样swift 会将object维护在container中,所以对象存储在ls方面很快,但是如果在文件系统中,对于上亿个文件的文件夹中使用ls,那你可有得等了。

 

更新:2016-10-17———————————————————————————————————————

之前自己 想不明白的问题,今天有了写想法,重新来说说,这位大神问的问题:“你能说说ceph这种对象存储,ls命令是怎么实现的?对象存储可以保存大量的对象,怎么才能快速的ls出所有对象的信息?”

今天,有人问了我这个问题  https://www.oschina.net/question/252560_2201216,在回答问题前 我仔细的想了一下,为什么ceph对象存储,ls命令可以快速列出所有对象的信息?平时我们感觉文件系统ls也很快啊,没感觉有什么神奇的。所以没关注过。但是今天在这里从新说一下

为什么会有对象存储? 同样都是文件,存在文件系统里不是一样的么?

这是因为当文件数目少时,他们差别不大,当存在大量的小文件时,比如淘宝的图片数据。海量图片文件。如果保存在文件系统中,你再去ls查看,会慢死。而对象存储中保存,使用ls会非常快速。

ls命令本身是查看文件元数据(文件大小,更新时间,属性等)的,在文件系统中,元数据和图片本身的数据在混在一起存储的,如果想要获取所有小文件的元数据,熟悉文件系统的朋友都知道,那可是费了劲儿,要到处找元数据所在的位置,遍历吧,所以时间会很长,慢死。但是在对象存储中不同,元数据和图片数据分开保存,可以直接读取元数据,不用区分元数据和图片数据,所以对象存储很快。

今天明白了,还是自己知道的太少,要多请教的。上面说的不恰当之处,请大家多加指点。

旧:—————————————————————————————————————————————

曾经被一个前辈问过:“你能说说ceph这种对象存储,ls命令是怎么实现的?对象存储可以保存大量的对象,怎么才能快速的ls出所有对象的信息?”

这个问题我不知道该怎么样来回答。脑子有点空白,这个问题是不是可以拆成如下两个问题:

问题1. 分布式对象存储和分布式文件系统存储相比较,当保存大量的对象文件或者文件时,对象存储的ls的速度要快,这是为什么?

问题2. 分布式对象存储的ls实现与分布式文件系统的ls实现分别是什么样的?

 

回答:

对于问题1:

      我没有测试过,所以我也不知道谁更快。但是二者的数据都是在内存中,对象存储的这部分数据分散在很多的osd节点上,文件系统的这部分数据都保存在mds上。对于对象存储时可以将ls分明分成多个任务发送到不同的osd上检索,最后合并结果。对于文件存储只能在mds单一节点上完成。这个是速度的关键么?如果有人懂得这个问题,请回答下,非常感谢。

对于问题2:

      ceph的对象存储检索方面,主要涉及到两点一个是rbd ls实现,一个是ceph ls实现。

rbd ls 实现比较简单,可以直接读取rbd_directory这个对象就可以了,该pool下面的所有的rbd都在这个对象文件中保存。

rbd命令从 rbd.cc中的main函数开始,开始解析命令,使用函数get_cmd。

165431_Tb5J_2460844.png

2505:开始解析命令参数。

2508:是否是ls 或者list命令。

2510:在这里解析为 OPT_LIST。完成后再回到main函数中

165812_hzNt_2460844.png

3349:解析参数opt_cmd。

3351:case 解释参数OPT_LIST。

3353:如果是ls 或者list rbd的信息,这里直接调用do_list()函数。

165812_tW0f_2460844.png

0280:执行do_list函数。

0284:调用rbd.list() 获取所有的rbd名字,放在容器names中。

165812_AV9u_2460844.png

0199:执行rbd.list()函数。

0202:调用librbd::list()函数,继续获取rbd名字的列表。

165812_sZ5F_2460844.png

0430:这里开始执行librbd::list()函数。

0436:读取指定的object,object的名字叫做RBD_DIRECTORY,这是个宏定义,解释出来就是rbd_directory文件。返回的结果保存在bl的buffer中。接下来在对bl中的数据进行解析出rbd name就好,然后将name全部都放在names中。返回结果。

 

总结rbd ls命令其实没有真正的去搜索所有的rbd名字,而是只读取了一个文件,就可以解析出所有的rbd名字列表。这个是不是快速的原因呢?

 

如果不使用rbd形式,而是直接作为object存储呢? 可以使用rados客户端或者网关实现对象文件的上传动作,需要把对象文件上传到指定的pool中。最终会根据对象文件的名字保存到一个pg中。由文件名字与pool的名字根据crush算法映射到一个pg中去,pg实际上是一个文件夹,一个pg中的所有的object文件都保存在这个文件夹下。

所以 这里跟踪下“rados  –p testpool  ls“ 命令,看看如何ls出指定pool下面的对象文件。

命令开始于rados.cc中的main函数。该main函数中最后调用了rados_tool_common()来处理命令,继续看rados_tool_common中的处理。

165812_Hj7r_2460844.png

1522:直接从这里开始看,前面都是参数解析和其他的操作。这里命令ls准备列举出所有的object对象。

1524:如果没有指定pool的名字,是返回错误的。所以你想ls出哪个pool中的object。

1548:获取所有的object头部,然后从头部开始列出,输出到outstream中。这里是重点,后面继续描述。

1550:循环所有的object对象,然后将object对象的信息全部都输出到outstream中。

 

来看看在1524行的代码 io_ctx.nobjects_begin();这个函数是如何返回所有的object链。

165813_YECv_2460844.png

1533:申明一个用于保存所有object的链 的头部listh。

1534:填充一些信息,用于获取object。

1535:声明一个object的迭代器。使用迭代器获取object。

1536:获取所有的object信息。并且保存。iter.get_next() -> impl->get_next()

165813_2Njf_2460844.png

0626:调用get_next函数,准备获取链表。

0636:调用rados_nobjects_list_next()开始获取。来到该函数中,查看下面的信息

165813_bi5f_2460844.png

3568:判断是不是链表是空的,这里空的代表之前没有获取过。

3569:这里调用librados::IoCtxImpl::nlist()函数。设定最大值等参数。

165813_3nlS_2460844.png

0366:设置最大的链表值。

0367:设置nspace的值。

0369:调用objecter->list_nobjects()函数,准备设置获取后的回调工作。

0371:加锁,等待完成操作唤醒。

0372:判断获取是否完成。

0373:等待获取完成,等待被唤醒。

0374:解锁操作。

 

接下来再看list_nobjects()中的一些处理情况。

165813_H86B_2460844.png

3286:发送请求必须有一个objectoperation的op操作。

3287:设置对于pg的操作。主要的是为op添加一个操作,CEPH_OSD_OP_PGNLS。

3289:清空list列表的缓存信息。

3290:设置应答回调信息。这里是非常重要的一个设计。list_context是继续其他pg遍历的处理回调,onfinish是所有pg完成遍历的处理回调。后续会介绍下C_NList中的finsh函数的处理。

3291:设置object目标的定位信息。这里不是为了发送到某个object,而是发送到pg处理即可。

3293:开始设置设置读取current_pg中的object列表。这时 pool_id确定,pg_id确定,可以根据crush算法知晓current_pg所在osd-set的位置,然后选择一个osd进行读取,后续命令封装的过程就不再详细解释了,拿着CEPH_OSD_OP_PGNLS标记直接去osd的代码中查看流程。

 

在replicatedPG.cc 中,函数do_pg_op()中会对CEPH_OSD_OP_PGNLS标记进行解释。case CEPH_OSD_OP_PGNLS。

165813_0L4M_2460844.png

0859:继续调用objects_list_partial进行处理。这个也是最重要的处理。

继续跟踪,来到代码的内部int PGBackend::objects_list_partial()

165814_O3Qa_2460844.png

来到这里是不是就非常的明了呢?

0128:循环获取object,由于每次获取object的数量有限,可以分多次完成。

0131:这里可以看的出,参数coll就是pg的目录,这个就是在扫描pg这个目录下的文件名字,然后当作是object,放在objects的结构中。这个扫描过程很简单了,继续向下的代码FileStore::collection_list_partial()中可以找到答案。由于目录内部的文件都已经建立了index,所以扫描起来也比较快。

 

还有一点需要说明的是,这只是获取其中一个pg的object信息,然后还需要处理其他的pg中object信息,对所有的object信息进行整合,返回给用户。什么时候开始处理的其他pg的object扫描呢?在读取pg的object时,设定了一个onack 回调函数,该函数具体由 list_nobjects()中申请的C_NList代替。这个C_NList的声明时使用了C_NList *onack = new C_NList(list_context, onfinish, this); C_NList继承自context类,所以它具有回调的属性。在C_NList中看如何回调的。

165814_94Ou_2460844.png

这个是C_NList设置的回调函数。这里有两个分支。参数r是本次处理结果是否正常。

1364:如果处理的结果正常,则需要继续处理扫描其他pg中的object信息。

1367:如果出现了错误等,则直接返回错误消息,不再处理其他pg的object信息。

Objecter::_nlist_reply 函数中会重新调用list_nobjects(list_context, final_finish),继续扫描其他的pg的object信息。直到完成后,会调用final_finish->complete(0);这个在代码中很容易看到,既然所有的object都已经扫描完成后,还需要这个回调做什么?

 

还记得在librados::IoCtxImpl::nlist()函数。

165814_pSgu_2460844.png

0373:这里等待被唤醒,才能返回用户的请求线程继续处理。

所以需要在final_finish->complete(0);中实现对cond.wait 的唤醒操作。

这里的final_finish就是0369这里声明的C_SafeCond 回调,该回调中会唤醒0373行的等待,0373行被唤醒后继续执行,最后返回给使用命令ls的结果。

  

 

总结:

1.rbd ls 命令列举某个pool中的rbd。会直接读取pool中的rbd_directory对象文件,该文件中保存了该pool中的所有的rbd名字信息。

2.rados ls命令列举某个pool中的object。先找到pool,并且读出pool中pg的数量,然后再遍历每个pg,读取每个pg下面的object。合并结果就是所有的object。这个做法必须一个一个pg的去扫描,并不能做到并行扫描。

 

根据以上两点可以知晓,rbd ls的操作比较简单直接读取目标文件即可。rados ls的操作需要串行的扫描pool中的每个pg中的object。

 

如果你问我rados ls这样的命令是怎么实现的,我可以讲述上面的实现的过程。

如果你问我对于文件系统的ls方式,我也可以讲述一下过程。

至于这两种方式的比较优势,还需要高人指点下,希望大家不吝赐教。

 

转载于:https://my.oschina.net/u/2460844/blog/669769

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值