|
|
本文先介绍 Ceph, 然后会聊到一些正确使用 Ceph 的姿势;在集群规模小的时候,Ceph 怎么玩都没问题;但集群大了(到PB级别),这些准则可是保证集群健康运行的不二法门;; C$ ^2 S; M) {: @' Z
& N0 o/ P! z) B% ?' }" n7 [Ceph 最初的目标是做一个分布式文件系统,直到现在这个目标也不能算完美实现;目前官网上对它的文件系统还是谨慎推荐的态度(不建议对线上核心业务部署);
; r& H( M) C8 F' k) m6 j( M- I& a& f! i% l
业界使用 Ceph ,大多是用它的对象存储;! i) O0 I) s& T% k5 p a
8 m. l2 W4 x% y! V3 eCeph 客户端
. U- ^4 S" @; C7 |2 h9 \3 Y7 XCeph 支持三种存储接口:对象存储 RGW(rados gateway)、块存储 RBD(rados block device) 和文件存储 CephFS;
0 N# ?; x* S: u" d- W3 c! f
* e- A" ~+ L9 s这三个接口只是在客户端的封装库不同,到服务端了都是对象存储;
/ \5 c1 N6 a9 Q! P" t" e4 F7 D( M* M0 Z5 Z
, j0 U2 j! I8 {* W: n7 j& z" P
对象存储(RGW:RADOS gateway)$ D o& ]" E1 \; G A0 |& z/ I: j
Ceph 对象存储服务提供了 REST 风格的 API ,它有与 Amazon S3 和 OpenStack Swift 兼容的接口。也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL和其他扩展;
3 |% x3 O- x# X$ x% H5 ?3 Z* f4 l8 E5 U
块存储(RBD:RADOS block device)
6 A* o L. t0 a( ?3 ?RBD 是通过librbd库对应用提供块存储,主要面向云平台的虚拟机提供虚拟磁盘;RBD类似传统的SAN存储,提供数据块级别的访问; t6 P4 Z( Q1 L. n9 J5 g2 n
( a/ b7 P6 @+ O9 t
目前 RBD 提供了两个接口,一种是直接在用户态实现, 通过 QEMU Driver 供 KVM 虚拟机使用。 另一种是在操作系统内核态实现了一个内核模块。通过该模块可以把块设备映射给物理主机,由物理主机直接访问。
/ _7 M$ c2 D. d+ i6 V! ~9 q8 _. P! t, H/ B. K: S
文件存储
7 ^% Y2 u+ y* nCeph 文件系统服务提供了兼容 POSIX 的文件系统,可以直接挂载为用户空间文件系统。它跟传统的文件系统如Ext4是一个类型,区别在于分布式存储提供了并行化的能力;
6 R! L- |) r3 H9 p3 M1 o. g0 ]% p A8 Q
原生接口3 K+ W) R* n6 k- S
除了以上3种存储接口, 还可以直接使用 librados 的原生接口,直接和RADOS通信;5 @2 ^: s! `5 [$ V! Z
$ K- m- h- h' p% K原生接口的优点是是它直接和和应用代码集成,操作文件很方便;但它的问题是它不会主动为上传的数据分片;一个1G的大对象上传,落到 Ceph 的存储磁盘上就是1G的文件;; K0 q& h- e2 W
; F: Q' z1 `0 B而以上三个接口是具有分片功能(即:条带化 file-striping)
% b# F. [" M' j6 K( E
+ j8 d% ]9 v$ b' V- L0 iPS:两个对象的区分 c, k+ \4 G% m; G7 X
9 h0 A+ P) G6 {. W0 f, i
需要说明下,这里提到两个对象的概念:一个是 RGW中的对象存储,一个是 Ceph 的后端存储的对象;这两个需要区分: ?# M; L7 L3 ^& A+ I
$ C# x* _: P* o, @+ h* y
第一个对象面向用户,是用户接口能访问到的对象;
& `! f9 D) x0 v) f) k
3 {4 _. X" f4 O* g第二个对象是ceph 服务端操作的对象;
. M! v$ ]1 x* C0 O
1 g8 ^3 s6 Z; @) a* ueg:使用RGW接口,存放一个1G的文件,在用户接口看到的就是存放了一个对象(1);而通过RGW 分片成多个对象(2)后最终存储到磁盘上;
% k7 r6 u$ I2 [0 e7 `6 T+ \$ i
! P9 R; _7 z/ a u( I
, h" {; d2 ]) R2 `, g/ ]Ceph 服务端* P, U5 ]( C; d$ s/ I2 a7 Z
8 R$ a4 u$ K$ P% N& y5 O
1 ^& T2 x8 X9 h服务端 RADOS 集群主要由两种节点组成:一种是为数众多的、负责完成数据存储和维护功能的OSD(Object Storage Device),另一种则是若干个负责完成系统状态检测和维护的monitor。
7 `+ p! C: S8 ?5 ]* ]# q
9 ^/ J0 j, v0 p' g; c/ }1 A6 SMonitor3 z% W! F* e1 o+ x6 A' [
Monitor 集群提供了整个存储系统的节点信息等全局的配置信息,通过 Paxos 算法保持数据的一致性。* n; ]6 b. L/ V: x4 n; U+ `
$ }$ Q W3 g6 u1 {& uPool 、PG和OSD" P, H/ ]6 O$ b) o* }% [3 [
Pool是存储对象的逻辑分区,它规定了数据冗余的类型和对应的副本分布策略;支持两种类型:副本(replicated)和 纠删码( Erasure Code);目前我们公司内部使用的Pool都是副本类型(3副本);/ S a; D( K: k( }
& U8 `2 `$ N& P/ F0 D3 r
PG( placement group)是一个放置策略组,它是对象的集合,该集合里的所有对象都具有相同的放置策略;简单点说就是相同PG内的对象都会放到相同的硬盘上; PG是 ceph的核心概念, 服务端数据均衡和恢复的最小粒度就是PG;7 z" K0 X+ q L6 s- e
) H9 o% [) i. D0 n7 t7 n
OSD是负责物理存储的进程,一般配置成和磁盘一一对应,一块磁盘启动一个OSD进程;2 h) e7 w- {) ^
7 F& \7 N; p4 D7 b8 d Q# F% z7 A
下面这张图形象的描绘了它们之间的关系:$ L4 h8 e" a0 D0 @0 j" F) _
/ Y0 t, A8 @; {一个Pool里有很多PG,- F7 k- m( p7 d% T7 j- w2 S
一个PG里包含一堆对象;一个对象只能属于一个PG;4 W0 y& g5 V) F: w2 w- r0 p5 w
PG有主从之分,一个PG分布在不同的OSD上(针对三副本类型)
! R+ {! s! ?$ c- F W) v5 }! Y8 ^% O: f3 A. c9 ?- l8 |0 n
* ]) ^* o/ a0 m; o/ y; b5 r& Q9 u
讲究的PG* ^4 f0 m1 [5 e; B0 i7 n. _
/ k& O5 j$ ~. r" W一个Pool里设置的PG数量是预先设置的,PG的数量不是随意设置,需要根据OSD的个数及副本策略来确定:
- |; J% f9 h7 j" B* v
2 _1 {. \- J! [0 D6 D; mTotal PGs = ((Total_number_of_OSD * 100) / max_replication_count) / pool_count' ?( K8 b) P8 E5 u& Q
9 l8 q4 C8 ]7 ?3 P4 J
& \7 n9 A" q1 s) D+ L$ k( f
线上尽量不要更改PG的数量,PG的数量的变更将导致整个集群动起来(各个OSD之间copy数据),大量数据均衡期间读写性能下降严重;
" V3 l6 ~3 X2 I! j5 L( `6 {9 k, S& f- |' r
良好的工程实践建议(掉坑后的教训):. e3 u2 x/ |9 F, y, C' h
预先规划Pool的规模,设置PG数量;一旦设置之后就不再变更;后续需要扩容就以 Pool 为维度为扩容,通过新增Pool来实现(Pool通过 crushmap实现故障域隔离);3 r% S o9 x+ f; m) O" J
9 J8 s9 q. V6 T( p0 n8 T5 o对象的寻址过程
3 n! U1 B8 x7 B: [查找对象在集群中的存储的位置,具体分为两步:' S& i/ f6 V" H9 b4 j1 b8 D
第一步,对象到PG的映射;将对象的id 通过hash映射,然后用PG总数对hash值取模得到pg id:3 R* ]2 w" X$ O, \6 W
# c0 v% i6 n7 F" _+ U4 K
pg_ id = hash( object_ id ) % pg_num
3 n; t2 D* a* W% ~ z3 k/ r5 W
7 M9 z8 W6 W1 N$ _. t2 `6 O, w" l: v3 K
第二步,PG到osd列表映射; 通过crush算法计算PG 上的对象分布到哪些OSD硬盘上;% C4 {/ b# _, x. R/ _/ M& g1 v
- n/ g6 J1 ^- k, i+ o8 c! \! \
CRUSH(PG_ID) =⇒ OSD
5 {: V3 t( _! W$ @CRUSH算法是 ceph的精华所在;* Y+ n4 J8 V2 m0 ?+ i! V
. ~; {: T! f4 h8 Ccrush的目标; K- g; N4 X. x' u
先看看crush算法的希望达成的目标:
- b7 r% E( n% U) b4 U5 O' X" n3 x( n, J% R
数据均匀的分布到集群中;
" i# S8 N8 X. E! @& p需要考虑各个OSD权重的不同(根据读写性能的差异,磁盘的容量的大小差异等设置不同的权重)
) O: O( F% `0 a1 V9 S: {当有OSD损坏需要数据迁移时,数据的迁移量尽可能的少;6 t3 \0 k0 P' w, [4 C# t# V
crush算法' O% I5 N5 W: l/ p! ?7 b
简单说下crush算法的过程:
8 {% d+ G- g! V; J p第一步输入PG id、可供选择的OSD id 列表,和一个常量,通过一个伪随机算法,得到一个随机数,伪随机算法保证了同一个key总是得到相同的随机数,从而保证每次计算的存储位置不会改变;, X# \! h! g4 P3 j. ^
8 t5 Y% ]: Y& }CRUSH_HASH( PG_ID, OSD_ID, r ) = draw
% V, t1 c2 L9 T4 X7 t! K; m第二步将上面得到的随机数和每个OSD的权重相乘,然后挑出乘积最大的那个OSD;9 W( `' S+ M. E6 ?" C* s: j
7 a( k2 U( ^" P' o2 W) ?7 k9 \
( draw &0xffff ) * osd_weight = osd_straw; ]; E0 V; } X1 ^3 x, o
在样本容量足够大之后,这个随机数对挑中的结果不再有影响,起决定性影响的是OSD的权重,也就是说,OSD的权重越大,被挑中的概率越大。
% Z* ]+ \& ]2 v到这里了我们再看看crush算法如何达成的目标:
- C2 j0 U; m- n5 n/ f通过随机算法让数据均衡分布,乘以权重让挑选的结果考虑了权重;而如果出现故障OSD,只需要恢复这个OSD上的数据,不在这个节点上的数据不需移动;
( L& d: f1 g! o- Z& F
: @* |! u8 K, tcrush优缺点! p6 J3 }0 R# V) E8 w
聊到这里,crush算法的优缺点就明显了:
+ R3 a% K" I: G0 ]8 _/ u) N9 ^优点如下:: B7 L& Q9 Y0 k& z/ D
) E/ L9 [5 K0 ~/ L, }0 h; k4 ^; W
输入元数据( cluster map、 placement rule) 较少, 可以应对大规模集群。/ j" M- p2 ]- K* C) N4 L2 [
可以应对集群的扩容和缩容。. l1 j4 U3 k- U5 N. b7 ]2 h
采用以概率为基础的统计上的均衡,在大规模集群中可以实现数据均衡。
+ Q" Q3 e+ }1 I7 L q! M0 ^5 [缺点呢:% d; v$ s8 w8 R+ s
7 A& ]# F( ^" g( e) J$ t/ j在小规模集群中, 会有一定的数据不均衡现象(权重的影响低,主要起作用的是伪随机算法)。$ S: E# N- f) U, F; d5 A- g- T- K
看清楚了寻址的过程,就明白为啥PG不能轻易变更了;PG是寻址第一步中的取模参数,变更PG会导致对象的PG id 都发生变化,从而导致整个集群的数据迁移;: `. l- U, u1 p
% M" L9 @! P7 c这里只是做个引子,关于crush算法,这篇文章讲的通俗直白,有兴趣的移步:大话Ceph--CRUSH那点事儿
, N( h9 R4 M. }! R7 X+ e4 t0 h* x( e) O7 H9 h- P
Ceph 是Sega本人的博士论文作品, 其博士论文被整理成三篇短论文,其中一篇就是 CRUSH,. c( Y0 d. S( T- b. s% H. l6 s* J
CRUSH论文标题为《CRUSH: Controlled, Scalable, Decentralized Placement of Replicated Data》,介绍了CRUSH的设计与实现细节。. t& \, G& o+ B# r. V5 w6 ?) H
(PS:另外两篇是 RADOS和 CephFS, 分别讲 Ceph 的服务器实现和 Ceph 文件系统的细节实现)+ p, V Z+ @7 {- f P) U
- C) E. W+ P' o- y" Y+ L. ?9 s8 L& i
故障域的划分
+ l1 s8 e) P6 \/ Y+ a% x. m. q刚开始接触 Ceph,通常会忽略 crushmap,因为即使对它不做任何设置,也不影响我们的正常使用;
; O' Z5 O" i H* S9 O一旦集群大了,没有它集群就处于一个危险的运行状态中;
% N9 c+ H% h. Y. P) i4 }7 n没有故障域的划分,整个集群就处于一个未隔离的资源池中;& K" e: }1 S; E
一个对象存过去,可能落在 500个OSD硬盘的任意三个上;7 W6 c3 W" o5 d& L% J' s; Q, X s
如果一块硬盘坏了,可能带来的是全局影响(副本copy,这个硬盘上丢失的PG副本可能分布在全局各个硬盘上);
! \# @& k3 X; y- \4 }
1 E( \) w! v, P) z1 C) D d* G使用crushmap 将整个集群的OSD 划分为一个个故障域,类似将一个集群按业务划分成为了多个小集群;每个Pool 只会用到特定的 OSD,这样,一旦某个OSD 损坏,影响的只是某个业务的某个Pool,将故障的范围控制在一个很小的范围内。' I$ w: [# W( K Y$ t) F: ^
2 v) p- k6 B/ x& ^* r) y
良好的工程实践建议:& Y2 u# N, R& ]5 V
使用crushmap 划分故障域,将pool限制在特定的osd list上,osd的损坏只会引起这个pool内的数据均衡,不会造成全局影响;
5 B9 U5 q4 a2 a' Z; U
* e" T) D2 u4 K) E; Y& x服务端对象的存储
+ r# w0 A- ^, s, F3 n3 G8 P对象是数据存储的基本单元, 一般默认 4MB 大小(这里指的是RADOS的底层存储的对象,非RGW接口的对象)。' l8 H6 v' q9 e9 ], A. w, g
* D8 l, r% b& O8 p9 k# D. Z1 S, B
对象的组成分为3部分:key 、value、元数据;
7 Z) D. s0 N" f6 h) l1 b5 @) F( Z6 t3 e p& v
元数据可直接存在文件的扩展属性中(必须是标准的文件属性),也可存到levelDb中;
; `6 L; C) | Vvalue 就是对象数据,在本地文件系统中用一个文件存储;% G7 M; M/ l7 p: Q: m5 t
对于大文件的存储,Ceph 提供的客户端接口会对大文件分片(条带化)后存储到服务端;这个条带化操作是在客户端接口程序完成的,在 Ceph 存储集群内存储的那些对象是没条带化的。客户端通过 librados 直接写入 Ceph 存储的数据不会分片。
" o; D9 L0 r7 k' }
, x8 u, f0 W$ p1 _& ^4 Q良好的工程实践建议:3 I* t: S9 E5 U# }: J) i
对于对象存储,只使用 Ceph 提供的 RGW 接口, 不使用 librados原生接口;不仅有分片功能,扩展也更容易(RGW是无状态的,可水平扩展);大量大对象直接存放到 Ceph中会影响 Ceph 稳定性(存储容量达到60%后);+ A8 e$ a$ w6 c9 S- g
$ O, \4 b( o' }& ]( K
总结
) i/ w) Q" C+ N' G上线 Ceph 前,8 R' m$ T- G1 ~6 c
先规划未来一年的预期使用量,为每个 pool 一次性设置 PG之后不再变更;
) @% w9 w3 Q( x2 k* w, E使用crushmap 设置故障域隔离,将磁盘故障后带来的数据平衡控制在一个小的范围之内。7 A* z$ e: ?4 V' Z# \' V
接口方面推荐只使用Ceph 提供的RGW 接口,不使用 librados原生接口。5 p4 u# J1 t8 F( w4 [9 i
" c0 k8 a8 }. L做好这些, 你的 Ceph 用起来会省心很多。
6 Q% C4 t1 D [; H. U- t. M
8 s* R9 b* ?" F) d7 c! o3 U; @ |
|