|
关于MongoDB,我们能看到的资料,基本都是在指导大家如何使用MongoDB,但是,MongoDB内部是如何运作的,资料不是很多。
+ B/ o( E. F2 l) `1 I% U6 Z( g$ k; X* y
阅读使用手册,会有很多疑惑之处。例如,有人说,MongoDB 等同于分布式的 MySQL。它把一个Table ,按 row,分割成多个Shards,分别存放在不同的 Servers 上。这种说法是否正确?! c% w5 m0 z6 ~" @6 {+ y
& j: s( Z# {. w! c" @$ K6 W( @' P+ m
不深入了解 MongoDB 的内部结构,就无法透彻地回答类似问题。这个系列文章,就来和大家探讨MongoDB的内部的工作方式。2 `# k7 Q$ U) ?9 R- {! R* S
2 s3 l9 T+ Z8 f& q8 m% I& V
) r- Z1 Y" U( T+ q7 q
% O/ F+ ^' A: Q4 r- I图1-1 MongoDB架构图 0 ]: J8 J& |7 d/ h: l6 ? J8 ?
1 J! a0 k/ P1 a$ D/ H) I. R# ^$ d MongoDB 通常运行在一个服务器集群上,而不是一个单机。图1-1,描述了一个MongoDB集群的基本组成部分,包括若干shards,至少一个config server,至少一个routing servers(又称 mongos)。- u0 P9 F- \4 p
. D% B8 ?: @% |Shards
) D3 J& ?# L% r, N* o% U5 i; ^7 h
, R4 Y/ e F' }* g1 n9 o$ E# f MongoDB的最基本的数据单元,叫document,类似于关系式数据库中的行 row。一系列documents,组成了一个collection,相当于关系式数据库中的table。当一个 collection 数据量太大时,可以把该collection按documents切分,分成多个数据块,每个数据块叫做一个chunk,多个chunks聚集在一起,组成了一个shard。
" `# m) G* T2 j9 k2 Z% h/ E! u! F& r
Sharding 的意义,不仅保障了数据库的扩容(scalability),同时也保障了系统的负载均衡(load balance)。& L9 n7 n% C+ V8 u z
, K4 V: o+ P- E1 P7 y2 i, M
每一个shard存储在一个物理服务器(server)上。Server上运行着mongod进程,通过这个进程,对shard中的数据进行操作,主要是增删改查。5 u8 |2 |6 [% [5 m6 g; p' @
/ u' z2 z& q4 e 如果系统中的每个shard,只存储了一份数据,没有备份,那么当这个shard所在的server挂了,数据就丢失了。在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。
F4 Z% z( u. |8 G. j" @# Z
9 ~% F7 d, |* z' fShard keys# M0 i |8 O" A- p3 b Y, ~7 b
& g" n9 y! S; `, X* }
为了把collection切分成不同的chunks,从而存放到不同的shards中,我们需要制定一个切分的方式。
; ^; W+ Z5 n9 o0 H9 {& F8 c4 U6 `/ @) b n6 Z# G9 {3 A& C
如前所述,在 MongoDB 数据库中,一个表collection由多个行 documents 组成,而每个 document,有多个属性 fields。同一个 collection 中的不同的 documents,可能会有不同的 fields。例如,有个 collection 叫 Media,包含两条 documents,
! I' o( |9 U5 o+ F0 y, B* v! ~' u/ s) E
{
2 u0 l' ~9 m" ~: y( y "ISBN": "987-30-3652-5130-82",/ B# T9 U# i- z% _% p
"Type": "CD",* Y8 \, d/ J0 w0 K. U. B7 _$ r% {
"Author": "Nirvana",
# o! H# x R q2 R7 G5 m "Title": "Nevermind",
6 j4 N# ~/ |* Z "Genre": "Grunge",
- p; \1 E+ d: l/ y$ R7 W2 ` "Releasedate": "1991.09.24",% b+ X9 p1 e0 o
"Tracklist": [3 x' H$ q8 a6 G8 X& q, c! u n e9 p
{
6 T# f ~2 [9 c' }. `6 S "Track" : "1",$ S% g m) ]# H g
"Title" : "Smells like teen spirit",+ {' m) x" u; f4 B+ a* Q! z
"Length" : "5:02"6 k6 T; s1 @+ p. H+ v$ A
},( q9 d- }2 b0 n, \
{
2 n+ k8 [( q# B; E( i "Track" : "2",
8 C+ T$ }8 {% R* I0 s "Title" : "In Bloom",
% n8 j! X/ i! f& ^; ` "Length" : "4:15"$ U, F% d6 V0 ?) U& c, }
}
; H5 ^! t. C* h# c+ z* \ ]
: Q; I# c/ l, h. w' \}5 |! T' m) F- j6 D
0 h t% O; G' h{
n; V( \0 f2 c* T9 k "ISBN": "987-1-4302-3051-9",1 N- s" Q3 B- f+ n
"Type": "Book",
, M0 R" Y* q; q7 j% e1 n; H9 f "Title": "Definite Guide to MongoDB: The NoSQL Database",
7 y+ b, o6 M$ d v* \. B1 y P! s "Publisher": "Apress",
6 G3 C: [1 E T2 m) x& |$ \" Y "Author": " Eelco Plugge",
5 D4 v9 z+ }, b' @8 y$ q9 [ "Releasedate": "2011.06.09"
/ y7 f: z) f% l}
; C& S$ p" m1 W- r! a$ P, g c, w" R! P- a: W2 s1 D. i, }
假如,在同一个 collection 中的所有 document,都包含某个共同的 field,例如前例中的“ISBN”,那么我们就可以按照这个 field 的值,来分割 collection。这个 field 的值,又称为 shard key。3 T4 S1 t8 P1 s) ~' K) l& }
3 ?5 f$ z1 g/ S$ M) x
在选择shard key的时候,一定要确保这个key能够把collection均匀地切分成很多chunks。8 S" n3 v0 I8 k# e/ T$ M
# A+ w/ b) h. h 例如,如果我们选择“author”作为shard key,如果有大量的作者是重名的,那么就会有大量的数据聚集在同一个chunk中。当然,假设很少有作者同名同姓,那么“author”也可以作为一个shard key。换句话说,shard key 的选择,与使用场景密切相关。
9 Z" h5 n! S! }; ?+ c" B9 U4 J+ s
很多情况下,无论选择哪一个单一的 field 作为shard key,都无法均匀分割 collection。在这种情况下,我们可以考虑,用多个 fields,构成一个复合的shard key。$ c B8 |( e2 j# `$ a
5 P# e& h8 W+ w$ h) k
延续前例,假如有很多作者同名同姓,他们都叫“王二”。用 author 作为 shard key,显然无法均匀切割 collection。这时我们可以加上release-date,组成name-date的复合 shard key,例如“王二 2011”。
4 _+ T3 f9 e/ _1 t3 m# o9 {" @, O/ M+ S, c6 v# }# }: w8 ?
Chunks6 g( z+ W! z& s" E/ z
5 o+ f0 g' r9 A: g6 h7 `+ D* C
MongoDB按 shard key,把 collection切割成若干 chunks。每个 chunk 的数据结构,是一个三元组,{collection,minKey,maxKey},如图1-2 所示。
7 e/ l7 s A$ g: \ M# j( F: S2 D9 u5 W& ]: b
: S+ M' g5 j" R' |图1-2 chunk的三元组
8 `: G, |! ~% b3 w2 a
% W/ t' k# O" w K 其中,collection 是数据库中某一个表的名称,而 minKey 和 maxKey 是 shard key的范围。每一个 document 的shard key 的值,决定了这条document应该存放在哪个chunk中。( s# B' v4 s/ |) j# B( o
7 u1 N0 N/ A- c5 H2 T
如果两条 documents 的 shard keys 的值很接近,这两条 documents 很可能被存放在同一个 chunk 中。
2 ]: f$ {' ] g; a W9 L$ I0 R7 _) |# r
% I! R; k1 [ k6 G( ? Shard key 的值的顺序,决定了 document 存放的 chunk。在 MongoDB 的文献中,这种切割 collection 的方式,称为order-preserving。
: u, r5 r, e' R& D. e' p
" ?/ n ?' s; [+ w7 N5 e. }. q 一个 chunk最多能够存储64MB的数据。 当某个chunk存储的 documents包含的数据量,接近这个阈值时,一个chunk会被切分成两个新的chunks。6 X; X1 u% I2 H) t. s, A: @. t1 X
# y. T) \1 z5 @+ F# I6 x1 V: q0 ~ 当一个shard存储了过多的chunks,这个shard中的某些chunks会被迁移到其它 shard中。* \& N8 a) {. P. e( Y& ~7 q
' |% ?: d. y' k, L 这里有个问题,假如某一条 document 包含的数据量很大,超过 64MB,一个 chunk 存放不下,怎么办?在后续章节介绍 GridFS 时,我们会详细讨论。
, v, \2 i* R5 |! a4 P$ N7 H1 O
, K# P8 ^/ f( g5 \Replica set
8 K- a$ y: r, b
0 h* B) x$ i5 S4 {" e9 O, _ 在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。
1 {% u8 [7 B( U% X. @4 S2 k- S& D( e# C, ^5 Z6 g
这个replica set包括一个primary DB和多个secondary DBs。为了数据的一致性,所有的修改(insert / update / deletes) 请求都交给primary处理。处理结束之后,再异步地备份到其他secondary中。
9 g/ z3 p5 W2 V4 G: Z' i0 `
! s j2 j9 L1 \' ~9 S Primary DB由replica set中的所有servers,共同选举产生。当这个primaryDB server出错的时候,可以从replica set中重新选举一个新的primaryDB,从而避免了单点故障。
! }- _: N' M8 T2 p4 I h
% w& k9 w, i0 d' U, g3 l) F4 o Replica set的选举策略和数据同步机制,确保了系统的数据的一致性。后文详述。$ |# M! t( G1 ]& a f
2 R8 A9 @% K/ f- [' K7 B- O
Config Server
! R* v A% {" ]9 t6 V) J
7 Q- N7 B: p4 }9 [6 o- l& l Config servers用于存储MongoDB集群的元数据 metadata,这些元数据包括如下两个部分,每一个shard server包括哪些chunks,每个chunk存储了哪些 collections 的哪些 documents。5 `( b8 Y* M4 Q6 V9 R! i( R2 g3 E
% r! X. Q! i1 Y3 }
每一个config server都包括了MongoDB中所有chunk的信息。+ r+ _0 V' d+ f! l* ~% f3 I( k
/ J5 ]2 |5 m) d# ~
Config server也需要 replication。但是有趣的是,config server 采用了自己独特的replication模式,而没有沿用 replica set。
2 L5 w* d& f. }5 ~: ?# |, ^
4 @6 W1 b. Z3 w! F" H6 s( X2 z 如果任何一台config server挂了,整个 config server 集群中,其它 config server变成只读状态。这样做的原因,是避免在系统不稳定的情况下,冒然对元数据做任何改动,导致在不同的 config servers 中,出现元数据不一致的情况。
0 a ]( S$ h. O$ }! q( [8 E" D# V
s; S6 t4 K3 f. b MongoDB的官方文档建议,配置3个config servers比较合适,既提供了足够的安全性,又避免了更多的config servers实例之间的数据同步,引起的元数据不一致的麻烦。
) S* g. P1 D3 t# W/ O2 b
& Z9 G& ]9 \. c( T( {# p) HMongos' y& }7 X& {7 H; Y3 ~4 p
! L, m+ g: ]6 A! C+ Z
用户使用MongoDB 时,用户的操作请求,全部由mongos来转发。
; h2 `, m( `( F n2 \3 o+ H& q
" r3 e6 X3 Y( \, x# N 当 mongos 接收到用户请求时,它先查询 config server,找到存放相应数据的shard servers。然后把用户请求,转发到这些 shard servers。当这些 shard servers完成操作后,它们把结果分别返回给 mongos。而当 mongos 汇总了所有的结果后,它把结果返回给用户。& [/ A" y+ C1 g
6 |/ |. ^" u3 Q* Y3 B Mongos每次启动的时候,都要到config servers中读取元数据,并缓存在本地。每当 config server中的元数据有改动,它都会通知所有的mongos。
* s3 u8 }6 A- G& N% q% \8 f
9 S& O4 R5 M- s. E& b8 V Mongos之间,不存在彼此协同工作的问题。因此,MongoDB所需要配置的mongos server的数量,没有限制。
7 d3 F2 W- S Z5 j; D0 @! |/ t/ F8 o
通过以上的介绍,我们对每个组成部分都有了基本的了解,但是涉及到工作的细节,我们尚有诸多疑问,例如,一个chunk的数据太大,如何切分?一个shard数据太多,如何迁移?在replica set中,如何选择primary?server挂了,怎么进行故障恢复?接下来的章节,我们逐个回答这些问题。# B- `" w% H' m5 n" S+ T
8 T3 ^2 k" X& K
( {* K& n% e' }; s k0 CReference,6 q x+ i; E3 V6 a, a- Q$ n
5 r" x! p* h+ B4 P1 m[0] Architectural Overview7 d" t4 u8 |& j
http://www.mongodb.org/display/DOCS/Sharding+Introduction
* u4 l/ y1 h) _1 E |
评分
-
查看全部评分
|