|
关于MongoDB,我们能看到的资料,基本都是在指导大家如何使用MongoDB,但是,MongoDB内部是如何运作的,资料不是很多。
. w1 h& ]2 r# R. a. o5 x6 Y0 y6 w/ H; q/ M& f H2 G" O8 X
阅读使用手册,会有很多疑惑之处。例如,有人说,MongoDB 等同于分布式的 MySQL。它把一个Table ,按 row,分割成多个Shards,分别存放在不同的 Servers 上。这种说法是否正确?+ l* s/ T. F0 X9 _; I4 }
" C- ]+ ~7 q! V- @- w 不深入了解 MongoDB 的内部结构,就无法透彻地回答类似问题。这个系列文章,就来和大家探讨MongoDB的内部的工作方式。
" k9 C% j% d3 B; \, s% ]; @+ J3 V! |& Z8 i5 B- G0 ]) M
: G; N1 Y5 u* |2 n$ U
' ?+ M s* d' Q6 K, n
图1-1 MongoDB架构图 9 k' ~" R2 [3 W+ {7 C# j3 ^4 G7 V
' h" m' ~ [/ c9 @6 J$ T4 R3 X MongoDB 通常运行在一个服务器集群上,而不是一个单机。图1-1,描述了一个MongoDB集群的基本组成部分,包括若干shards,至少一个config server,至少一个routing servers(又称 mongos)。/ B) @2 U. A; r" e
9 A" h: R- {: Q' h1 z. R6 u- H
Shards1 z3 |! C& I# f
! G* I4 e( n M h/ G0 P' m MongoDB的最基本的数据单元,叫document,类似于关系式数据库中的行 row。一系列documents,组成了一个collection,相当于关系式数据库中的table。当一个 collection 数据量太大时,可以把该collection按documents切分,分成多个数据块,每个数据块叫做一个chunk,多个chunks聚集在一起,组成了一个shard。1 V9 p' W9 f! t8 R4 [3 l, [
* [; |. Y* p) L! M: S, U6 q' v
Sharding 的意义,不仅保障了数据库的扩容(scalability),同时也保障了系统的负载均衡(load balance)。
7 q7 V% A/ x& H
/ j d3 S: p& b 每一个shard存储在一个物理服务器(server)上。Server上运行着mongod进程,通过这个进程,对shard中的数据进行操作,主要是增删改查。3 P+ u/ }1 C5 Y# L
4 `4 P, k- F& _8 U* n9 F. J
如果系统中的每个shard,只存储了一份数据,没有备份,那么当这个shard所在的server挂了,数据就丢失了。在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。. G( I* E' p7 l$ P% `( `& o+ R
1 ^* u( i. X) r* y4 [Shard keys
$ {. b9 v+ b7 w8 u _3 H' y t' R
* k" C% _8 M* b9 h 为了把collection切分成不同的chunks,从而存放到不同的shards中,我们需要制定一个切分的方式。3 g; D+ U" J- x% B. L2 ~! b7 v, E6 T
- H/ h# l6 x6 k
如前所述,在 MongoDB 数据库中,一个表collection由多个行 documents 组成,而每个 document,有多个属性 fields。同一个 collection 中的不同的 documents,可能会有不同的 fields。例如,有个 collection 叫 Media,包含两条 documents,
: q0 ?: A8 s+ k: w% U$ I2 S
: g6 }# u. R* _1 E{
# h2 c% i3 C& m4 e, ` "ISBN": "987-30-3652-5130-82",
5 [4 c, L& w& P# U9 | "Type": "CD",* B3 ?1 m9 r. R4 h. F8 V. d
"Author": "Nirvana",3 n' ^, ^2 |! z0 v
"Title": "Nevermind",# B# r6 ~! _. B: c# a! X) K) k
"Genre": "Grunge",
. V) g; N- t# p+ V3 ] "Releasedate": "1991.09.24",
2 _, Q- ?! l: h' y "Tracklist": [ ?5 d: g$ f5 }6 r0 y
{8 U9 s- j- i( x; V4 M) T" ]- J& S0 P
"Track" : "1",* y" p, S# y9 p9 ^
"Title" : "Smells like teen spirit",
# @9 [$ W, |& i+ { "Length" : "5:02"
8 Y5 C3 `1 T. e1 t- G- N },+ Y- w3 @+ W" W
{
2 p$ o2 O0 l& k "Track" : "2",
2 [" X8 M8 e- R) Z; \1 a "Title" : "In Bloom",5 u9 i4 H; ^ \' e( H* e' G# n
"Length" : "4:15"
6 E4 {& B) G, f) w2 A }/ x8 Y7 _& ]1 P( R# J4 N2 v
], E' F% x0 [6 ]8 q4 C
}# }6 L7 @' W {8 L* S+ U5 I& K4 T
: O: r8 Y0 X+ G5 j$ Y{
9 y& B C" ]! h. \" X$ Z: i7 ~; k "ISBN": "987-1-4302-3051-9",; m, Q& g* F8 |4 z; m. Z
"Type": "Book",
- m4 k1 T6 M/ D2 Y "Title": "Definite Guide to MongoDB: The NoSQL Database",
. f* X8 I9 a5 p. Q2 |" ~; ^( V "Publisher": "Apress",
- H: X- l3 e' ? @ u "Author": " Eelco Plugge",! K5 L9 }9 C. d k/ B {% M
"Releasedate": "2011.06.09"
' U2 E4 O- Y9 d$ Y% b}
' A4 p% A& T0 d# u0 t7 M8 F M$ H' d+ v, U3 ~: Y
假如,在同一个 collection 中的所有 document,都包含某个共同的 field,例如前例中的“ISBN”,那么我们就可以按照这个 field 的值,来分割 collection。这个 field 的值,又称为 shard key。
& j) j8 m6 g" u- }* r& \2 R( s
! y5 l; w4 Z3 Y( ^3 W7 P& k2 a 在选择shard key的时候,一定要确保这个key能够把collection均匀地切分成很多chunks。
8 y# W! M5 }! c
# ~) b5 k4 K& r6 Z0 O 例如,如果我们选择“author”作为shard key,如果有大量的作者是重名的,那么就会有大量的数据聚集在同一个chunk中。当然,假设很少有作者同名同姓,那么“author”也可以作为一个shard key。换句话说,shard key 的选择,与使用场景密切相关。
" [ S- H0 a, u, s
/ S9 R( r( m" R. s* o9 H 很多情况下,无论选择哪一个单一的 field 作为shard key,都无法均匀分割 collection。在这种情况下,我们可以考虑,用多个 fields,构成一个复合的shard key。$ W5 W+ l$ F# q0 n
3 e/ m! F( g% }( X) r7 V
延续前例,假如有很多作者同名同姓,他们都叫“王二”。用 author 作为 shard key,显然无法均匀切割 collection。这时我们可以加上release-date,组成name-date的复合 shard key,例如“王二 2011”。
: {1 l; J% G/ n5 R0 V3 C& ^4 ?# R7 i* A: _" ^7 K
Chunks
+ r7 B! b: p; d% n
: K/ N) J. @/ x/ w7 k/ z6 Z0 ~ MongoDB按 shard key,把 collection切割成若干 chunks。每个 chunk 的数据结构,是一个三元组,{collection,minKey,maxKey},如图1-2 所示。
8 q% z" a7 s V, c$ C; N5 a- ^. r& k0 {% V" d
4 o# F* @$ ^% e R/ }, v2 t图1-2 chunk的三元组 " o" z. E& [6 P' w4 D. |* B; K
, h5 `, \& o# }7 A1 n% K: X4 G8 m
其中,collection 是数据库中某一个表的名称,而 minKey 和 maxKey 是 shard key的范围。每一个 document 的shard key 的值,决定了这条document应该存放在哪个chunk中。2 x. v, ]0 i' A
( H+ x, n0 [6 x2 j. M6 V+ Y
如果两条 documents 的 shard keys 的值很接近,这两条 documents 很可能被存放在同一个 chunk 中。- Z; H9 k' f1 v3 `2 D: m
' _* t% s J2 d6 h8 X Shard key 的值的顺序,决定了 document 存放的 chunk。在 MongoDB 的文献中,这种切割 collection 的方式,称为order-preserving。
& \+ Z/ B. E- e( {
, g, t1 f! b0 W2 c. Y4 U/ Y 一个 chunk最多能够存储64MB的数据。 当某个chunk存储的 documents包含的数据量,接近这个阈值时,一个chunk会被切分成两个新的chunks。
0 s* X9 G0 m' r. p7 p( y% b: z4 [$ n" k$ E- U8 p
当一个shard存储了过多的chunks,这个shard中的某些chunks会被迁移到其它 shard中。, a$ y0 G0 F, Z9 K
3 |/ `2 |8 n0 o; P( m8 s4 N 这里有个问题,假如某一条 document 包含的数据量很大,超过 64MB,一个 chunk 存放不下,怎么办?在后续章节介绍 GridFS 时,我们会详细讨论。
# U( y7 z d& Y E( ~2 O
B6 X1 B3 P/ Q9 n& a% y4 s* z, |) ]Replica set
Z/ c- G6 @' f* P
9 d% s# L6 V3 U/ @2 v5 ?" f 在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。! R+ y8 t( a3 C8 y
$ D& M% O3 w, S0 v6 F8 V 这个replica set包括一个primary DB和多个secondary DBs。为了数据的一致性,所有的修改(insert / update / deletes) 请求都交给primary处理。处理结束之后,再异步地备份到其他secondary中。: }( e7 X3 E$ x
* B2 t. X" V+ ^7 z, S! S+ w+ _) g
Primary DB由replica set中的所有servers,共同选举产生。当这个primaryDB server出错的时候,可以从replica set中重新选举一个新的primaryDB,从而避免了单点故障。
/ \: [, E( Z% f& m5 _( R
# f# { }8 s' w4 @* l4 X% k! m* R; ]! q Replica set的选举策略和数据同步机制,确保了系统的数据的一致性。后文详述。
; _$ h, [7 P7 o1 c/ ?8 f1 ~4 m0 h8 V; [( p# O. r6 ~
Config Server( j- E3 V* w; A" z/ Q
' { R# K+ _/ i F Y0 J
Config servers用于存储MongoDB集群的元数据 metadata,这些元数据包括如下两个部分,每一个shard server包括哪些chunks,每个chunk存储了哪些 collections 的哪些 documents。) m* P% h) R* J# B
1 l2 U5 N$ p$ g
每一个config server都包括了MongoDB中所有chunk的信息。
" V* u' s, r: ]2 A6 U& f3 u9 u8 u
3 a: f; x2 ?" A) e Config server也需要 replication。但是有趣的是,config server 采用了自己独特的replication模式,而没有沿用 replica set。 `$ b- E. k2 L7 H9 d! O4 D" {: f" m, ?* m
) T9 f" k: Z, M 如果任何一台config server挂了,整个 config server 集群中,其它 config server变成只读状态。这样做的原因,是避免在系统不稳定的情况下,冒然对元数据做任何改动,导致在不同的 config servers 中,出现元数据不一致的情况。$ B3 q7 O6 S; J: V
$ I- M- t5 |, R2 A" e
MongoDB的官方文档建议,配置3个config servers比较合适,既提供了足够的安全性,又避免了更多的config servers实例之间的数据同步,引起的元数据不一致的麻烦。
+ C! [3 T. }* d6 F X+ b. o& F! B! k. G* b. G* v: c
Mongos
4 ^0 e0 G8 a' ?* ^
y6 ^/ _6 w8 a N. I& v# p 用户使用MongoDB 时,用户的操作请求,全部由mongos来转发。' R5 D! ~5 ~# D* d
; x& { B2 N$ Q Y$ r4 d+ c) ~ m1 q) W
当 mongos 接收到用户请求时,它先查询 config server,找到存放相应数据的shard servers。然后把用户请求,转发到这些 shard servers。当这些 shard servers完成操作后,它们把结果分别返回给 mongos。而当 mongos 汇总了所有的结果后,它把结果返回给用户。1 V+ m0 S( y1 N
% g7 \! d4 _+ u7 g' h2 t/ A Mongos每次启动的时候,都要到config servers中读取元数据,并缓存在本地。每当 config server中的元数据有改动,它都会通知所有的mongos。. J6 z3 {5 U* c% u
# _$ r) T/ V1 ~
Mongos之间,不存在彼此协同工作的问题。因此,MongoDB所需要配置的mongos server的数量,没有限制。
8 K3 F6 R& X' g# y) n3 w" X+ m0 N; W# _5 W2 P; t
通过以上的介绍,我们对每个组成部分都有了基本的了解,但是涉及到工作的细节,我们尚有诸多疑问,例如,一个chunk的数据太大,如何切分?一个shard数据太多,如何迁移?在replica set中,如何选择primary?server挂了,怎么进行故障恢复?接下来的章节,我们逐个回答这些问题。
# L2 V6 E; n2 Y$ A; h- d. R( D8 u9 s( B( |( E; a' Y' a/ D7 O0 t3 c
3 N, ]" H" t; J/ a2 ^
Reference,7 P u- L. `. F" A: w& I6 n6 w/ i7 Y
! d4 e5 `' L. o g. L
[0] Architectural Overview
5 E) O# N/ E. ihttp://www.mongodb.org/display/DOCS/Sharding+Introduction
g1 i3 C! d4 X |
评分
-
查看全部评分
|