Monogdb特性
- 文档模型,简单灵活、适合快速开发、迭代场景
- 复制集,保证数据高可靠、服务高可用
- 分片集群,存储容量、服务能力水平扩展
- 功能强大,位置索引、文本索引、TTL索引、GridFS、Aggregation pipeline、MapReduce
- 高性能,wiredtiger、mmapv1、inMemory
Tips
部署模式
MongoDB在用于生产环境的三种模式,master-slaves(主从模式); replica set(副本集);auto shard (分片模式)。
- 主从复制master-slaves
master-slaves 模式是MongoDB早期还不支持副本集时使用,目前已经基本上废弃,官方已经不推荐使用。
master-slaves与replica set的不同点:
主从复制中只有一个Master,当Master节点挂掉之后,所有的Slave节点无法自动变为Master,继续提供服务。这时所有的 Slave也就会因连不上Master而无法继续备份数据,直到它再次连到Master。
而副本集中有一套自己的选举机制,所有的节点都可以成为“活跃”节点,一旦当前的活跃节点宕机,集群会立即从其它完好的备份节点中推选出一个节点作为“活跃节点”,整个副本集群不会停止备份服务。
- 副本集(复制集)replica set
1.1MongoDB的建议最小部署是3个数据节点构成的副本集。副本集可以提供以下优点:
- 系统99.999% 高可用
- 自动故障切换
- 数据冗余
- 容灾部署
- 读写分离
问题1 思考:为什么推荐最小部署3个节点?1主2从和1主1从1仲裁的选择?
1.2 设计副本集
副本集的重要概念“大多数”:选择主节点时需要由大多数决定,主节点只有在得到大多数支持时才能继续作为主节点,写操作被复制到大多数成员时这个写操作就是安全的。这里的大多数被定义为“副本中一半以上的成员”。
怎样才算大多数
副本集中的成员总数 | 副本集中的大多数 |
1 | 1 |
2 | 2 |
3 | 2 |
4 | 3 |
5 | 3 |
6 | 4 |
7 | 4 |
假设有一个包含5个成员的副本集,其中3个成员不可用,仍然有2个可以正常工作,那么剩余的2个成员已经无法达到副本集“大多数”的要求,所以它们无法选举主节点。如果这两个成员中有一个是主节点,当它注意到无法得到“大多数”成员支持时,就会从主节点上退位。
问题2思考:为什么剩余的2个成员不能选举出主节点?原因:避免出现多个主节点。
到此处,对于问题1前半部分的回答:
如果设置是使用2个成员的副本集:一个主节点和一个备份节点,假设其中一个成员不可用,另一个成员就看不到它了,此时网络任何一端都无法达到“大多数”的条件,所以这个副本集会退化为拥有2个备份节点(没有主节点)的副本集。因此通常不建议使用这一的配置。
问题3 思考:对于2个成员的副本集有没有别的方法来处理“大多数”的缺点?
选举机制
- 当一个备份节点无法与主节点连通时,它就会联系并请求其他的副本集成员将自己选为节点。
- 其他成员会做几项理性的检查:自身是否能够与主节点连通?发起选举希望成为主节点的备份节点的数据是否最新?是否有更高优先级的成员可以被选举为主节点?
- 如果成员发现任何原因表明当前希望成为主节点的成员不应该成为主节点,则会投否决票。而否决票拥有极高权重,等价于一票否决权,一张否决票相当于10000张赞成票。因此即使“大多数”成员中只有一个否决了本次选举,选举就会取消。
- 如果被选举为主节点的成员能够得到副本集中“大多数”成员的投票,就会成为主节点。
- 每个成员都只能要求自己被选举为主节点,不能推荐其他成员被选举为主节点,只能为申请成为主节点。
选举仲裁者
为了解决两个成员的副本集在“大多数”要求上的缺点,同时为了节省资源,不保存第三份数据,MongoDB支持一种特殊的类型成员,称为仲裁者。
仲裁者唯一的作用就是参与选举。成员一旦以仲裁者的身份添加到副本集中,就永远只能是仲裁者:无法将仲裁者重新配置为非仲裁者,反之亦然。
最多只能使用一个仲裁者
如果副本集是偶数个节点时,发起选举时,仲裁者能投出关键一票,但是如果节点数量是奇数时,就不需要了,添加额外的仲裁者,并不能加快选举速度,也不能提供更好的数据安全性。
3个成员的副本集,需要2个成员才能组成“大多数”,而此时添加一个仲裁者,需要有3个成员才能组成“大多数”,副本集的稳定性其实是降低了:原本只需要67%的成员可用,副本集就可用;现在必须75%的成员可用,副本集才可用。
仲裁者的缺点,同时对于问题1后半部分1主2从和1主1从1仲裁的选择的回答:
1主1从1仲裁节省资源,由于仲裁机不保存数据,也不为客户端提供服务,仅用于选举用,因此1主2从比起1主1从1仲裁需要多存储一份数据副本,占用更多的硬件资源。如果应用程序的使用量小,没有强需求存储三分数据,从节省资源的角度看选择后者更优。
1主2从的副本集具有更高的稳定性, 1主1从1仲裁一旦宕机一个节点,所有的压力都会集中到主节点上,此时如果再使用主节点来初始化一个新的备份节点,需要将主节点的数据副本复制到备份节点,一旦数据量较大,将会对主节点和应用程序造成显著影响,超过100GB以上,问题将会更为严重。相反,1主2从的副本集即便其中1个节点宕机,剩下2个节点也满足“大多数”,因此确保了整个副本集中依然还有一个主节点和一个备份节点,不会影响正常运作。此时使用剩余的备份节点来初始化一个新的备份节点,不必依赖主节点。
建议:如果可能,尽可能在副本集中使用奇数个数据成员,而不要使用仲裁者。
延迟备份节点
延迟备份节点的数据会比主节点延迟指定的时间(单位是秒,默认1小时),使用slaveDelay设置,且要求成员的优先级是0(优先级为0的成员永远不能成为主节点。这样的成员被称为被动成员)。
副本集的同步
MongoDB的复制功能是使用操作日志oplog实现的,操作日志包含了主节点的每一次写操作。Oplog是主节点的local数据库中的固定结合,备份节点通过查询这个集合就可以知道需要进行复制的操作。
- 分片auto-shard
分片架构:
- mongos: 数据路由,和客户端打交道的模块。mongos本身没有任何数据,他也不知道该怎么处理这数据,需要从config server获取配置信息。
- config server:保存集群和分片的元数据,即各分片包含哪些数据的信息。鉴于它所包含的数据的极端重要性,必须启用其日志功能,并确保数据保存在非易失性驱动器上,每个配置服务器都应位于单独的物理机器上,最好是分布在不同地理位置的机器上。
启动配置服务器时,不要使用—replSet选项,配置服务器不是副本集成员,所有配置服务器都必须是可写的。一般设置3台配置服务器。
- shard:正在的数据存储位置,以块为单位存数据。添加分片有两种方法:一是通过使用的副本集来构建第一个分片。另一种方法是从零开始建立集群。
参考资料:https://www.cnblogs.com/clsn/p/8214345.html#auto_id_22
3.1不要过早考虑分片
分片可以用来扩展你系统的读写能力,但是分片也会带来不少新的挑战比如说管理上的复杂度,成本的增加,选择合适片键的挑战性等等。一般来说,你应该先穷尽了其他的性能调优的选项以后才开始考虑分片,比如说,索引优化,模式优化,代码优化,硬件资源优化,IO优化等。
3.2选择合适的分片数
使用分片的一些触发条件:
- 数据总量太大,无法在一台服务器上管理
- 并发量太高,一台服务器无法及时处理
- 磁盘IO压力太大
- 单机系统内存不够大,无法装下热数据
- 服务器网卡处理能力达到瓶颈
- 多地部署情况下希望支持本地化读写
根据分片的触发条件,按照总需求,除以每台服务器的能力来确定所需的分片数。
3.3选择合适的片键
在分片场景下, 最重要的一个考量是选择合适的片键。选择片键需要考虑到应用的读写模式。通常来说一个片键要么是对写操作优化,要么是对读操作优化。要根据哪种操作更加频繁而进行相应的权衡。
选择片键的一些参考条件:
- 片键值应该具有很高的基数,或者说,这个片键在集合内有很多不同的值,例如_id就是一个基数很高的片键因为_id值不会重复。
- 片键一般不应该是持续增长的,比如说timestamp就是个持续增长的片键。此类片键容易造成热分片现象,即新的写入集中到某一个分片上。
- 好的片键应该会让查询定向到某一个(或几个)分片上从而提高查询效率。一般来说这个意味着片键应该包括最常用查询用到的字段。
- 好的片键应该足够分散,让新的插入可以分布到多个分片上从而提高并发写入率。
- 可以使用几个字段的组合来组成片键,以达到几个不同的目的(基数,分散性,及查询定向等)。
- 索引
索引官方介绍参考:https://docs.mongodb.com/manual/indexes/
- 为每一个查询建立合适的索引。
针对于数据量较大,几十万到百万级以上文档数目的集合必须使用索引查询,否则MongoDB可能需要把所有的文档从磁盘上读到内存中,会对MongoDB服务器造成较大的压力并影响其他请求的执行。
- 创建合适的复合索引,不要依赖于交叉索引
如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和复合索引。交叉索引就是针对每个字段单独建立一个单字段索引,然后在查询执行时候使用相应的单字段索引进行索引交叉而得到查询结果。交叉索引目前触发率较低,所以如果你有一个多字段查询的时候,建议使用复合索引能够保证索引正常的使用。
- 复合索引字段顺序:精确匹配条件在前,范围条件在后(Equality First, Range After)
- 隐式索引,复合索引具有双重功能,对于不同的查询可以表现为不同的索引。如果有一个{“age”:1, “username”:1}索引,”age”字段会被自动排序,可以当做{“age”:1}索引一样使用。由此推广到尽可能多的键:如果拥有一个N个键的索引,那么同时得到了所有这N个键的前缀组成的索引。
- 尽可能地使用覆盖索引(Covered Index)
当一个索引包含用户请求的所有字段,MongoDB可以直接从索引中取的所需要的返回值,无需扫描实际文档(文档可能需要从磁盘调入到内存)
如果在一个含有数组的字段上做索引,这个索引永远也无法覆盖查询,因为数组是被保存在索引中的,即便将数组字段从需要返回的字段中剔除,这样的索引任然无法覆盖查询。
- 分片集合中,原有的唯一键可能会失效(例如id),能够生效的唯一键必须包含分片指定的片键。
- 低效率的操作符:$where, $exists, $not, $nin 。以上操作符不能使用索引,如果需要快速执行一个这类型的查询,需要找到另一个能够正常使用索引的语句,将其添加进去,这样MongoDB进行无索引匹配时,会先将结果集的文档数量减到一个比较小的水平。
- 尽可能使用$in 而不是 $or, $or 表达式执行时,MongoDB 要么扫描全集合,要么所有子句都支持索引,这时MongoDB会匹配索引,实际上$or操作是对每个子句单独执行然后将结果集合并。
https://docs.mongodb.com/manual/reference/operator/query/or/
- 索引对象和数组:嵌套文档本身可以创建索引,也可以对嵌套文档的某个字段建立索引。无法对整个数组创建索引,对数组建立索引,实际上是对数组的每个元素建立一个索引条目。而一个索引中的数组字段最多只能有一个,这是为了避免在多键索引中索引条目爆炸性增长。
对于某个索引的键,如果这个键在某个文档中是一个数组,那么这个索引就会被标记为多键索引(mutikey index)。索引一旦被标记为多键索引,就无法再变成非多键索引,只能删除数据和索引,重建索引。
在已有的文档上创建索引会比新建索引再插入文档快一点。
- 索引基数:集合中某个字段拥有的不同值的数量。通常,应该在基数比较高的键上建立索引,或者在复合索引中把基数较高的键放在索引的前面(低基数的键之前)。
- 对一个已经存在大量数据的集合新建索引是一件既费时又浪费资源的事情。默认情况下,MongoDB会尽可能块的创建索引,阻塞所有对数据库的读写请求,一直到索引创建完成。可以在创建索引时指定后台运行{background:true},但仍然会对性能有比较大的影响,对于线上环境的大数据量集合新建索引,一定要规划好执行时间区间。
- explain() 可以输出对集合操作的信息,详细使用方法参考:
http://docs.mongoing.com/reference/method/db.collection.explain.html
Verbosity Modes:
queryPlanner Model,默认的模式,运行查询优化器选择获胜的计划,执行完获胜计划后,返回执行的获胜计划的信息
executionStats Model,MongoDB 运行查询优化器选择获胜的计划,执行完获胜计划后,返回获胜计划的信息和执行获胜计划的统计信息
allPlansExecution Mode,如果查询优化器考虑的不止一个计划,返回结果除了executionStats Model 的信息外,还包含所有被拒绝的候选计划的信息。
- 查询优化器 query optimizer
基本来说,如果一个索引能够精确匹配一个查询,那么查询优化器就会使用这个索引。否则可能会有几个索引都适合该查询。MongoDB会从这些候选的索引子集中为每次查询计划选择一个,这些查询计划是并行执行的。最早返回100个结果的就是胜者,其他的查询计划就会被中止。
这个胜出的查询计划会被缓存,这个查询接下来都会使用它,直到集合数据发生比较大的变化,查询优化器就会重新挑选可行的查询计划。
建立索引时,或是每执行1000次查询之后,查询优化器都会重新评估查询计划。
- hint() 可以指定查询匹配的索引,有时候,查询优化器选中的获胜计划使用的索引并非是你预期中的索引,此时可以使用hint()来指定你想要的索引。详细使用方法参考:
http://docs.mongoing.com/reference/method/cursor.hint.html
- Index Filters 索引过滤器
索引过滤器会决定优化器评估哪一个索引作为一个 query shape(查询形状)。
一个查询形状由查询、排序以及映射说明组成。如果一个给定的查询形状存在一个过滤器,优化器将只会考虑过滤器中指定的这些索引。
MongoDB 2.6开始,当查询形状中存在一个索引过滤器时,将会忽略hint(),通过explain() 查看 indexFilterSet 可以确定是否使用了索引过滤器。
索引过滤器只会影响到优化器评估的索引,优化器有可能会仍然选择集合扫描作为某给定查询形状的优胜方案。
设置查询优化器详情参考:
http://docs.mongoing.com/reference/command/planCacheSetFilter.html
由于索引过滤器重写了优化器以及 hint() 方法预期的操作,请谨慎合理使用索引过滤器。
- TTL索引
TTL索引是一种特殊索引,通过这种索引MongoDB会过一段时间后自动移除集合中的文档。这对于某些类型的信息来说是一个很理想的特性,例如机器生成的事件数据,日志,会话信息等,这些数据都只需要在数据库中保存有限时间。
建立TTL的字段需要是日期字段,或者是包含日期值的数组。
TTL索引不能保证过期数据会被立刻删除。在文档过期和MongoDB从数据库中删除文档之间,可能会有延迟。
删除过期数据的后台任务 每隔60秒 运行一次。所以,在文档过期 之后 和 后台任务运行或者结束 之前 ,文档会依然存在于集合中。
TTL索引能够作为普通索引使用。
例如:db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
详情介绍见:http://docs.mongoing.com/core/index-ttl.html
- MongoDB 索引的实现原理,B-tree/B+tree/B*tree 的整理,参考:
https://blog.csdn.net/zwz2011303359/article/details/63262541
http://lib.csdn.net/article/mongodb/53952
https://blog.csdn.net/wwh578867817/article/details/50493940
B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;
B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;
- 集合(表)结构设计
- 不要按照关系型来设计表结构
MongoDB虽然可以像关系型数据库一样设计表结构,但它不支持外键,也不支持复杂的Join!如果你的程序发现有大量使用Join的地方,那可能需要重新设计。
- 数据库集合(collection)的数量不宜太多
MongoDB的模式设计基于灵活丰富的JSON文档模式。在多数情况下,一个MongoDB应用的数据库内的集合(表)数量应该远远小于使用关系型数据库的同类型应用。
MongoDB集合设计不遵从第三范式(非主键列必须直接依赖于主键,不能存在传递依赖),可以允许数据在多个文档中重复。
MongoDB的数据模型非常接近于对象模型,所以基本上就是按照主要的Domain Object的数量来建立相应的集合。
一般小型应用的集合数量通常只有几个,中大型的应用会在10多个或者几十个。
不建议对集合进行分表,要么使用分片,要么按租户级别分库。副本集资源不变的情况下,对集合分表并不能缓解服务器压力,在索引合理覆盖查询的情况下,查询的瓶颈主要是在内存热数据中没有需要的数据,从磁盘读取文档到内存中时的IO。
如果问题不是一套集群能解决的,请再来一套。
- 不要害怕数据冗余,MongoDB模式设计不能按照第三范式,很多时候允许数据在多个文档中重复。一般来说如果某个字段的数据值经常会变,则不太适合被大量冗余到别的文档或者别的集合里面去。
- 对于1:N(一些)的关系使用全部内嵌。对于一对多关系,如一个人有几个联系方式,一本书有几个章节,把N的数据以数组形式来描述。
- 对于1:NN(很多)的关系使用ID内嵌。
如果一对多的多端数据量较大,就采用应用ID的方式,因为MongoDB对文档大小限制为16MB。
- 对于1:NNN(很多)的关系使用对主文档的id引用,并对该字段创建索引。
- 把二进制文件和元数据分集合存放。如果你有需要把PDF文件,图片,甚至小视频等二进制文件需要管理,建议使用MongoDB 的GridFS API 或者自己手动分集合来分开管理二进制数据和元数据。
- 频繁更新的数据不要放在嵌套数组内。
- 避免使用太长的字段名。MongoDB 没有表结构定义。每个文档的结构由每个文档内部的字段决定。所有字段名会在每个文档内重复。使用太长的字段名字会导致对内存、网络带宽更多的需求。(由于压缩技术,长字段名对硬盘上的存储不会有太多占用)
- 程序配置
- 设定合适的MongoDB连接池大小。Java驱动的默认连接池大小是100。建议按照应用的实际情况做调整。对压力较小的应用可以适当调小减少对应用服务器的资源占用。
- 正确使用写关注设置(Write Concern)
WriteConcern 定义了Mongodb的安全写入级别。
MongoDB的建议最小部署是一个复制集,包含3个数据节点。默认情况下应用的写操作(更新,插入或者删除)在主节点上完成后就会立即返回。写操作则通过OPLOG方式在后台异步方式复制到其他节点。在极端情况下,这些写操作可能还未在复制到从节点的时候主节点就出现宕机。这个时候发生主备节点切换,原主节点的写操作会被回滚到文件而对应用不可见。为防止这种情况出现,MongoDB建议对重要的数据使用 {w: “marjority”} 的选项。{w: “majority”} 可以保证数据在复制到多数节点后才返回成功结果。使用该机制可以有效防止数据回滚的发生。
另外你可以使用 {j:1} (可以和 w:”majrotiy” 结合使用) 来指定数据必须在写入WAL日志之后才向应用返回成功确认。这个会导致写入性能有所下降,但是对于重要的数据可以考虑使用。
- 正确使用读选项设置(Read Preference)
- primary: 默认,在主节点上读数据
- priaryPreferred: 优先从主节点上读,如果失败再到任意一台从节点上读
- secondary: 在从节点上读数据(当有多台节点的时候,随机的使用某一台从节点)
- secondaryPreferred: 优先从节点上读,如果从节点由于某种原因不能提供服务,则从主节点上进行读。
- nearest: 从距离最近的节点来读。距离由ping操作的时间来决定。
除第一个选项之外,其他读选项都存在读到的数据不是最新的可能。原因是数据的复制是后台异步完成的。
- 不要实例化多个MongoClient。
MongoClient是个线程安全的类,自带线程池。通常在一个JVM内不要实例化多个MongoClient实例,避免连接数过多和资源的不必要浪费。
- 使用有规律的命名方式。
数据库命名建议使用小写如:school, course, stuent_record
- 正确使用更新语句。
不要把MongoDB和普通的键值型数据库(KV)视为等同。MongoDB支持和关系型数据库update语句类似的in place update。你只需要在update语句中指定需要更新的字段,而不是整个文档对象。
举例来说,把用户的名字从TJ改为Tang Jianfa.
不建议的做法:
user = db.users.findOne({_id: 101});
user.name="Tang Jianfa"
db.users.save(user);
建议的做法:
user = db.users.findOne({_id: 101});
// do certain things
db.users.update({_id:101}, {$set: {name: "Tang Jianfa"}});
- 使用投射 (projection)来减少返回的内容
MongoDB 支持类似于SQL语句里面的select,可以对返回的字段进行过滤。使用Projection可以减少返回的内容,降低网络传输的量和代码中转化成对象所需的时间。
- 存储引擎
Mongodb 3.0支持用户自定义存储引擎,用户可配置使用Mmapv1, WiredTiger 或者In-Memory Storage Engine存储引擎, 从3.2版开始,MongoDB使用WiredTiger作为默认的存储引擎。
详情见http://docs.mongoing.com/core/storage-engines.html
- WiredTiger
如果说插件式存储引擎API为MongoDB 3.0打造了一个武器库,那么WiredTiger绝对是武器库中第一枚也是最重要的一枚重磅炸弹。因为MMAP存储引擎自身的天然缺陷(耗费磁盘空间和 内存空间且难以清理,库级别锁),MongoDB为数据库运维人员带来了极大痛苦。
WiredTiger 通过MVCC实现文档级别的并发控制,即文档级别锁。
WiredTiger 支持对所有集合和索引进行Block压缩和前缀压缩(如果数据库启用了journal,journal文件一样会压缩),已支持的压 缩选项包括:不压缩、Snappy压缩和Zlib压缩。
WiredTiger在集合和索引级别分配文件,数据库中的所有集合和索引均存储在单独的 文件中,集合或者索引删除后,对应的存储文件随即删除。
WiredTiger 支持内存使用容量配置。
按照Mongodb默认的配置,WiredTiger的写操作会先写入Cache,并持久化到WAL(Write ahead log),每60s或log文件达到2GB时会做一次Checkpoint,将当前的数据持久化,产生一个新的快照。Wiredtiger连接初始化时,首先将数据恢复至最新的快照状态,然后根据WAL恢复数据,以保证存储可靠性。
- mmapv1
MongoDB 3.0出了引入WiredTiger外,对于原有的存储引擎MMAP也进行了一定的完善,该存储引擎依然是3.0版的默认存储引擎。遗憾的是改进后的 MMAP存储引擎依旧在数据库级别分配文件,数据库中的所有集合和索引都混合存储在数据库文件中,所以磁盘空间无法及时自动回收的问题如故。
锁粒度由库级别锁提升为集合级别锁。这在一定程度上也能够提升数据库的并发处理能力。
文档空间分配方式改变
在 MMAP存储引擎中,文档按照写入顺序排列存储。如果文档更新后长度变长且原有存储位置后面没有足够的空间放下增长部分的数据,那么文档就要移动到文件 中的其他位置。这种因更新导致的文档位置移动会严重降低写性能,因为一旦文档发生移动,集合中的所有索引都要同步修改文档新的存储位置。
MMAP 存储引擎为了减少这种情况的发生提供了两种文档空间分配方式:基于paddingFactor(填充因子)的自适应分配方式和基于 usePowerOf2Sizes的预分配方式,其中前者为默认方式。第一种方式会基于每个集合中文档更新历史计算文档更新的平均增长长度,然后在新文档 插入或旧文档移动时填充一部分空间,如当前集合paddingFactor的值为1.5,那么一个大小为200字节的文档插入时就会自动在文档后填充 100个字节的空间。第二种方式则不考虑更新历史,直接为文档分配2的N次方大小的存储空间,如一个大小同样为200字节的文档插入时直接分配256个字 节的空间。
MongoDB 3.0版本中的MMAPv1抛弃了基于paddingFactor的自适应分配方式,因为这种方式看起来很智能,但是因为一个集合中的文档的大小不一,所 以经过填充后的空间大小也不一样。如果集合上的更新操作很多,那么因为记录移动后导致的空闲空间会因为大小不一而难以重用。目前基于 usePowerOf2Sizes的预分配方式成为默认的文档空间分配方式,这种分配方式因为分配和回收的空间大小都是2的N次方(当大小超过2MB时则 变为2MB的倍数增长),因此更容易维护和利用。如果某个集合上只有insert或者in-place update,那么用户可以通过为该集合设置noPadding标志位,关闭空间预分配。
- In-Memory Storage Engine
详情参考:http://docs.mongoing.com/core/inmemory.html
从MongoDB企业版3.2.6开始,内存存储引擎在64位的版本中正式发布。与其它元数据和诊断数据不同内存存储引擎不维护任何磁盘中的数据,包括配置数据,索引,用户认证信息等。通过避免磁盘I/O,内存存储引擎提供了数据库操作更大的可预测延迟。
内存存储引擎是非持久化的,因此不会将数据写到一个持久化的存储中。也就是说,非持久化的数据包括应用数据以及系统数据,例如用户、权限、索引、复制集配置、分片集群配置等。
- 系统
使用SSD 或RAID10 来提高存储IOPS能力
MongoDB是一个高性能高并发的数据库,其大部分的IO操作为随机更新。一般来说本机自带的SSD是最佳的存储方案。
如果使用普通的硬盘,建议使用RAID10条带化来提高IO通道的并发能力。RAID10也被称为镜象阵列条带。像RAID0一样,数据跨磁盘抽取;像RAID1一样,每个磁盘都有一个镜象磁盘, 所以RAID 10的另一种会说法是 RAID 1+0。RAID10提供100%的数据冗余,支持更大的卷尺寸,但价格也相对较高。对大多数只要求具有冗余度而不必考虑价格的应用来说,RAID10提供最好的性能。使用RAID10,可以获得更好的可靠性,因为即使两个物理驱动器发生故障,每个阵列中都有一个,数据仍然可以得到保护。RAID10需要4 + 2*N 个磁盘驱动器(N >=0), 而且只能使用其中一半或更小的磁盘用量, 例如 4 个 250G 的硬盘使用RAID10 阵列, 实际容量是 500G。
使用XFS 文件系统
MongoDB在WiredTiger存储引擎下建议使用XFS文件系统。Ext4最为常见,但是由于ext文件系统的内部journal和WiredTiger有所冲突,所以在IO压力较大情况下表现不佳。
WiredTiger下谨慎使用超大缓存
WiredTiger 对写操作的落盘是异步发生的。默认是60秒做一次checkpoint。做checkpoint需要对内存内所有脏数据遍历以便整理然后把这些数据写入硬盘。如果缓存超大(如大于128G),那么这个checkpoint时间就需要较长时间。在checkpoint期间数据写入性能会受到影响。目前建议实际缓存设置在64GB或以下。
关闭 Transparent Huge Pages
Transparent Huge Pages (THP) 是Linux的一种内存管理优化手段,通过使用更大的内存页来减少Translation Lookaside Buffer(TLB)的额外开销。 MongoDB数据库大部分是比较分散的小量数据读写,THP对MongoDB这种工况会有负面的影响所以建议关闭。
http://docs.mongoing.com/tutorial/transparent-huge-pages.html
启用Log Rotation
防止MongoDB 的log文件无限增大,占用太多磁盘空间。好的实践是启用log rotation并及时清理历史日志文件。
http://docs.mongoing.com/tutorial/rotate-log-files.html
分配足够的Oplog空间
足够的Oplog空间可以保证有足够的时间让你从头恢复一个从节点,或者对从节点执行一些比较耗时的维护操作。假设你最长的下线维护操作需要H小时,那么你的Oplog 一般至少要保证可以保存 H 2 或者 H3 小时的oplog。如果你的MongoDB部署的时候未设置正确的Oplog 大小,可以参照下述链接来调整:
http://docs.mongoing.com/tutorial/change-oplog-size.html
关闭数据库文件的 atime
禁止系统对文件的访问时间更新会有效提高文件读取的性能。这个可以通过在 /etc/fstab 文件中增加 noatime 参数来实现。例如:
/dev/xvdb /data ext4 noatime 0 0
修改完文件后重新 mount就可以:
# mount -o remount /data
提高默认文件描述符和进程/线程数限制
Linux默认的文件描述符数和最大进程数对于MongoDB来说一般会太低。建议把这个数值设为64000。因为MongoDB服务器对每一个数据库文件以及每一个客户端连接都需要用到一个文件描述符。如果这个数字太小的话在大规模并发操作情况下可能会出错或无法响应。 你可以通过以下命令来修改这些值:
ulimit -n 64000
ulimit -u 64000
禁止 NUMA
在一个使用NUMA技术的多处理器Linux 系统上,你应该禁止NUMA的使用。MongoDB在NUMA环境下运行性能有时候会可能变慢,特别是在进程负载很高的情况下。
预读值(readahead)设置
预读值是文件操作系统的一个优化手段,大致就是在程序请求读取一个页面的时候,文件系统会同时读取下面的几个页面并返回。这原因是因为很多时候IO最费时的磁盘寻道。通过预读,系统可以提前把紧接着的数据同时返回。假设程序是在做一个连续读的操作,那么这样可以节省很多磁盘寻道时间。
MongoDB很多时候会做随机访问。对于随机访问,这个预读值应该设置的较小为好.一般来说32是一个不错的选择。
你可以使用下述命令来显示当前系统的预读值:
sudo blockdev --report
要更改预读值,可以用以下命令:
sudo blockdev --setra 32
使用NTP时间服务器
在使用MongoDB复制集或者分片集群的时候,注意一定要使用NTP时间服务器。这样可以保证MongoDB集群成员之间正确同步。
参考资料
http://www.mongoing.com/archives/3895
《Mongodb权威指南》