Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1748958
  • 博文数量: 152
  • 博客积分: 3730
  • 博客等级: 上尉
  • 技术积分: 3710
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-02 14:36
个人简介

减肥,运动,学习,进步...

文章分类

全部博文(152)

文章存档

2016年(14)

2015年(17)

2014年(16)

2013年(4)

2012年(66)

2011年(35)

分类: C/C++

2016-01-22 19:03:17

      Ceph通过Crush算法使得其与传统的存储系统有较大的区别,通过Crush算法能够快速的定位出对象的位置,减少了系统对大量元数据的保存。同时Crush是可以定制的,同步定制不同的Crush映射就能确定不同池的对象保存和定位方式。Crush的定制也是比较复杂的过程。虽然目前提供了通过命令行设置Crush的方式,但是在大型的系统中通常都是通过编辑crush文件的方式完成。下面部分主要是对官方文档的部分翻译和理解。
      CRUSH
算法通过计算的方式确定对象的存储和检索位置,CRUSH授权Ceph的客户端直接与OSD交流,而不必要通过中心服务器或代理的方式交流。Ceph通过算法的方式确定存储和检索数据避免了单点故障、性能瓶颈、以及扩展时的物理限制(主要是指中央服务器或交换机)Crush需要一个集群的映射,并使用该CRUSH映射伪随机的在集群的OSD间进行均匀分布的存储和检索对象。Crush映射中包含了一个OSD设备的列表,一个聚合存储设备到物理位置的容器列表,一个告诉CRUSH如何在Ceph集群池中拷贝数据的规则列表。通过分析底层物理设备的组织,CRUSH可以通过建模从而解决由于相关设备故障的潜在故障源头。这些故障源头可能包括物理距离、共享的电源、共享的网络等。通过将这些信息编码到集群映射中,CRUSH的放置策略可以分散对象拷贝到不同的故障域并维持希望的分布。例如为了解决可能的并发故障,最理想的是拷贝数据的设备来自不同的机架、机柜、电源、控制器以及位置。

      采用ceph-deploy方式创建配置文件并部署Ceph时,Ceph为对应的配置产生了一个默认的CRUSH映射。默认的CRUSH映射在测试环境下是不错的,但是当部署一个大规模的数据集群时,应该经过深思熟虑的开发和定制一个CRUSH映射,这将对管理Ceph集群非常有帮助,同时也能提高性能和提高数据的安全性。比如:若一个OSD Down,在需要支持或修复映射时,Crush映射将可以帮助定位出具体的数据中心,房间、行、拥有该OSD主机的机架。同时CRUSH可以帮助快速的定位故障。可以帮助定位处于退化状态的PG关联的主机的物理位置。

     按照CRUSH映射的层次来描述OSD的位置将被称为Crush位置,该位置说明符将采用键值对列表的形式标识位置:如果一个OSD是一个特定行、机架、主机,是default Crush树的一部分,则该OSD的定位信息就是: root=default row=a rack=a2 chassis=a2a host=a2a1
关于该定位信息的键值顺序是无关紧要的,但是对应的键值(=左边的键名)必须是一个合法的CRUSH类型。这些类型可以是root, datacenter, room, row, pod, pdu, rack, chassis, host等,通过修改CRUSH映射把这些类型定制成任何合适的,且不是所有键都需要。

1   编辑CRUSH映射的步骤

       >> 获取当前的CRUSH映射, 可以通过如下的命令获取 ceph osd getcrushmap -o compiled-crushmap-file, 该命令将集群的映射导出到文件中,但是该文件时编译后的,需要反编译之后才能编辑。

       >> 反编译当前的CRUSH映射, 可以采用如下的命令反编译: crushtool -d compiled-crushmap-file -o decompiled-crushmap-file, 将反编译后的映射输出到文件中,该文件是能够识别的,可以编辑。

       >> 编辑设备、桶、或者规则,至少需要编辑一条,这部分是最重要的,也是CRUSH的重心, 见后面的分析。

       >> 重新编译新的CRUSH映射, 可以通过如下的命令编译修改后的CRUSH映射。crushtool -c decompiled-crushmap-file -o compiled-crushmap-file

       >> 设置对应的CRUSH映射, 可以通过如下的命令实现: ceph osd setcrushmap -i compiled-crushmap-file 为了激活一个CRUSH映射到一个特定的池中,将公用的规则集合ID设置到特定的池。


1.1    CRUSH映射的参数

       在CRUSH映射中主要存在4个主要的区域:

       Devices: 包含了任何类型的存数设备,通常是device-xx, 对应的存储设备关联到一个ceph-osd守护进程。因此在配置文件中为每个OSD进程分配一个设备。为了映射PG到不同的OSD,因此CRUSH映射需要OSD设备的列表,设备名从配置文件中获取,因此在第一次部署过程中尽可能的完善配置文件。设备列表在CRUSH映射的第一项。在CRUSH映射中声明设备,只需要在设备列表下创建一行,输入device xxx, 并关联到一个ceph-osd实例,其中OSD进程通常映射到单个存储设备或RAID。如:device 0 osd.0      

       Bucket Type: 装置类型定义了在CRUSH层次使用的桶类型。桶由一个分层聚合的存储位置构成(如行、机架、底座、主机等),并分配该对应的权重。

       Bucket Instances: 一旦定义了桶的类型,必须为主机声明桶实例,并选择任何类型的故障分区。第二个列表是CRUSH映射定义的桶类型。桶构成了节点和叶子之间的层次性。节点桶通常代表了层次中的物理位置,节点由叶子节点和其他的节点聚合而成,叶子节点桶代表了ceph-osd进程关联的存储设备。添加桶类型到CRUSH映射,需要在类型列表后添加: type {num} {bucket-name}

       Rules: 规则包含了选择桶的方式。

       如果启动Ceph采用ceph-deploy的方式,并不需要创建CRUSH映射,Ceph-deploy将根据配置文件中列出的设备产生一个默认的CRUSH映射。依据在Ceph配置文件的osd分区为每个主机声明一个桶。但是为了更好的宝座数据的安全性和可用性应该创建拥有反应集群故障域的CRUSH映射。默认生产的CRUSH映射没有考虑大颗粒的故障域,在生产环境中应该尽可能考虑到更大范围的故障域。比如机架、行、数据中心等。

       Crush算法依据每个设备的权重值分散数据对象到不同的设备中,近似一个统一的均匀分布,根据集群中定义的集群映射层次关系分布对象和拷贝。CRUSH映射代表了可用的存储设备和设备之间的逻辑关系。为了映射PG到交叉故障域的OSD中,CRUSH映射定义了一个有层次的桶类型,创建桶的层次的目的是使得叶子节点通过他们的故障域隔离。除了叶子节点代表了OSD,其他的层次都是任意的,完全根据需求定义需要的层次。推荐使用物理设备的名字来定义层次。

      由于叶子节点反应了存储设备,而这些设备在devices列表中已经声明,因此不需要再次声明这些设备作为桶实例,在层次中叶子之上的桶都是桶节点或叶子节点聚合而成,通常第二底层的是主机层(可命名为host, computernode),在高密度的环境中通常多台主机占用一个电源底座,因此通常应该考虑底层的故障。声明一个桶实例,实现必须指定桶的类型,并给定一个唯一的桶名,分配一个整形的唯一ID,将其所有子项的权重和作为当前桶实例的权重,指定对应的桶算法(通常有straw)hash的方法(通常为0,即选择rjenkins1算法)、指定该桶中的一个或多个子项(item),这些子项可以是子节点,也可以是叶子节点。子节点的权重反应了子节点的权重。

点击(此处)折叠或打开

  1. [bucket-type] [bucket-name] {
  2.       id [a unique negative numeric ID]
  3.       weight [the relative capacity/capability of the item(s)]
  4.       alg [the bucket type: uniform | list | tree | straw ]
  5.       hash [the hash type: 0 by default]
  6.       item [item-name] weight [weight]
  7. }


目前对于桶的算法至少支持4种,每种都代表了一种性能和重组的消息权衡。在不确定使用哪种算法时,推荐使用straw算法:

      >> Uniform: 该桶的设备通常都有完全相同的权重,通常设备都有完全相同的物理配置。

      >> List: 该桶采用链表的方式聚合所有的子项,最大的优点是进行数据迁移性能最好。

      >> Tree: 采用二叉树的方式,当该桶的子项特别多时性能相比List的效率要高很多,减少了放置的时间。

      >> Straw: ListTree的结合版本。

每个桶都会使用一个Hash算法,目前支持rjenkins1, 通常都是设置为0,即选择了rjenkins1

      有关桶的权重:采用双精度小数作为权重,权重是指设备容量的相对差异性。通常将1TB的存储设备的相对权重设置为1.00。按照这种方案即可计算出不同存储设备的相对权重。高层次的桶的权重是其子项的权重之和。因此权重可认为是该节点的存储相对容量,但是这个容量并不是一定的,存在不同速率的OSD时,可将高速的设置的权重较大,而低速的权重较小,但保持两者和的权重不变即可。

1.2    CRUSH映射的规则

      CRUSH支持CRUSH规则的概念,在某个池中该规则决定了数据的放置位置。在大型的集群中 可以创建多个池,每个池都可以拥有其自己的CRUSH规则集合和规则。默认的CRUSH映射中每个池都有一个规则,并为默认的池分配了一个规则集合,目前的默认池只有rbd

CRUSH规则确定了放置和复制的策略或分布的策略,允许用户指定CRUSH如何放置对象副本。规则的形式如下所示:

点击(此处)折叠或打开

  1. rule <rulename> {
  2.       ruleset <ruleset> <<----用于分类规则归属的规则集合,通过设置规则集合到池就能激活。默认为0
  3.       type [ replicated | erasure ] <<----描述了规则适用于复制还是纠删码,只能是replicated和erasure中的一个,默认为replicated
  4.       min_size <min-size> <<----如果当池选择了小于该值的拷贝数,CRUSH将不选择该规则,必须的选项,默认为1
  5.     max_size <max-size> <<----若干当池选择了大于该值的拷贝数,CRUSH将不选择该规则,必须选项,默认值为10
  6.       step take <bucket-type> <<----选择一个桶的名,从该桶迭代下面的数。
  7.       step [choose|chooseleaf] [firstn|indep] <N> <bucket-type>
  8.       step emit <<----用于输出当前的值并清空堆栈,通常使用在规则的尾部,但是也可能采用相同的规则从不同的树中选择。跟随着step choose
  9. }


      其中的step choose firstn {num} type {bucket-type}, 选择一定数量给定类型的桶,该值通常是该池的拷贝份数。如果{num}==0,则选择pool-num-replicas个桶,若{num} > 0 && < pool-num-replicas个桶,则选择num。如果{num} < 0, 意味着选择pool-num-replicas - {|num|}。通常跟随着step takestep choose

      step chooseleaf firstn {num} type {bucket-type}, 选择给定类型的一个桶集合,并从该集合中的每个桶中选择一个叶子节点,其中的桶个数是池的拷贝份数。如果{num}==0,则选择pool-num-replicas个桶,若{num} > 0 && < pool-num-replicas个桶,则选择num。如果{num} < 0, 意味着选择pool-num-replicas - {|num|}。通常跟随着step takestep choose

      在某个池中激活某个通用规则集合中的规则需要设置pool的规则集合ID

      在Ceph中客户端读或者写数据都会直接同PG中的主OSD沟通,但是有些时候一些慢速的OSD相比快速的OSD并不是非常适合作为主OSD。为了最大化的使用硬件,并防止性能瓶颈,可以设置慢速OSD的primary affinity(主OSD的姻亲),这样CRUSH将不太可能选择该OSD作为主OSD。通过如下的命令执行: ceph osd primary-affinity 其中的权重默认为1,但是可以设置在0-1之间,0表示该OSD不能被作为主OSD,1表示该OSD可能被选为主OSD,在小于1时,将有比较小的可能选为主OSD。

2    Crush映射定制

      在默认情况下,存在一个Crush映射,集群都是通过该Crush映射确定对象的存储位置。默认的Crush映射包含了当前的设备列表、一系列的类型,一系列的host实例,host实例中包含了该设备中的所有osd子项,一个默认root实例default,最后还包含一个默认的规则replicated_ruleset,该规则的功能就是从不同的host主机中选择不同的osd节点。这是默认的选择规则,在创建了纠删池的集群中,通常会创建默认的规则erasure-code,默认的纠删池osd选择都是基于该规则进行OSD的选择OSD这些默认规则能够实现数据到不同OSD的均匀分布,但是在实际环境中通常将需要创建属于自己的Crush映射。

2.1    定制新的类型

      可以选择属于当前属于自己的类型实例,比如在集群中创建基于SSDroot实例,基于Flash卡的root实例,以及基于HDD磁盘的root实例。如下定义了两个新的root实例:

点击(此处)折叠或打开

  1. root ssd {
  2.         id -5 # do not change unnecessarily
  3.         # weight 0.04
  4.         alg straw
  5.         hash 0 # rjenkins1
  6.         item node01 weight 0.040
  7. }
  8. root hdd {
  9.         id -6 # do not change unnecessarily
  10.         # weight 0.080
  11.         alg straw
  12.         hash 0 # rjenkins1
  13.         item node02 weight 0.040
  14.         item node03 weight 0.040
  15. }


      以上添加的两个root实例是指创建了一个ssdroot实例,该实例由是node01构成,即服务器node01用于ssd设备。一个hddroot实例,还实例是由node02和node03构成,这两个服务器中的存储节点都是hdd,该例子中子节点(item)是服务器。

     需要注意在定义类型实例时需要将id设置为负数,在ceph中只有设备的id是大于等于0的。而其他的类型实例的id通常都是小于0的。即桶的id都是负数,在crush映射的代码实现中就是通过id值来判断桶是否为叶子节点(OSD节点为叶子节点),即对于非叶子节点的实例都必须为小于0id

     实际上类似实例的子项(item)可以由更加丰富的类型构成,比如可以将三台服务器中分别部署一块SSD、多块HDD磁盘等。此时可以构建基于设备的ssd实例、hdd实例。此时的item的类型就是osd,不再是之前的host。具体定义方式如下所示:

点击(此处)折叠或打开

  1. root ssd {
  2.         id -5 # do not change unnecessarily
  3.         # weight 0.060
  4.         alg straw
  5.         hash 0 # rjenkins1
  6.         item osd.0 weight 0.020
  7.         item osd.1 weight 0.020
  8.         item osd.4 weight 0.020
  9. }
  10. root hdd {
  11.         id -6 # do not change unnecessarily
  12.         # weight 0.060
  13.         alg straw
  14.         hash 0 # rjenkins1
  15.         item osd.2 weight 0.020
  16.         item osd.3 weight 0.020
  17.         item osd.5 weight 0.020
  18. }


     上面例子中实现了两个root实例,这些实例由osd节点构成,不再是上面基于服务器的结构。这说明可以在映射中形成非常丰富的类型实例。类型实例实际是一个存储系统中单元的抽象。比如在一个大型的存储系统中,可以创建多层次的类型实例:存储设备--->服务器--->电源--->机架--->行--->机房--->数据中心--->根节点,这些层次实际是一系列的类型,层次的成员构成实际就是一系列的类型实例,而某个类型实例的子项通常都是其下一层次类型的实例构成。因此在实际使用过程中就是对这些类型实例的拼接成一个满足需要的存储系统。类型实例的构建实际是集群局部范围或故障域的定义。类型实例的定义和涉及也是在Ceph中构建高可用性存储的重要部分。

2.2    定制新的规则

      在上述的部分中说明了可以定制一系列的集群空间,各个空间可以由一些了的子空间构成,在类型的定制中实现了不同的故障域,而在实际存储中如何将数据局限在一定范围内,如何在高速和低速设置之间进行分层,如何提高系统的抗故障能力等都是通过Crush的规则实现的,即通过定制合适的规则确定具体需要的对象存储方式。

      目前Ceph中的存储是分配是基于池的,即可以通过定制池的存储分配规则。关于规则的定义主要是故障域的选择。关于故障域可以选择在不同主机之间、不同存储之间、不同数据中心之间等,也可以选择主OSD节点的设备为快速、高价格的SSDFlash卡,而第二OSD、第三OSD为低速的HDD磁盘,在高性能的场景中甚至都处于SSD中,有关这些都是可以通过规则来完成。以默认的Crush规则来说明:

点击(此处)折叠或打开

  1. rule replicated_ruleset {
  2.         ruleset 0
  3.         type replicated
  4.         min_size 1
  5.         max_size 10
  6.         step take default
  7.         step chooseleaf firstn 0 type host
  8.         step emit
  9. }


      该规则在之前的编辑Crush中已经说明,这部分主要说明step的几个操作,首先确定从哪个根节点(root)进入,该规则中选择了default作为根节点,然后以host实例为故障域,即在不同的host中选择合适的OSD集合作为保存对象的存储设备,这种选择实际上就确定了对象的副本保存在了不同的主机中,这样对象的故障域是基于主机的。step chooseleaf firstn 0 type host就决定了选择叶子节点的故障域为host类型。由此可见,最简单的规则实际上是选择合适的根节点,然后根据需要的故障域选择叶子节点。根据之前的SSD和HDD的root实例,可以构造将对象全部保存到SSD中或HDD中的规则,具体的规则如下所示:


点击(此处)折叠或打开

  1. rule ssdrule {
  2.         ruleset 3
  3.         type replicated
  4.         min_size 0
  5.         max_size 4
  6.         step take ssd << --从ssd根节点出发
  7.         step chooseleaf firstn 0 type osd << --故障域为osd,选择多个osd作为osd集合来保存对象的多备份
  8.         step emit << --输出对应的选择
  9. }
  10. rule hddrule {
  11.         ruleset 4
  12.         type replicated
  13.         min_size 0
  14.         max_size 10
  15.         step take hdd << --从hdd根节点出发
  16.         step chooseleaf firstn 0 type osd << --故障域为osd,选择多个osd作为叶子节点
  17.         step emit
  18. }


      从上面的两个例子中分别从不同的根节点出发,然后从需要的故障域开始选择合适的叶子节点(设备)。例子中采用了OSD故障域,即在hdd的osd设备集合中选择一定数量的设备。实际上这边的故障域是任意的,可以是基于电源的、机架的、机房的、甚至是基于数据中心的,故障域的选择需要根据之前定义的类型实例来选择,即根据类型实例的子项构成选择出合适的故障域。比如在2份拷贝的复制池中,在只有一个数据中心的集群中选择故障域为数据中心,则会导致对象无法正确写入,即无法找到另一个数据中心的存储设备保存数据拷贝。虽然无法选择基于数据中心的故障域,但可以选择基于机房的、基于电源的等多种方案。因此在故障域的选择中需要根据之前确定的类型实例。

     在复杂的场景中甚至可能需要从多个根节点出发,选择出多层次的存储结构,比如在3拷贝的复制池中,为了存储系统的性价比,将一份拷贝保存在SSD中,而另外两份保存在一般的HDD盘中,这样就能达到较好的性能以及合适的价格。以该需求为例,可以设置如下的规则:

点击(此处)折叠或打开

  1. rule ssd_primaryrule {
  2.         ruleset 5
  3.         type replicated
  4.         min_size 0
  5.         max_size 10
  6.         step take ssd << --从ssd根节点出发
  7.         step chooseleaf firstn 1 type osd << --从ssd整个系统中选择一个OSD作为主OSD
  8.         step emit << --输出选择,清除栈空间
  9.         step take hdd << --重新从hdd根节点出发
  10.         step chooseleaf firstn -1 type osd << --从hdd中选择处剩下的需要的OSD
  11.         step emit
  12. }

     该例子首先从SSD根节点出发,选择出一个OSD作为该PG的主OSD,然后重新从HDD根节点出发,选择出其他的OSD保存其他的拷贝,这样就将PG的主OSD选择在了SSD路径中,而其他的OSD选择在了HDD路径中,其中的故障域只是选择了OSD,实际上可以在实际使用时选择合适的故障域。这样也就实现了存储数据的分层控制,对于多拷贝的情况甚至可以定制更加复杂的保存方案。规则的定义使实际上就完成了类型实例的拼接,或者称为层次的构建。

     根据以上的分析可知,在CRUSH的定制中主要是理解类型实例的定制。类型实例实际上就决定了Ceph存储的故障域。而存储规则的定制实际上是从一系列的类型实例中设定出一个满足需求的存储系统。只有理解这两个关键部分的定制,才能在复杂的集群系统中完成高性能、高性价比、高稳定性等不同需求的Ceph存储系统定制。



阅读(3940) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~