|
|
本文先介绍 Ceph, 然后会聊到一些正确使用 Ceph 的姿势;在集群规模小的时候,Ceph 怎么玩都没问题;但集群大了(到PB级别),这些准则可是保证集群健康运行的不二法门;
1 O6 Z+ L4 g% j- x" K
N8 i/ s# r3 y/ ~Ceph 最初的目标是做一个分布式文件系统,直到现在这个目标也不能算完美实现;目前官网上对它的文件系统还是谨慎推荐的态度(不建议对线上核心业务部署);
. ]6 I, j/ J0 N( ~
4 K, y% a6 G& N2 i8 t业界使用 Ceph ,大多是用它的对象存储;
1 I- |' V4 s8 o
( S; S+ {& y$ A# ?Ceph 客户端) M: f6 q2 O* y7 g9 w
Ceph 支持三种存储接口:对象存储 RGW(rados gateway)、块存储 RBD(rados block device) 和文件存储 CephFS;8 C" F* E7 j5 Z4 Q/ ~8 O
5 @- d; @( `, @7 q2 m! S
这三个接口只是在客户端的封装库不同,到服务端了都是对象存储;: u. O& B% \ l" ]. p% A! {
~; _ G9 [( J! k6 D( a& X8 H
* }, ~0 e) P( m$ w对象存储(RGW:RADOS gateway)
3 D3 ]; W6 i- Q/ Y2 A3 SCeph 对象存储服务提供了 REST 风格的 API ,它有与 Amazon S3 和 OpenStack Swift 兼容的接口。也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL和其他扩展;. A% H' s) V) z/ {
' ?8 M3 K# e: t# ^7 O块存储(RBD:RADOS block device)
- Y, _9 C5 h! J9 x9 Z' ]$ LRBD 是通过librbd库对应用提供块存储,主要面向云平台的虚拟机提供虚拟磁盘;RBD类似传统的SAN存储,提供数据块级别的访问;
- I9 ]& a, V8 {; f; ?$ r/ m0 t4 W' G( u" s |& C
目前 RBD 提供了两个接口,一种是直接在用户态实现, 通过 QEMU Driver 供 KVM 虚拟机使用。 另一种是在操作系统内核态实现了一个内核模块。通过该模块可以把块设备映射给物理主机,由物理主机直接访问。6 Z0 [3 v w/ i, T2 f
( n: q; H3 x# [8 w* d# Q
文件存储
6 J7 m) L& _' s& {+ Z' b E+ hCeph 文件系统服务提供了兼容 POSIX 的文件系统,可以直接挂载为用户空间文件系统。它跟传统的文件系统如Ext4是一个类型,区别在于分布式存储提供了并行化的能力;" u2 s% F! O1 Y4 P5 C3 L8 ]
0 P+ M5 r" C- {0 m
原生接口" t$ c' L8 F! Y$ i: i) E$ \- A
除了以上3种存储接口, 还可以直接使用 librados 的原生接口,直接和RADOS通信;6 O4 q1 t9 r+ S* d: S' A$ z
1 i& n1 W" E. y
原生接口的优点是是它直接和和应用代码集成,操作文件很方便;但它的问题是它不会主动为上传的数据分片;一个1G的大对象上传,落到 Ceph 的存储磁盘上就是1G的文件;
7 H2 v5 }; t7 f' R
3 i* E% v6 T( Z l而以上三个接口是具有分片功能(即:条带化 file-striping)& w1 }0 T+ q- P5 X5 |/ n6 X
; O) ?$ l& }5 x4 l9 W" I4 yPS:两个对象的区分, ^% I' z8 I4 C( z) U6 [& |
& N+ a5 p( z7 N7 S1 i# u需要说明下,这里提到两个对象的概念:一个是 RGW中的对象存储,一个是 Ceph 的后端存储的对象;这两个需要区分:0 i$ @3 x2 v1 d: s' o
: |, [# m9 O5 s4 }: w第一个对象面向用户,是用户接口能访问到的对象;. M( O, y% h5 y4 W, o1 D$ K
0 h- g- D. J1 i4 Y* V9 ^" W- I
第二个对象是ceph 服务端操作的对象;
9 C* f$ x. _7 H/ h7 D6 ~* p: r: b5 B* \/ b! J7 Q
eg:使用RGW接口,存放一个1G的文件,在用户接口看到的就是存放了一个对象(1);而通过RGW 分片成多个对象(2)后最终存储到磁盘上;# p( A: x. r2 v8 {: |8 y
! h9 v" N6 o$ d+ F
6 u( f$ T/ }1 e% aCeph 服务端3 n: C- C8 ~$ \7 `$ O8 T! m
' J6 [/ t) Y3 `4 ^) J
) `# U$ ]+ {: N, f+ m+ L+ N服务端 RADOS 集群主要由两种节点组成:一种是为数众多的、负责完成数据存储和维护功能的OSD(Object Storage Device),另一种则是若干个负责完成系统状态检测和维护的monitor。+ y! k; x9 D c4 t
' U5 ^% _" F% T* O" K4 U
Monitor$ @# V6 K* \ w& h
Monitor 集群提供了整个存储系统的节点信息等全局的配置信息,通过 Paxos 算法保持数据的一致性。
! w G$ ]( e, u3 M4 V
0 W( v) l! E8 Q$ } m+ K5 iPool 、PG和OSD
6 z8 g) ^; l1 ]! l) v9 a0 ~) RPool是存储对象的逻辑分区,它规定了数据冗余的类型和对应的副本分布策略;支持两种类型:副本(replicated)和 纠删码( Erasure Code);目前我们公司内部使用的Pool都是副本类型(3副本);
) i+ F# H+ ^) ]: F" L% I. t; Q4 X) L7 a' }& K
PG( placement group)是一个放置策略组,它是对象的集合,该集合里的所有对象都具有相同的放置策略;简单点说就是相同PG内的对象都会放到相同的硬盘上; PG是 ceph的核心概念, 服务端数据均衡和恢复的最小粒度就是PG;
. R* o1 R( ? Q
9 T. `2 V3 ?! U% L% wOSD是负责物理存储的进程,一般配置成和磁盘一一对应,一块磁盘启动一个OSD进程;* ? ?8 W3 O/ K# O2 J
2 W5 \% q; X0 N( I* |* S- U
下面这张图形象的描绘了它们之间的关系:4 Q1 u, j/ t9 ]9 D8 O
6 M( G! L: [" ^) r一个Pool里有很多PG,
8 L% |3 _% F8 L, O" l! y/ _一个PG里包含一堆对象;一个对象只能属于一个PG;$ v. r0 z& Q( l/ `, Q
PG有主从之分,一个PG分布在不同的OSD上(针对三副本类型)
5 L$ J$ [7 I! S* l
4 H, k5 i# @3 l8 O, ^; x$ ^ O% f5 H/ }) \
讲究的PG
2 V; ~/ y3 G* ?5 \* \9 ]4 c+ `/ g* U9 j8 t9 a- g5 ~. _4 W f/ v9 h
一个Pool里设置的PG数量是预先设置的,PG的数量不是随意设置,需要根据OSD的个数及副本策略来确定:
* [) J$ f; J2 r: Q
# t$ n7 S4 R* z3 B* `Total PGs = ((Total_number_of_OSD * 100) / max_replication_count) / pool_count% T8 R! P; m2 j$ I6 A6 {7 b* @
6 A0 t3 _/ v0 I& T: A! b _5 v5 i$ E! q, K' n/ S
线上尽量不要更改PG的数量,PG的数量的变更将导致整个集群动起来(各个OSD之间copy数据),大量数据均衡期间读写性能下降严重;
3 V/ [0 b9 X% Q$ k
# B. W7 F9 V8 W良好的工程实践建议(掉坑后的教训): O+ a# y7 H+ _2 h# \
预先规划Pool的规模,设置PG数量;一旦设置之后就不再变更;后续需要扩容就以 Pool 为维度为扩容,通过新增Pool来实现(Pool通过 crushmap实现故障域隔离);
. O# m# y: N5 r
$ {' P' m: V3 k" q/ H/ X4 f对象的寻址过程; F) a& U! U) r" C& r) j) G
查找对象在集群中的存储的位置,具体分为两步:
" G- M( g7 l C; t) b I第一步,对象到PG的映射;将对象的id 通过hash映射,然后用PG总数对hash值取模得到pg id:
- p5 O. r( U. c& o1 r. E2 a- u6 ^; O R( Y1 r% _4 @" F
pg_ id = hash( object_ id ) % pg_num5 L) i+ v5 n0 x; z
6 C( v. n2 ?, ^
2 d7 k6 g4 E8 W5 N第二步,PG到osd列表映射; 通过crush算法计算PG 上的对象分布到哪些OSD硬盘上;( {. P( u# W: d' m
6 ^2 h2 B) C+ U9 ]0 ~. s7 _CRUSH(PG_ID) =⇒ OSD
4 ~) Z0 ^+ j0 b% K1 D: ZCRUSH算法是 ceph的精华所在;
$ ]. ]7 |1 Q$ K9 h& K6 S) \" E* A2 O( M) h3 H- }8 ~ e" y9 ^3 N x
crush的目标3 e/ L% D1 J5 q1 _
先看看crush算法的希望达成的目标:3 o. y4 D. }* j4 }9 k) a* v
( Q( p* R+ h) N9 o, o数据均匀的分布到集群中;% s* i8 ], L' V/ @) i% f# Q
需要考虑各个OSD权重的不同(根据读写性能的差异,磁盘的容量的大小差异等设置不同的权重)
2 y1 U! `0 l. |3 j' v当有OSD损坏需要数据迁移时,数据的迁移量尽可能的少;
* J/ n+ O# J1 `- J( hcrush算法
5 n% \& w6 K" J8 ^1 S# L简单说下crush算法的过程:
0 c0 \$ K4 D/ @5 |" M第一步输入PG id、可供选择的OSD id 列表,和一个常量,通过一个伪随机算法,得到一个随机数,伪随机算法保证了同一个key总是得到相同的随机数,从而保证每次计算的存储位置不会改变;2 {/ u6 G$ d( E3 W* f% s( r
3 A0 O! k' P# g$ ~CRUSH_HASH( PG_ID, OSD_ID, r ) = draw: i! I# \ h5 v' t. n3 s9 Z
第二步将上面得到的随机数和每个OSD的权重相乘,然后挑出乘积最大的那个OSD;
5 O% Y4 ]. K+ v3 G
! f7 K% D1 ~+ g) k ( draw &0xffff ) * osd_weight = osd_straw* C% i* u/ I" K& E; A
在样本容量足够大之后,这个随机数对挑中的结果不再有影响,起决定性影响的是OSD的权重,也就是说,OSD的权重越大,被挑中的概率越大。, A5 O* K6 n. e7 E5 w
到这里了我们再看看crush算法如何达成的目标:
q# d5 w3 C) t: q+ H通过随机算法让数据均衡分布,乘以权重让挑选的结果考虑了权重;而如果出现故障OSD,只需要恢复这个OSD上的数据,不在这个节点上的数据不需移动;- l8 f4 T& _0 N: {. ~( c; X: X
( D* J; z1 |' @2 J/ B3 {4 E
crush优缺点- b W, |1 r2 `
聊到这里,crush算法的优缺点就明显了: E2 U& a! P4 ?# `4 m) a5 k9 C
优点如下:3 \/ p( Z* k/ p. B. T7 ]
8 F. L8 ~4 `4 n' d7 O1 a( T Q3 M输入元数据( cluster map、 placement rule) 较少, 可以应对大规模集群。
9 a. L2 C: a( j i. L可以应对集群的扩容和缩容。$ y6 ^/ O4 i) Z5 ]- R. x
采用以概率为基础的统计上的均衡,在大规模集群中可以实现数据均衡。
2 j# `# B7 J) b, x缺点呢:
6 K$ o E7 n5 i5 i8 W8 j8 s9 b' N# c' Z, o5 N0 g) }( |/ M: h, f
在小规模集群中, 会有一定的数据不均衡现象(权重的影响低,主要起作用的是伪随机算法)。
7 \1 B3 v2 _! T O, @4 N看清楚了寻址的过程,就明白为啥PG不能轻易变更了;PG是寻址第一步中的取模参数,变更PG会导致对象的PG id 都发生变化,从而导致整个集群的数据迁移;% U3 `1 ?% p9 ~) F. |: ]( I& \: k
6 j( s- U" |6 X/ M# t这里只是做个引子,关于crush算法,这篇文章讲的通俗直白,有兴趣的移步:大话Ceph--CRUSH那点事儿- ^% q) h* R q- [5 r6 N
. D3 t G, }4 r6 r% E" F% fCeph 是Sega本人的博士论文作品, 其博士论文被整理成三篇短论文,其中一篇就是 CRUSH,
5 O- J" A, z; F8 s- G+ E4 ^CRUSH论文标题为《CRUSH: Controlled, Scalable, Decentralized Placement of Replicated Data》,介绍了CRUSH的设计与实现细节。; ~, M4 `: M d+ C6 n4 K
(PS:另外两篇是 RADOS和 CephFS, 分别讲 Ceph 的服务器实现和 Ceph 文件系统的细节实现)' I3 q3 | E* v$ D+ O3 y
4 @- t; r% n2 }2 n: H+ c' s
故障域的划分, }7 r& T. a4 |) R% a
刚开始接触 Ceph,通常会忽略 crushmap,因为即使对它不做任何设置,也不影响我们的正常使用;1 ^: A5 B! l: W- T5 F
一旦集群大了,没有它集群就处于一个危险的运行状态中;
$ u$ U- _- }( G( I/ x: P, T没有故障域的划分,整个集群就处于一个未隔离的资源池中;
6 s+ T5 T5 ^) n一个对象存过去,可能落在 500个OSD硬盘的任意三个上;# D4 J4 ?2 N1 k9 P. j8 I: f
如果一块硬盘坏了,可能带来的是全局影响(副本copy,这个硬盘上丢失的PG副本可能分布在全局各个硬盘上);
5 K, J# z+ f; n. D% ^9 L6 i* y$ W
6 [2 W& H. |" p. x2 ]$ ?0 W使用crushmap 将整个集群的OSD 划分为一个个故障域,类似将一个集群按业务划分成为了多个小集群;每个Pool 只会用到特定的 OSD,这样,一旦某个OSD 损坏,影响的只是某个业务的某个Pool,将故障的范围控制在一个很小的范围内。
8 l1 _8 f: e! K5 ^; w) _! n% g; P* F( L
良好的工程实践建议:
- Y8 w' h" w$ A# x使用crushmap 划分故障域,将pool限制在特定的osd list上,osd的损坏只会引起这个pool内的数据均衡,不会造成全局影响;
+ a0 h4 A2 }' _' h# {9 T- Y2 `: \5 L1 u' a" j, Q" {8 w
服务端对象的存储
4 R. p' p0 R8 I) U对象是数据存储的基本单元, 一般默认 4MB 大小(这里指的是RADOS的底层存储的对象,非RGW接口的对象)。
- E2 k: m/ I& ~% w) \2 h
: G( I! m) u3 ^2 [对象的组成分为3部分:key 、value、元数据;
2 c# _# M F" v! y) d j4 n, i5 v
! ~' }3 {# n! D3 `3 y# g元数据可直接存在文件的扩展属性中(必须是标准的文件属性),也可存到levelDb中;
) S% @4 i1 N6 f1 wvalue 就是对象数据,在本地文件系统中用一个文件存储;- A u; |/ v( |
对于大文件的存储,Ceph 提供的客户端接口会对大文件分片(条带化)后存储到服务端;这个条带化操作是在客户端接口程序完成的,在 Ceph 存储集群内存储的那些对象是没条带化的。客户端通过 librados 直接写入 Ceph 存储的数据不会分片。3 L/ k2 }& G0 x2 o ~; z0 E
3 g- r( q: v ] ?8 A" r6 Q
良好的工程实践建议:
5 _5 u6 ^/ R n7 j# q C3 j# l; q对于对象存储,只使用 Ceph 提供的 RGW 接口, 不使用 librados原生接口;不仅有分片功能,扩展也更容易(RGW是无状态的,可水平扩展);大量大对象直接存放到 Ceph中会影响 Ceph 稳定性(存储容量达到60%后);1 p" h8 E: X! ?, {
- T) d5 V: p* t9 H# `
总结
3 R' o# D$ L0 T5 ~5 ~, _上线 Ceph 前,+ E' t) k5 p8 F- U' i
先规划未来一年的预期使用量,为每个 pool 一次性设置 PG之后不再变更; 1 `- y) Y) g w9 z% E, l9 P) d
使用crushmap 设置故障域隔离,将磁盘故障后带来的数据平衡控制在一个小的范围之内。
' A, ]# P9 D' Q" l. r- e- p接口方面推荐只使用Ceph 提供的RGW 接口,不使用 librados原生接口。2 U2 x/ y$ G( A+ ~3 I
! \0 Q( s# d4 Z+ H7 k* r0 k做好这些, 你的 Ceph 用起来会省心很多。: t+ P0 n2 t4 L8 k4 o
$ x* m5 o- Y" }2 A |
|