一 数据同步 一个健康的secondary在运行时,会选择一个离自己最近的,数据比自己新的节点进行数据同步。选定节点后,它会从这个节点拉取oplog同步日志,具体流程是这样的: a.执行这个op日志 b.将这个op日志写入到自己的oplog中(local.oplog.rs)
c.再请求下一个op日志
如果同步操作在第1步和第2步之间出现问题宕机,那么secondary再重新恢复后,会检查自己这边最新的oplog,由于第2步还没有执行,所以自己这边还没有这条写操作的日志。这时候他会再把刚才执行过的那个操作执行一次。那对同一个写操作执行两次会不会有问题呢?mongodb在设计oplog时就考虑到了这一点,所以所有的oplog都是可以重复执行的,比如你执行 {$inc:{counter:1}} 对counter字段加1,counter字段在加1 后值为2,那么在oplog里并不会记录 {$inc:{counter:1}} 这个操作,而是记录 {$set:{counter:2}}这个操作。所以无论多少次执行同一个写操作,都不会出现问题。
注:从节点不一定要从主节点的操作日志来读取数据,它也可以选择距离自己最近的(根据ping的时间来计算)的且比自己操作日志记录更新的从节点获取操作日志。二 同步过程 当我们在mongodb时执行一个写操作时,默认会直接返回成功,同时也可以通过设置w参数,指定这个写操作同步到几个节点后才返回成功。如下:
db.foo.runcommand({getlasterror:1, w:2})
上面例子就是执行getlasterror命令,使其在上一个写操作同步到两个节点上后再返回。不同的客户端可能在写法上不太一样,不过这个功能应该都是有的。对于重要数据,可以考虑采用这样的方式,通过牺牲一部分写性能来提升数据的安全性。
这个功能是如何实现的呢,primary节点是如何知道数据同步了几份呢?在调用上面命令时,实际上mongodb内部执行了如下的一些流程:
a.在primary上完成写操作
b.在primary上记录一条oplog日志,日志中包含一个ts字段,值为写操作执行的时间,比如本例中记为t
c.客户端调用{getlasterror:1, w:2}命令等待primary返回结果
d.secondary从primary拉取oplog,获取到刚才那一次写操作的日志
e.secondary按获取到的日志执行相应的写操作
f.执行完成后,secondary再获取新的日志,其向primary上拉取oplog的条件为{ts:{$gt:t}}
g.primary此时收到secondary的请求,了解到secondary在请求时间大于t的写操作日志,所以他知道操作在t之前的 日志都已经成功执行了
h.这时候getlasterror命令检测到primary与secondary都完成了这次写操作,于是 w:2 的条件满足了,返回给客户端成功
注意:1.如果设置的w参数大于当前复制集中的从节点数目的话,写入操作会被阻塞,一直到写入节点数达到w参数所设置的数据才会返回。 2.将w参数设置成当前负责集合中从节点的数目的话,这个复制集将会对外提供强一致性的服务,但是整个复制集的写性能也会下降。启动初始化 当一个新节点启动并加入到现在的replica sets中时,这时候新启动的节点会查看自己的oplog,通过一个叫 lastoptimewritten 的命令查找到它最近的一条写操作。这个命令你也可以随便在命令行执行:
> rs.debug.getlastopwritten()
这个命令会返回一条oplog记录,其中的ts字段就是最近一次写操作的时间了。
如果你这个节点是全新的,没有数据,那么oplog里也没有数据,这时候节点会选择执行一次全量的同步。本文暂时不对全量同步的方法进行描述。
选择同步源节点 replica sets中的节点之间总在同步数据,但是他们不是通过传统的一主多从的方式来同步的。mongodb的策略是选择一个合适的节点作为数据源。
首先secondary节点会通过ping的时间来确定其它节点与它的距离。时间越长的识为距离越远。然后通过下面方法确定其源节点:
for each member that is healthy: if member[state] == primary add to set of possible sync targets if member[lastoptimewritten] > our[lastoptimewritten] add to set of possible sync targetssync target = member with the min ping time from the possible sync targets
对于节点是否healthy的判断,各个版本不同,但是其目的都是找出正常运转的节点。在2.0版本中,它的判断还包括了salve delay这个因素。
你可以通过运行db.admincommand({replsetgetstatus:1})命令来查看当前的节点状况,在secondary上运行这个命令,你能看到syncingto这个字段,这个字段的值就是这个secondary的同步源。(其实名字应该是叫syncingfrom,但是由于版本兼容的原因,沿用了这个错误的名字)
链式同步结构 vcq9vfjq0m2ssr2ho8v50tth6b/2u+g4tntt0rvqqagjpc9wpgo8cd4kicagicagicagscji587sw8ft0l3atenbo6zoqnbyaw1hcnm92rxjo6ziu7rzqr3atepoqnnly29uzgfyeb3ateojrmv8tnnbvdq1482ssr3k/b7do6znrmqx09bt0hnly29uzgfyeb3atendo6zl/lttzazkx3nly29uzgfyebxeqr3atepnrlk9yv2+3agj1elr+uetjmd0o0itjmd0o0pwrrzkvs3qzrpjwcvsu7j2wbtkvbxezayyvb3hubmho8jnufvo0sphyei2qhfoqjojrmthw7rbvdq148jnus7e3naqtcbdvdq149lrvq2000k92rxjzayyvbpjuabby8tyo788l3a+cgogicagicaginxiysfnqln9b3bsb2fnrlk90k3s6cc0yrxp1rxeo6zo0sph08pnqmvxtctt79huwls94srn0rvpwm9wbg9ntctnrlk90k3s6agjcgo8yni+cgokicagys61suo000lnrlk9yv2+3cqxo6xdu+hu2tct0unw0lbuqsu1o7ro0tkqtnpe49xizayyvcr9vt3by6osyoe5+9c0stnx99pqd7loyv21xluwo6zo0rxezayyvdkyy+pjz7djoamkicagyi7iu7rzqrvhu9i08mu1o7ro0rk7ysfsu7j2chjpbwfyeb3ateojrm7su+gw0ctjtctv4rj2vmbk/deqtb3o0rxezayyvds0yc/iptxicj4kicagyy7iu7rzqttzttrbtpk/qtk7upbqwrxeway906ossqlh0rbuqcu1o7rv4rj2way908tjvs21sbpjysddtcswyaos0rll49k7upa8xsr91np3wo+hozxicj4kicagzc7v4sqxuvlu2kg/tmc0o6y+zdpqwb249sgsvdpbrlw9y/vjz8pmo6zsu7j2ysdco6zsu7j2ysfq6ctitcrdo6zv4sg9upbbrl3ttrze3lgouobl+8u1zeqzychlzayyvblz1/ehozxicj4kpha+ciagicagicagtbhsu7j20lsy2df31npbyc/wtndquvojrelk18/izayyvbw91ek49rlz1/e1xg9wbg9no6zwtndqzeq687vhuobl30gjrm7s1rtq0m3qwcuho8i7uvndzazr+bttqsnpu/hiobw9qrxeb3bsb2ejrnky1rtq0mhl1elsu8z10lsy2df3o6ziu7rzy/u45svfqqosztlwtndqzerby6osqttaytw1vdxiupbp7nomuvojrlvhzai5/bjvssw/qs2otctq6ctizai1wlj6qcu1o6zo0srh0one4rxeq73ateojrm7s0rln6rpj0lsy2df3wcuho9xiyrg68kg+zdaqtccjreghokkhokpi/bj2vdq147a8zeqzydc0stnx98hloan3o7oztctm9bz+wvrx46osylu687e1u9i4+lx308nnzxrmyxn0rxjyb3k1xl/nu6e2y6oszeqzydxitm6y2df3oam8l3a+cjxwpgq+38zlyp249r3ateo85lxeway908jnz8lnvko6pc9wpgo8chjlignsyxnzpq==brush:java;>c b a
b和a之间有两条通道,双线那条是真正的同步连接,单线那条是一个虚拟连接。
注意:mongodb这种链式同步结构类似于hadoop中hdfs中数据块的流式复制,这样的好处是可以大大减轻主节点的压力,提高数据同步的速度。
三 新功能展望 上面就是当前的replica sets同步的内部实现,在后续这一块mongodb还会进行一些新特性的开发。在2.2版本中,会提供replsetsyncfrom命令,让用户可以手动设置一个secondary的同步源。使用方法大概是这样:
> db.admincommand({replsetsyncfrom:otherhost:27017})
