MongoDB 性能优化

Read Preferences/读写分离

有时候为了考虑应用程序的性能或响应性,为了提升读取操做的吞吐率,一个常见的措施就是进行读写分离,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

oplog是一种特殊的capped collection,用来滚动的保存MongoDB中全部数据操做的日志。副本集中secondary节点异步的从primary节点同步oplog而后从新执行它记录的操做,以此达到了数据同步的做用。这就要求oplog必须是幂等的,也就是重复执行相同的oplog记录获得的数据结构必须是相同的。

事实上副本集中全部节点之间都相互进行heartbeat来维持联系,任何节点都能从其它节点复制oplog。

capped collection是MongoDB中一种提供高性能插入、读取和删除操做的固定大小集合。当集合被填满的时候,新的插入的文档会覆盖老的文档。由于oplog是capped collection因此指定它的大小很是重要。若是过小那么老的文档很快就被覆盖了,那么宕机的节点就很容易出现没法同步数据的结果,但也不是越大越好,MongoDB在初始化副本集的时候都会有一个默认的oplog大小:

  • 在64位的Linux,Solaris,FreeBSD以及Windows系统上,MongoDB会分配磁盘剩余空间的5%做为oplog的大小,若是这部分小于1GB则分配1GB的空间。
  • 在64的OS X系统上会分配183MB。
  • 在32位的系统上则只分配48MB。

首先生产环境使用MongoDB毫无疑问必须的是64为操做系统。其次大多数状况下默认的大小是比较适合的。举个例子,若是oplog大小为空闲磁盘的5%,它在24H内能被填满,也就是说secondary节点能够中止复制oplog达24H后仍然可以catch up上primary节点。并且一般的MongoDB副本集的操做量要比这低得多。

 

oplog数据结构

oplog的数据结构以下所示:

{ ts : ..., op: ..., ns: ..., o: ... o2: ...  }

  • ts: 8字节的时间戳,由4字节unix timestamp + 4字节自增计数表示。这个值很重要,在选举(如master宕机时)新primary时,会选择ts最大的那个secondary做为新primary。
  • op:1字节的操做类型,例如i表示insert,d表示delete。
  • ns:操做所在的namespace。
  • o:操做所对应的document,即当前操做的内容(好比更新操做时要更新的的字段和值)
  • o2: 在执行更新操做时的where条件,仅限于update时才有该属性

其中op有如下几个值:

  • "i": insert
  • "u": update
  • "d": delete
  • "c": db cmd
  • "db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')
  • "n":  no 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:

  • 在同一个时刻更新多个文档:oplog为了维持幂等性必须将mutil-updates翻译成一个个独立的操做,这会用去大量的oplog空间,但数据库中的数据量却没有相对称的增长。
  • 多文档同时更新从1.1.3就有的特性,在mongo shell执行相似以下的命令,第四个参数必须制定为true:
  • db.test.update({foo: "bar"}, {$set: {test: "success!"}}, false, true);
  • 在插入时同时删除相同大小数据:和上面的结果同样在数据量没有增长的状况下却消耗了大量的oplog空间。
  • 大量的In-Place更新操做:In-Place更新是指更新文档中原有的部分,但并不增长文档的大小。

上面三点总结起来就是消耗了大量的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之间的数据同步操做。

“滞后”是不可避免的,须要作的就是尽量减少这种滞后,主要涉及到如下几点:

  • 网络延迟:这是全部分布式系统都存在的问题。咱们能作的就是尽量减少副本集节点之间的网络延迟。
  • 磁盘吞吐量:secondary节点上数据刷入磁盘的速度比primary节点上慢的话会致使secondary节点很难跟上primary节点的节奏。
  • 并发:并发大的状况下,primary节点上的一些耗时操做会阻塞secondary节点的复制操做,致使复制操做跟不上主节点的写入负荷。解决方法是经过设置操做的write concern(参看这里:http://docs.mongodb.org/manual/core/write-concern/#replica-set-write-concern)默认的副本集中写入操做只关心primary节点,可是能够指定写入操做同时传播到其余secondary节点,代价就是严重影响集群的并发性。
    • 注意:并且这里还存在一个问题若是,若是写入操做关心的某个节点宕机了,那么操做将会一直被阻塞直到节点恢复。
  • 适当的write concern:咱们为了提升集群写操做的吞吐量常常会将writer concern设置为unacknowledged write concern,这致使primary节点的写操做很快而secondary节点复制操做跟不上。解决方法和第三点是相似的就是在性能和一致性之间作权衡。

数据同步:

副本集中数据同步有两个阶段。

初始化(initial sync):这个过程发生在当副本集中建立一个新的数据库或其中某个节点刚从宕机中恢复,或者向副本集中添加新的成员的时候,默认的,副本集中的节点会从离它最近的节点复制oplog来同步数据,这个最近的节点能够是primary也能够是拥有最新oplog副本的secondary节点。这能够防止两个secondary节点之间相互进行同步操做。

复制(replication):在初始化后这个操做会一直持续的进行着,以保持各个secondary节点之间的数据同步。

在MongoDB2.0之后的版本中,一旦初始化中肯定了一个同步的目标节点后,只有当和同步节点之间的链接断开或链接过程当中产生异常才可能会致使同步目标的变更,而且具备就近原则。考虑两种场景:

  • 1) 有两个secondary节点在一个机房,primary在另一个机房。假设几乎在同一时间启动这三个实例(以前都没有数据和oplog),那么两个secondary节点应该都是从primary节点同步数据,由于他们以前见都不会拥有比对方更新的oplog。若是重启其中一个secondary,那么它的同步目标将会变成另外一个secondary,由于就近原则。
  • 2) 若是有一个primary和一个secondary分别在不一样的机房,那么在以前secondary所在的机房中向副本集中新加一个节点时,那么新节点必然是从原先的那个secondary节点同步数据的。

在2.2版本之后,数据同步增长了一些额外的行为:

  • 1) secondary节点只有当集群中没有其余选择的时候才会从delayed节点同步数据;
  • 2) secondary节点毫不会从hidden节点同步数据;
  • 3) 当一个节点新加入副本集中会有一个recovering过程,在这段时间内secondary不会进行数据同步操做;
  • 4) 当一个节点从另外一个节点同步数据的时候,须要保证两个节点的local.system.replset.members[n].buildIndexes值是同样的,要不都是false,要不都是true。

:buildIndexes指定副本集中成员是否能够建立索引(某些状况下好比没有读操做或者为了提升写性能能够省略索引的建立)。固然即便该值为false,MongoDB仍是能够在_id上建立索引觉得复制操做服务。

从新数据同步

有时当secondary节点落后太多没法追遇上primary节点的时候,这时候可能须要考虑从新同步数据(Resync data)。

有两种方法一种是指定一个空的目录从新启动落后的节点,这很简单,可是数据量大的状况下回花费很长的时间。另外一种方法是基于另外一个节点的数据做为“种子”进行从新同步,关于这两种方法在后面向一个现有副本集中添加成员一节会有详细说明。

 

Elction

在如下几种情景发生的时候,副本集经过“选举”来决定副本集中的primary节点:

  • 当第一次初始化一个副本集的时候;
  • primary几点steps down的时候,出现这种状况多是由于执行了replSetStepDown命令,或者是集群中出现了更适合当primary的节点,好比当primary节点和集群中其余大部分节点没法通讯的时候,当primary steps down的时候,它会关闭全部客户端的链接。
  • 当集群中一个secondary节点没法和primary节点创建链接的时候也会致使一次election发生。
  • 一次failover。
  • 执行rs.conf()命令。

 

在一次选举中包括hidden节点、仲裁者甚至正处于recovering状态的节点都具备“投票权”。默认配置中全部参与选举的节点具备相等的权利,固然在一些特定状况下,应明确的指定某些secondary会优先成为primary,好比一个远在千里以外异地机房的节点就不该该成为primary节点,选举的权重经过设置priority来调节,默认该值都是1,在前面简单副本集的搭建中已经介绍过了如何修改该值。

集群中任何一个节点均可以否决选举,即便它是non-voting member:

  • 若是发起选举的节点不具备选举权(priority为0的成员);
  • 发起选举的节点数据落后太多;
  • 发起选举的节点的priority值比集群中其余某一个节点的小;
  • 若是当前的primary节点比发起选举的节点拥有更新或同等新的数据(也就“optime”值相等或更大)。
  • 当前的primary节点会否决,若是它拥有比发起选举的节点更新或相同新的数据。

首先获取最多选票的成员(实际上要超过半数)才会成为primary节点,这也说明了为何当有两个节点的集群中primary节点宕机后,剩下的只能成为secondary,当primary宕掉,此时副本集只剩下一个secondary,它只有1票,不超过总节点数的半数,它不会选举本身为primary。

 

要想更详细的了解选举细节,参看这篇源码分析的文章:http://nosql-db.com/topic/514e6d9505c3fa4d47017da6

相关文章
相关标签/搜索