|
关于MongoDB,我们能看到的资料,基本都是在指导大家如何使用MongoDB,但是,MongoDB内部是如何运作的,资料不是很多。
& {7 v: V; q! Z S: p/ [- M. I$ b9 D: D9 b- y ]/ r! e3 r
阅读使用手册,会有很多疑惑之处。例如,有人说,MongoDB 等同于分布式的 MySQL。它把一个Table ,按 row,分割成多个Shards,分别存放在不同的 Servers 上。这种说法是否正确?+ F7 n+ @% Z6 C; c e
N! R( }1 [1 p- s+ \. j0 H2 W 不深入了解 MongoDB 的内部结构,就无法透彻地回答类似问题。这个系列文章,就来和大家探讨MongoDB的内部的工作方式。; [# w! h. B8 W# v9 b @
3 x" K8 V' @: u7 y8 P
$ C+ }+ ^& u- R0 k8 [) w R1 N9 ?; I# [
图1-1 MongoDB架构图
" b/ f6 O& P- ?2 d: |" r5 f' D) Y# O3 y
MongoDB 通常运行在一个服务器集群上,而不是一个单机。图1-1,描述了一个MongoDB集群的基本组成部分,包括若干shards,至少一个config server,至少一个routing servers(又称 mongos)。9 N9 M! }+ }8 P+ j; C
# ?9 r7 J9 x/ m3 q% G
Shards( N7 t9 ~! ], J) u6 p$ c
' S0 q- {( v. M7 [' z0 W+ s! d& b MongoDB的最基本的数据单元,叫document,类似于关系式数据库中的行 row。一系列documents,组成了一个collection,相当于关系式数据库中的table。当一个 collection 数据量太大时,可以把该collection按documents切分,分成多个数据块,每个数据块叫做一个chunk,多个chunks聚集在一起,组成了一个shard。* [' {( Y' x2 c( ^2 Z: H
" M. q( W7 L I: e/ p, F Sharding 的意义,不仅保障了数据库的扩容(scalability),同时也保障了系统的负载均衡(load balance)。/ p5 S W% Y7 r; @
6 @" V/ w5 t: M) o
每一个shard存储在一个物理服务器(server)上。Server上运行着mongod进程,通过这个进程,对shard中的数据进行操作,主要是增删改查。
& b: i, C" h# @
* U( a/ d O J' I2 H# r' j 如果系统中的每个shard,只存储了一份数据,没有备份,那么当这个shard所在的server挂了,数据就丢失了。在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。
0 f/ E9 |; @: d/ @1 M1 n6 q; P" w( X7 M" ^2 v
Shard keys
9 X: d0 ?) }- b8 U {! l9 R
1 W! u1 ~2 ?; a" l0 H& u, C4 E2 H9 A% N 为了把collection切分成不同的chunks,从而存放到不同的shards中,我们需要制定一个切分的方式。
" }, e- J8 @/ s; j" B3 t+ t$ i; H
( h; N% ]3 s3 ]6 r( p1 k i4 X l 如前所述,在 MongoDB 数据库中,一个表collection由多个行 documents 组成,而每个 document,有多个属性 fields。同一个 collection 中的不同的 documents,可能会有不同的 fields。例如,有个 collection 叫 Media,包含两条 documents,) U% D0 `; R/ h/ ?6 X
* c4 I3 z" g$ h3 r- Z
{
, ]6 L$ n5 z8 r6 k. I i "ISBN": "987-30-3652-5130-82",
- P: Q1 `) C4 k; u' F "Type": "CD",! _, b" Z# r* r8 E
"Author": "Nirvana",: U4 p6 C5 n! H8 e: q" N
"Title": "Nevermind",
- V+ W4 Q& S& r6 C4 G "Genre": "Grunge",6 E$ J9 h9 [" B/ F, c
"Releasedate": "1991.09.24",
) y8 `- i! |" K" h+ t4 _3 J! G "Tracklist": [: c- D1 M2 A: N/ _
{7 g: l2 X6 X$ g! \+ {: \ S \8 P
"Track" : "1",
. {4 u$ a! R8 V2 t) d "Title" : "Smells like teen spirit",
7 D) I# X2 F. c$ r1 ` ` "Length" : "5:02"- J8 O6 H- w2 j+ J# b
}, ?- Y. W) g; T4 H
{- @6 f% H; `" W# W
"Track" : "2",
, J i( e! M+ V. s- |2 m "Title" : "In Bloom",8 P# f3 ]- p' O
"Length" : "4:15"( g; @$ _3 a Z* w m+ q8 A
}
$ A1 t4 R( i1 n/ k ]
5 d( n& q5 Z5 J* x8 u( m/ j% t# Z}; y$ K. o1 X4 M/ w5 X" @$ Q
; r4 c6 o+ \- u H% R$ H
{ ~4 m; ~+ y, [" B& }! t' Z
"ISBN": "987-1-4302-3051-9",
7 j; }9 J, c' b" n, y) q( s! s2 w "Type": "Book",5 w4 T+ v4 c5 q: b! I. k1 F7 C
"Title": "Definite Guide to MongoDB: The NoSQL Database",) `/ W6 W# P1 }! |" I9 v! X
"Publisher": "Apress",
! m% U* p- y1 {+ ~% y! J( w "Author": " Eelco Plugge",1 b, g9 p5 w6 B( d8 g2 Q$ c
"Releasedate": "2011.06.09"
1 a9 A3 x' ?, T! ^8 |: |}& A, m# ^! {) N- ?# U& ~4 H
B. X; X: l3 l( C; b2 x
假如,在同一个 collection 中的所有 document,都包含某个共同的 field,例如前例中的“ISBN”,那么我们就可以按照这个 field 的值,来分割 collection。这个 field 的值,又称为 shard key。2 Y& m. D0 }; S$ T
" c! e5 O, z b8 V2 W6 T4 ?: P1 c
在选择shard key的时候,一定要确保这个key能够把collection均匀地切分成很多chunks。& \5 L! g5 q8 u/ ^
7 a# b; g% G2 r% q
例如,如果我们选择“author”作为shard key,如果有大量的作者是重名的,那么就会有大量的数据聚集在同一个chunk中。当然,假设很少有作者同名同姓,那么“author”也可以作为一个shard key。换句话说,shard key 的选择,与使用场景密切相关。
( z- C x+ r. e2 W+ U. f. L% k# F9 ?
$ ]1 j6 A+ X% `. G/ T7 a( W 很多情况下,无论选择哪一个单一的 field 作为shard key,都无法均匀分割 collection。在这种情况下,我们可以考虑,用多个 fields,构成一个复合的shard key。! i/ y5 l6 i$ o- v
& T& A# j8 W! U+ h% a
延续前例,假如有很多作者同名同姓,他们都叫“王二”。用 author 作为 shard key,显然无法均匀切割 collection。这时我们可以加上release-date,组成name-date的复合 shard key,例如“王二 2011”。
% n0 v, B% X9 U5 g# t0 l( x+ m+ _) i' C7 c' T/ H& M' P' a& Z
Chunks
3 a, q# n2 Y4 S 0 f% G3 }) S' L0 h
MongoDB按 shard key,把 collection切割成若干 chunks。每个 chunk 的数据结构,是一个三元组,{collection,minKey,maxKey},如图1-2 所示。
" ~( J: h! R* n+ T/ t% e+ I: h. }9 z! c4 ?. j
: l3 j) P; w) M( e* @图1-2 chunk的三元组 # _- D5 Y* o! ? b# N4 k
( g1 X4 k# m3 ~$ C1 q4 I+ W" H2 \. y+ s 其中,collection 是数据库中某一个表的名称,而 minKey 和 maxKey 是 shard key的范围。每一个 document 的shard key 的值,决定了这条document应该存放在哪个chunk中。
9 Z7 L$ t. k# `3 t9 R5 r* y- x! L+ t: D5 o" W! G
如果两条 documents 的 shard keys 的值很接近,这两条 documents 很可能被存放在同一个 chunk 中。
8 X# W) G3 ~9 [% }# @
7 t O: r$ M6 \4 D5 m* w, g- q6 K* T6 \ Shard key 的值的顺序,决定了 document 存放的 chunk。在 MongoDB 的文献中,这种切割 collection 的方式,称为order-preserving。
1 u. M) v4 R, ]0 |
* I3 A$ k& m( x$ Z5 ~3 S/ y7 S 一个 chunk最多能够存储64MB的数据。 当某个chunk存储的 documents包含的数据量,接近这个阈值时,一个chunk会被切分成两个新的chunks。0 g8 p$ b' g R8 b& o
' s3 K' r0 L, g3 h$ d. ` 当一个shard存储了过多的chunks,这个shard中的某些chunks会被迁移到其它 shard中。$ r; z1 R5 R5 \& `
, z) H& m/ \, H; Q$ `7 a; {
这里有个问题,假如某一条 document 包含的数据量很大,超过 64MB,一个 chunk 存放不下,怎么办?在后续章节介绍 GridFS 时,我们会详细讨论。
3 R1 H" B: q8 Q, d2 U8 i% R( q% H6 s6 H4 v8 s5 O
Replica set
I5 o4 V. L" z# x- a6 ~+ g / A6 w' y: w; g" B/ C- P
在生产环境中,为了保证数据不丢失,为了提高系统的可用性(availability),每一个shard被存储多份,每个备份所在的servers,组成了一个replica set。
$ ]% P+ a3 Z; o$ \! o# ~( w' V% q! e7 \
这个replica set包括一个primary DB和多个secondary DBs。为了数据的一致性,所有的修改(insert / update / deletes) 请求都交给primary处理。处理结束之后,再异步地备份到其他secondary中。# v5 o/ n$ Y2 `# e( s6 y
0 m- u5 N+ B2 O+ V3 n2 ^$ P7 l2 C Primary DB由replica set中的所有servers,共同选举产生。当这个primaryDB server出错的时候,可以从replica set中重新选举一个新的primaryDB,从而避免了单点故障。7 A6 [2 D- k: L2 ~5 x$ M2 _6 T
1 \9 R# N7 a6 t: Q Replica set的选举策略和数据同步机制,确保了系统的数据的一致性。后文详述。+ m* {3 b m* W; N) Q
4 R! O1 b9 Q2 C7 K
Config Server& s5 |; z3 [+ a: `
9 I1 @0 @& y) o
Config servers用于存储MongoDB集群的元数据 metadata,这些元数据包括如下两个部分,每一个shard server包括哪些chunks,每个chunk存储了哪些 collections 的哪些 documents。
+ ~. ?7 S( P7 F8 w, S, ~& q* F! p+ s# h( r0 c Q" M+ o
每一个config server都包括了MongoDB中所有chunk的信息。4 H% [, f" D3 N. C
; c8 l- p+ e( b& U6 d. A4 m2 t
Config server也需要 replication。但是有趣的是,config server 采用了自己独特的replication模式,而没有沿用 replica set。
, L5 w1 I7 g# S8 n% t6 I$ w
0 y- l' W' _' ]% v7 q 如果任何一台config server挂了,整个 config server 集群中,其它 config server变成只读状态。这样做的原因,是避免在系统不稳定的情况下,冒然对元数据做任何改动,导致在不同的 config servers 中,出现元数据不一致的情况。
) A" t ^, K# ]6 P: D7 F2 m
" L# D( t1 P8 m$ k$ f3 j8 R MongoDB的官方文档建议,配置3个config servers比较合适,既提供了足够的安全性,又避免了更多的config servers实例之间的数据同步,引起的元数据不一致的麻烦。* T. ?$ L* Q" F6 E* u' D5 O( J8 `& Q
' ~0 Q v/ l- Y* L$ K: K/ QMongos/ I, X' v% _2 [( ^( G
: F' d% o; b) Q8 w2 n! X4 R, z+ Y 用户使用MongoDB 时,用户的操作请求,全部由mongos来转发。
" U! i* \; o8 T7 B5 Z) |4 t) o0 H
当 mongos 接收到用户请求时,它先查询 config server,找到存放相应数据的shard servers。然后把用户请求,转发到这些 shard servers。当这些 shard servers完成操作后,它们把结果分别返回给 mongos。而当 mongos 汇总了所有的结果后,它把结果返回给用户。
( J) T4 K% X5 g1 G( |* C6 L( x+ m. J: x
. J$ h1 L' u4 W2 ?" a Mongos每次启动的时候,都要到config servers中读取元数据,并缓存在本地。每当 config server中的元数据有改动,它都会通知所有的mongos。
' B" f7 j# e3 h+ f2 Z4 x3 }2 u$ R
. r/ N3 N8 j7 V Mongos之间,不存在彼此协同工作的问题。因此,MongoDB所需要配置的mongos server的数量,没有限制。
6 T2 `9 N3 T, C( _- K! R6 q% ^
通过以上的介绍,我们对每个组成部分都有了基本的了解,但是涉及到工作的细节,我们尚有诸多疑问,例如,一个chunk的数据太大,如何切分?一个shard数据太多,如何迁移?在replica set中,如何选择primary?server挂了,怎么进行故障恢复?接下来的章节,我们逐个回答这些问题。: o: d( g% N3 }# h0 O1 K
: b, N8 k. d: r8 h- s. S. r% j1 y( T
% H1 S: L* I1 n5 H
Reference,7 s) _2 `- }0 ~) \6 j# g7 Z- F
( T. Q3 [; m; {: [. s[0] Architectural Overview
+ y( K+ S1 k( W( x f; j3 Vhttp://www.mongodb.org/display/DOCS/Sharding+Introduction1 E# y1 h/ X/ y" c& }4 b2 y, r+ ^
|
评分
-
查看全部评分
|