有时候为了考虑应用程序的性能或响应性,为了提升读取操做的吞吐率,一个常见的措施就是进行读写分离,MongoDB副本集对读写分离的支持是经过Read Preferences特性进行支持的,这个特性很是复杂和灵活。如下几种应用场景可能会考虑对副本集进行读写分离:前端
1)操做不影响前端应用程序,好比备份或者报表;sql
2)在一个物理上分布的副本集群中,为了减小应用程序的延迟,可能会优先选择离应用程序更近的secondary节点而不是远在千里以外机房的主节点;mongodb
3)故障发生时候可以提供一个优雅的降级。副本集primary节点宕机后再选出新的primary节点这段时间内(10秒或更长时间)可以依然响应客户端应用的读请求;shell
4)应用可以容忍必定程度的数据不一致性。数据库
Read References:网络
应用程序驱动经过read reference来设定如何对副本集进行读取操做,默认的,客户端驱动全部的读操做都是直接访问primary节点的,从而保证了数据的严格一致性。数据结构
但有时为了缓解主节点的压力,咱们可能须要直接从secondary节点读取,只须要保证最终一致性就能够了。并发
MongoDB 2.0以后支持五种的read preference模式:app
primary:默认,只从主节点上进行读取操做;异步
primaryPreferred:在绝大部分的情形都是从主节点上读取数据的,只有当主节点不可用的时候,好比在进行failover的10秒或更长的时间内会从secondary节点读取数据。
警告:2.2版本以前的MongoDB对Read Preference支持的还不彻底,若是客户端驱动采用primaryPreferred实际上读取操做都会被路由到secondary节点。
secondary:只从secondary节点上进行读取操做,存在的问题是secondary节点的数据会比primary节点数据“旧”。
secondaryPreferred:优先从secondary节点进行读取操做;
nearest:既有可能从primary,也有可能从secondary节点读取,这个决策是经过一个叫member selection过程处理的。
MongoDB容许在不一样的粒度上指定这些模式:链接、数据库、集合甚至单次的操做。不一样语言的驱动基本都支持这些粒度。
oplog是一种特殊的capped collection,用来滚动的保存MongoDB中全部数据操做的日志。副本集中secondary节点异步的从primary节点同步oplog而后从新执行它记录的操做,以此达到了数据同步的做用。这就要求oplog必须是幂等的,也就是重复执行相同的oplog记录获得的数据结构必须是相同的。
事实上副本集中全部节点之间都相互进行heartbeat来维持联系,任何节点都能从其它节点复制oplog。
capped collection是MongoDB中一种提供高性能插入、读取和删除操做的固定大小集合。当集合被填满的时候,新的插入的文档会覆盖老的文档。由于oplog是capped collection因此指定它的大小很是重要。若是过小那么老的文档很快就被覆盖了,那么宕机的节点就很容易出现没法同步数据的结果,但也不是越大越好,MongoDB在初始化副本集的时候都会有一个默认的oplog大小:
首先生产环境使用MongoDB毫无疑问必须的是64为操做系统。其次大多数状况下默认的大小是比较适合的。举个例子,若是oplog大小为空闲磁盘的5%,它在24H内能被填满,也就是说secondary节点能够中止复制oplog达24H后仍然可以catch up上primary节点。并且一般的MongoDB副本集的操做量要比这低得多。
oplog数据结构:
oplog的数据结构以下所示:
{ ts : ..., op: ..., ns: ..., o: ... o2: ... }
其中op有如下几个值:
注:关于oplog有两个常见的错误timestamp error和duplicate error,参看这里:http://docs.mongodb.org/manual/tutorial/troubleshoot-replica-sets/#replica-set-troubleshooting-check-oplog-size
查看oplog大小:
经过db.printReplicationInfo() 能够查看副本集节点的oplog状态:
1. rs0:PRIMARY> db.printReplicationInfo() 2. configured oplog size: 1793.209765625MB 3. log length start to end: 12.643999999854714secs (0hrs) 4. oplog first event time: Sat Jan 17 1970 06:22:38 GMT+0800 (CST) 5. oplog last event time: Sat Jan 17 1970 06:22:51 GMT+0800 (CST) 6. now: Sat Aug 17 2013 18:02:12 GMT+0800 (CST)
以我以前搭建的副本集为例,oplog的大小是1793MB,其中持有的数据时间区间只有12秒。
修改oplog大小:
能够在启动mongod的时候指定--oplogSize,单位MB:
7. ./bin/mongod --fork --dbpath data/rs0-0/ --logpath log/rs0-0/rs0-0.log --rest --replSet rs0 --oplogSize 500 --port 37017
但有的时候咱们可能须要修改现有副本集的oplog大小。这个本人很是不推荐,官网有详细的教程,这里我就不赘述了,能够看这里:http://docs.mongodb.org/manual/tutorial/change-oplog-size/。
在现有的副本集中修改oplog的大小是至关麻烦的并且影响副本集性能,所以咱们最好是预先根据应用的状况评估好oplog的大小:若是应用程序是读多写少,那么默认的大小已经足够了。若是你的应用下面几种场景不少可能考虑须要更大的oplog:
db.test.update({foo: "bar"}, {$set: {test: "success!"}}, false, true);
上面三点总结起来就是消耗了大量的oplog可是数据量却没有等量的增长。
数据滞后:
前面已经提到MongoDB副本集中secondary节点是经过oplog来同步primary节点数据的,那具体的细节是怎么样的?在说数据如何同步之间先介绍一下replication lag,由于存在数据同步那必然存在必定程度的落后。这个问题对于整个MongoDB副本集的部署是相当重要的。
1. rs0:PRIMARY> db.printSlaveReplicationInfo() 2. source: 192.168.129.129:37019 3. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 4. = 172971 secs ago (48.05hrs) 5. source: 192.168.129.129:37020 6. syncedTo: Thu Jan 01 1970 08:00:00 GMT+0800 (CST) 7. = 1376744556 secs ago (382429.04hrs)
当前集群的情况是,37017端口是primary节点,37019和37020是secondary节点,其中37020已经宕机,能够看到37019同步数据是在两天前(由于这两天我没有对副本集有任何数据操做),而宕机的节点显示的同步时间是一个很早时间点。
如今从新启动37020后再执行命令:
1. rs0:PRIMARY> db.printSlaveReplicationInfo() 2. source: 192.168.129.129:37019 3. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 4. = 175566 secs ago (48.77hrs) 5. source: 192.168.129.129:37020 6. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 7. = 175566 secs ago (48.77hrs)
能够看到两个secondary节点的同步时间是一致的,咱们向集群中插入几条数据后再执行db.printSlaveReplicationInfo():
1. rs0:PRIMARY> db.test.insert({"name":"zhanjindong","age":23}) 2. rs0:PRIMARY> db.printSlaveReplicationInfo() 3. source: 192.168.129.129:37019 4. syncedTo: Sat Aug 17 2013 21:48:31 GMT+0800 (CST) 5. = 6 secs ago (0hrs) 6. source: 192.168.129.129:37020 7. syncedTo: Sat Aug 17 2013 21:48:31 GMT+0800 (CST) 8. = 6 secs ago (0hrs)
能够看到很快就引起了primary和secondary之间的数据同步操做。
“滞后”是不可避免的,须要作的就是尽量减少这种滞后,主要涉及到如下几点:
数据同步:
副本集中数据同步有两个阶段。
初始化(initial sync):这个过程发生在当副本集中建立一个新的数据库或其中某个节点刚从宕机中恢复,或者向副本集中添加新的成员的时候,默认的,副本集中的节点会从离它最近的节点复制oplog来同步数据,这个最近的节点能够是primary也能够是拥有最新oplog副本的secondary节点。这能够防止两个secondary节点之间相互进行同步操做。
复制(replication):在初始化后这个操做会一直持续的进行着,以保持各个secondary节点之间的数据同步。
在MongoDB2.0之后的版本中,一旦初始化中肯定了一个同步的目标节点后,只有当和同步节点之间的链接断开或链接过程当中产生异常才可能会致使同步目标的变更,而且具备就近原则。考虑两种场景:
在2.2版本之后,数据同步增长了一些额外的行为:
注:buildIndexes指定副本集中成员是否能够建立索引(某些状况下好比没有读操做或者为了提升写性能能够省略索引的建立)。固然即便该值为false,MongoDB仍是能够在_id上建立索引觉得复制操做服务。
从新数据同步:
有时当secondary节点落后太多没法追遇上primary节点的时候,这时候可能须要考虑从新同步数据(Resync data)。
有两种方法一种是指定一个空的目录从新启动落后的节点,这很简单,可是数据量大的状况下回花费很长的时间。另外一种方法是基于另外一个节点的数据做为“种子”进行从新同步,关于这两种方法在后面向一个现有副本集中添加成员一节会有详细说明。
在如下几种情景发生的时候,副本集经过“选举”来决定副本集中的primary节点:
在一次选举中包括hidden节点、仲裁者甚至正处于recovering状态的节点都具备“投票权”。默认配置中全部参与选举的节点具备相等的权利,固然在一些特定状况下,应明确的指定某些secondary会优先成为primary,好比一个远在千里以外异地机房的节点就不该该成为primary节点,选举的权重经过设置priority来调节,默认该值都是1,在前面简单副本集的搭建中已经介绍过了如何修改该值。
集群中任何一个节点均可以否决选举,即便它是non-voting member:
首先获取最多选票的成员(实际上要超过半数)才会成为primary节点,这也说明了为何当有两个节点的集群中primary节点宕机后,剩下的只能成为secondary,当primary宕掉,此时副本集只剩下一个secondary,它只有1票,不超过总节点数的半数,它不会选举本身为primary。
要想更详细的了解选举细节,参看这篇源码分析的文章:http://nosql-db.com/topic/514e6d9505c3fa4d47017da6