• 1. Mongodb 实践总结2012年3月
  • 2. 目录 NoSQL数据库介绍 MongoDB 简介 数据文件及内存 索引最佳实践 备份及恢复 Sharding机制 监控及诊断 测试及总结
  • 3. NoSQL数据库介绍随着互联网WEB2.0网站的兴起,传统关系型数据库力不从心 数据库高并发读写的需求 数据库并发负载非常高,往往每秒数万次读写请求,磁盘IO瓶颈 海量数据的高效率访问的需求 对数亿甚至数十亿的记录高效查询 高可扩展性和高可用性的需求 7*24小时高可用,Failover,易扩展
  • 4. NoSQL数据库介绍对于互联网海量数据来说,许多SQL特性无用武之地 数据的一致性需求 很多场景对读一致性要求较低,事务系统成为高负载下的负担 数据的读写绝对实时性需求 Feed/微博系统,发送一条微博并不需要马上被粉丝读到 复杂SQL查询,表关联需求 多数业务只是单表查询或主键关联,SQL Parser大量消耗CPU资源
  • 5. NoSQL数据库介绍传统SQL数据库时代对大数据的处理 单表单库时代:用户不停的增长、数据量增大导致压力过大 Replication及主从分离 分表分库时代:按业务key分片到不同的库,通常按取模算法 增加维护成本,不停的重复劳动 没有完美的Sharding Framework(Hivedb,限制Order/Join)
  • 6. NoSQL数据库介绍 需要的存储:高性能、分布式、易扩展 Nosql = Not Only SQL 根据海量数据特点补充关系型数据库的不足
  • 7. NoSQL数据库介绍类型部分代表特点key-value存储Dynamo Tokyo Cabinet Berkeley DB Voldemort MemcacheDB Redis LevelDB可以通过key快速查询到其value/list。value是最灵活及易扩展的数据结构,一般来说,存储不管value的格式,照单全收。灵活性和复杂性是双刃剑:一方面可以任意组织文档的结构,另一方面查询需求会变得比较复杂列存储BigTable Hbase Cassandra Hypertable鉴自Google 的BigTable,按列存储。方便存储结构化和半结构化数据,方便数据压缩,对针对某一列或者某几列的查询有非常大的IO优势文档存储MongoDB CouchDB Riak一般用类json的格式存储,存储的内容是文档型的。这样也就有有机会对某些字段建立索引,而且可以实现关系数据库的某些复杂查询功能图存储Neo4j FlockDB HyperGraphDB数据并非对等的,关系型的存储或者键值对的存储,可能都不是最好的存储方式,图存储是图形关系的最佳存储
  • 8. NoSQL数据库介绍CAP理论介绍 C:Consistency一致性,任何一个读操作总是能读取到之前完成的写操作结果 A:Availability可用性,每一个操作总是能够在确定的时间内返回 P:Tolerance of network Partition分区容忍性,在出现网络分布的情况下,仍然能够满足一致性和可用性 最多只能同时满足以上两种需求 ConsistencyAvailabilityTolerance to Network Partitions
  • 9. NoSQL数据库介绍 I/O的五分钟法则 Jim Gray 与 Gianfranco Putzolu 发表了“五分钟法则”,如果一条记录频繁被访问,就应该放到内存里,否则的话就应该待在硬盘上按需要再访问,这个临界点就是五分钟 memory is the new disk,and disk is the new tape.—Jim Gray 对于随机访问,硬盘慢得不可忍受;但如果你把硬盘当成磁带来用,它吞吐连续数据的速率令人震惊;它天生适合用来给以RAM为主的应用做日志 结论:把随机读写交给RAM,磁盘尽量用作顺序读写及持久化
  • 10. NoSQL数据库介绍Consistent Hashing 传统分布式架构: Hash() mod n or Linear hashing 问题,增删节点或节点失效会引起所有数据重新分配 一致性哈希架构: 每台Server分成v个虚拟节点,把n台机器的所有虚拟节点哈希映射到一致性哈希环上,增加数据按顺时针寻找第一个vnode节点存储。节点增删只影响两个节点之间的数据重新分配
  • 11. NoSQL数据库介绍Quorum NRW机制 N: 复制的节点数量 R: 成功读操作的最小节点数 W: 成功写操作的最小节点数 通过,W+R>N 保证强一致性 场景举例: W = 1,R = N,对写操作要求高性能高可用 R = 1,W = N,对读操作要求高性能高可用 W = Q,R = Q where Q = N/2+1 读写之间取得平衡
  • 12. NoSQL数据库介绍Vector clock 一个(node, counter)的列表,记录数据历史版本 冲突解决:业务逻辑或时间戳
  • 13. NoSQL数据库介绍节点间的通信 传统解决方案:heart-beat,ping Gossip协议,类P2P病毒式传播,去中心化 通过gossip,每个节点都能知道集群中包含哪些节点,以及这些节点的状态,使得集群中的任何一个节点都可以完成任意key的路由,任意一个节点不可用都不会造成灾难性的后果
  • 14. MongoDB 简介MongoDB介绍 一种可扩展的高性能的开源的面向文档的数据库,C++开发 10gen公司支持与推广,名字从humongous中截取,野心不言而明 文档完善,官方文档覆盖了所有问题 一直维持着较快的版本更新速度 对社区关注度高,积极推动社区活动 红杉等著名投资机构共融资3000万美金,后续升级及服务有保障
  • 15. MongoDB 简介MongoDB主要特性 ReplicaSet Automatic failover 轻松增删数据结点 Sharding 自动分片 自动数据迁移 mongos自动路由 数据管理及监控 对单个文档的原子更新 对任意属性可建立索引 数据监控命令工具丰富 查询支持 丰富的查询语法 查询优化及监控机制完善
  • 16. MongoDB 简介术语对照RDBMSMongoDBTableCollectionRow/RecordDocumentColumn nameField nameIndexIndexJoinEmbedding & LinkdingPartitionShardPartition KeySharding Key
  • 17. MongoDB 简介BSON及数据类型 Binary JSON是一种类JSON二进制形式的存储格式,支持内嵌的文档对象和数组对象 基本JSON数据类型:string, integer, boolean, double, null, array 和object 包括JSON没有的数据类型: timestamp, object id, binary data, regular expression 和code
  • 18. MongoDB 简介ObjectId详解 被设计为跨机器分布式环境中全局唯一的类型,长度12个字节 0-3这4个字节是时间戳(timestamp) 4-6这3个字节是机器码(machine) 7-8两个字节是进程id(pid) 9-11是程序自增id或随机码(inc/random) Note1:查询时db.collection.find({_id:”xx”})查不到结果,正确写法是db.collection.find({_id:new ObjectId(“xx”)}) Note2:转成字符串,需要24字节(每字节转成2字节的16进制表示) Note3:_id保证collection唯一,可以是任意简单类型,不能是array/list,插入或保存为空时则自动生成
  • 19. MongoDB 简介MongoDB插入操作: var data={'name':'lisn', 'mobile':13520663641, 'email':'lsn1996@163.com'} use test db.user.insert(data) db.user.save(data) 两种操作区别:insert操作如果主键(_id)存在则不做任何处理,save操作如果主键(_id)存在则进行记录更新
  • 20. MongoDB 简介MongoDB更新操作: 更新语法:db.collection.update( criteria, objNew, upsert, multi ) criteria:update的查询条件,类似sql update查询内where后面的条件 objNew:update的对象和一些更新的操作符(如$inc,$set...)等,类似sql update查询内set后面的条件 upsert: 这个参数的意思是,如果不存在update的记录,是否插入,true为插入,默认是false,不插入 multi: mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新 Update modifier: $inc $push $pushAll $pull $pullAll $addToSet $set $unset…都是原子操作,可以混搭
  • 21. MongoDB 简介MongoDB查询操作: 查询实例: db.user.find({'name':'lisn'}) db.user.find({'mobile':{$gte:13500000000} }).limit(10) db.user.find({}).sort({'mobile':1}) db.user.find ({'name':'lisn'},{'mobile':1}) db.user.find().skip(10) db.user.count() Select modifier: $gt $lte $all $exists $mod $ne $in $nin $or $nor $size $where $type … 正则表达式: db.user.find({‘name’:/l.*n$/i}),查询以l开头以n结尾的记录 Distinct: 返回指定字段的value列表,db.user.distinct('name')
  • 22. MongoDB 简介Cursor: 返回结果集的都隐式创建游标,服务端会清理关闭cursor var cursor = db.user.find(); while (cursor.hasNext()) { var obj = cursor.next(); print(tojson(obj) ) } 查询一致性:通过对cursor进行snapshot()操作来保证一致性读,此时再进行插入或删除符合条件的记录时,结果集将不再变化,mongodb对小于1M的结果集自动进行snapshot
  • 23. MongoDB 简介Capped collection: 性能出色的有着固定大小的集合,以FIFO规则和插入顺序进行 age-out处理 可以插入及更新,不允许删除,可用drop()删除集合中数据 创建时需预先指定大小,默认不对_id 创建索引 db.createCollection("cappedcoll",{size:10000000,autoIndexId:false}) 普通集合转化:db.runCommand({"convertToCapped":“user",size:10000}) Tailable Cursor作用于capped collection,操作完成后可不关闭,阻塞等待新的集合元素,常用于实现消息队列或数据推送功能
  • 24. 数据文件及内存 MongoDB每个数据库都有自己的独立文件,如果开启 directoryperdb 选项,则每个库的文件会单独放在一个文件夹里 每个库由一个名字空间文件和多个数据文件组成,名字空间文件以.ns结尾,数据文件按照数据量大小由0开始增长,第一个数据文件为64M,翻倍增长直至2G .ns文件保存数据库所有名字空间,每一个集合、索引都将占用一个命名空间以保存元数据。默认16M最大为2G,启动时可通过nssize参数选项更改ns文件大小以增加可创建集合及索引个数 使用预分配数据文件方式来保证写入性能的稳定(可用–noprealloc关闭)。预分配在后台进行,并且每个预分配的文件都用0进行填充 数据库启动后会产生一个6bytes的mongd.lock数据文件,用于防止同一个实例多次运行,非正常退出后需要删除此文件才能重启成功
  • 25. 数据文件及内存 名字空间文件保存的是一个hash table,保存了每个名字空间的信息,其内容为存储信息元数据,包括其大小,块数,第一块位置,最后一块位置,被删除的块的链表以及索引信息 每个数据文件被分成多个数据块(Extent),用双向链表连接。头部包含了块的元数据:自己的位置,上一个和下一个块的位置以及块中第一条和最后一条记录的位置指针。剩下部分存储具体的数据记录,也是通过双向链接来进行连接 每条记录中头部包含记录元数据,如:头部长度,块中的位置偏移,上一条和下一条记录的位置。剩余是具体数据内容,如果padding > 1则实际长度大于数据内容本身长度
  • 26. 数据文件及内存 内存管理采用OS的MMAP内存映射机制,把数据文件映射到内存中,读操作可以起到缓存作用,写操作可由内存转化成顺序磁盘写入 操作系统虚拟内存管理器会托管所有磁盘操作,这样MongoDB内存管理实现会很简单,缺点是人工没有办法控制占多大内存 32位主机最大寻址4GB,1GB用于kernel,约0.5GB用于MongoDB Stack空间,剩下约2.5G可用于映射数据文件,超出报”mmap failed with out of memory” 64位主机最大可映射128TB数据
  • 27. 数据文件及内存 可通过mongo命令行来监控MongoDB的内存使用情况: db.serverStatus().mem { "resident" : 22346, "virtual" : 1938524, "mapped" : 962283 } 相关字段含义: mapped:映射到内存的数据大小 virtual: 占用的虚拟内存大小 resident:占用物理内存大小
  • 28. 索引最佳实践MongoDB索引简介: MongoDB的索引跟传统数据库的索引相似,一般的如果在传统数据库中需要建立索引的字段,在MongoDB中也可以建立索引。 MongoDB中_id字段默认已经建立了索引,并且不可删除,Capped Collections例外 单个collection 最多有64个index,单个query只会选择1个index Key大小超过800字节不能被索引,会提示警告 索引数据结构采用B树,每个节点数据由8k大小的Bucket承载。Bucket中的每个key由两部分组成,一部分是排好序的Key Nodes,其中保存子节点及记录值的disk location,另一部分是索引值本身Key Object,两部分通过每个Key Node中 的offset字段相关联
  • 29. 索引最佳实践B树索引示例
  • 30. 索引最佳实践B树索引结构
  • 31. 索引最佳实践B树索引结构
  • 32. 索引最佳实践普通索引: db.user.ensureIndex({mobile:1}) 其中1表示升序,-1表示降序 db.user.getIndexes() [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.user", "name" : "_id_" }, { "v" : 1, "key" : { "mobile" : 1 }, "ns" : "test.user", "name" : "mobile_1" } ]
  • 33. 索引最佳实践唯一索引: 创建命令:db.users.ensureIndex({name: 1}, {unique: true}) 创建唯一索引后如果插入一条name已经存在的数据会报duplicate key error index错误 添加dropDups选项可强制将重复的项删掉:db.users.ensureIndex({name: 1}, {unique: true, dropDups: true})
  • 34. 索引最佳实践复合索引: MongoDB可对多个字段建立复合索引,字段后面的1表示升序,-1表示降序,是用1还是用-1主要是跟排序的时候或指定范围内查询的时候有关的 示例: db.user.ensureIndex({name:1,mobile:-1}) 利用复合索引的多值性,可以通过以索引字段开头的组合条件进行查询,例如:以a,b,c三个字段建立索引,则查询可利用索引的查询条件为:a或者a,b或者a,b,c
  • 35. 索引最佳实践多值索引: 可对array或文档建立索引,生成索引时会分别生成多个索引元素 多值索引示例: db.data.insert({name:“lisn",address:{city: “beijing", state: “haidian" } } ); db.data.ensureIndex({address:1}); db.data.find({address:{city: “beijing", state: “haidian"}}); db.data.find({address:{$gte:{city:“beijing“}}}); db.data.find({address:{state:“haidian”,city:“beijing”}});无法利用索引
  • 36. 索引最佳实践稀疏索引(1.8+): Sparse index解决索引文件过大的问题,普通索引会对所有记录建立索引,由于MongoDB的Schema free的特性,每个Collection中的记录属性可能不一致,sparse index只索引包含指定属性的记录,不会对该项值为空的记录进行索引,某些情况下会大大减少索引大小 示例: db.user.ensureIndex({name:1},{sparse:true}) 限制:目前只能包含一个属性
  • 37. 索引最佳实践地理位置索引: 此索引是MongoDB的一大亮点,也是foursquare选择它的原因之一。原理是建立索引时根据坐标对平面进行多次geohash,近似查询时就可以利用相同前缀的geohash值,mongoDB默认进行26次geohash,hash次数可控 示例: db.map.ensureIndex({point : “2d”}) db.map.find( {point:[50,50]} ) db.map.find( {point:{$near:[50,50]}}) db.map.find( {point:{$within:{$box:[[40,40],[60,60]]}}} )
  • 38. 索引最佳实践索引管理: 可通过往system.indexes中插入记录来创建索引,如 var spec = {ns: “test.user", key: {‘name’: 1}, name: ‘name_index’} db.system.indexes.insert(spec, true) 删除索引命令: db.user.dropIndexes()删除该集合所有索引 db.user.dropIndexes({name:1}) db.runCommand({dropIndexes:'user', index : {name:1}}) db.runCommand({dropIndexes:'user', index :'*'}) 索引重建:db.user.reIndex()
  • 39. 索引最佳实践利用查询计划进行分析: 使用命令:db.collection.find(query).explain(); { "cursor" : "BasicCursor", "indexBounds" : [ ],     "nscanned" : 40,     "nscannedObjects" : 40,     "n" : 40,     "millis" : 0,        …. } 简单说明 cursor: 返回游标类型(BasicCursor 或 BtreeCursor) nscanned: 被扫描的文档数量,如果过高可能无索引利用 n: 返回的文档数量 millis: 耗时(毫秒) indexBounds: 所使用的索引
  • 40. 索引最佳实践优化及注意事项: Skip函数无法利用索引,对跳过的数据也会进行扫描,如果对页面个数无严格大小要求,可考虑记录时间戳状态进行分页查询 db.user.totalIndexSize()查询索引大小, Memory > Index + hot data 是高性能的保障 创建索引会添加全局写锁,如果数据比较大,会挂起读写数据库的操作。可添加background选项,db.user.ensureIndex({name:1}, {background: true}) 不同的数据类型对索引大小影响很大,开发前对字段数据类型的设计很重要
  • 41. 备份及恢复MongoDB 可靠性日志 Journal:通过journal日志保证单机的可靠性,启动时可通过选项—journal决定是否开启,会预分配日志空间。journal采用group commit机制进行提交,避免频繁的IO操作,默认100ms,可调整 oplog:多机replication机制通过oplog来实现,primary/master向oplog写操作记录,通过集群复制oplog并replay来达到replication目的。oplog是capped collection,所以老的日志会被overwrite,如果secondary/slave落后主节点数据量超过oplog大小,会被认为是stale node,将进行全部primary sync操作,所以需要根据网络状况预先设置好oplogSize
  • 42. 备份及恢复MongoDB Replication机制 两种机制:Master-Slave or Replica Set(RS)
  • 43. 备份及恢复Replica Set: 以master-slave复制模式为基础,增加了auto-failover和auto-recovery的功能 理论上需要三个实例,最小集为2个mongod+1个arbiter 当主节点fail后,集群自动选举新的primary 客户端自动切换新的primary节点进行读写操作 实现读写分离功能,只有primary可写,其余secondary可读 可以设置被动节点(passive members),该节点永远不会成为primary 可以设置延迟节点(delayed nodes),保持与primary一段时间的数据延迟 可以设置隐藏节点(hidden nodes),此节点不提供线上服务
  • 44. 备份及恢复Replica Set配置: 使用--replSet 参数启动mongod服务,此时log会打印告警信息提示没有初始化集群配置,system.replset config from self or any seed (EMPTYCONFIG) 在集群中任意节点运行replSetInitiate进行初始化 rs.initiate({ _id : "set", members : [ {_id : 0, host : "172.21.1.197:27018",priority:0}, {_id : 0, host : "172.21.1.197:27017"}, {_id : 1, host : "172.21.1.198:27018"}, {_id : 2, host : "172.21.1.200:27018",arbiterOnly: true} ]}); rs.initiate(config); rs.status()查看集群当前状态,rs.config()查看集群配置信息
  • 45. 备份及恢复Replica Set配置: 进入primary节点才能进行集群配置修改 增加新成员:rs.add(“172.21.1.198:27017”),启动后节点会自动全量同步数据并成为secondary 删除成员: config = rs.config(); config.members = [config.members[1], config.members[3]]; rs.reconfig(config, {force : true})
  • 46. 备份及恢复Replica Set启动配置项详解: 命令默认描述arbiterOnlyfalse如果为true,则该节点只负责仲裁,无数据存储buildIndexestrue如果为false,secondary不会被建立索引hiddenfalse如果为true,不会对客户端暴露此节点,通常用于数据备份或数据挖掘priority1.0权重,0则永远不会成为主节点slaveDelay0延迟同步于主节点选项,以秒为单位,如果设置则priority只能为0votes1选举时的投票成员数,通常不变initialSync {}新节点加入时设置同步规则,如指定从哪台主机同步,指定何时开始同步
  • 47. 备份及恢复Replica Set选举机制 各节点每两秒向其他节点发送心跳,每个节点收到心跳后会更新自己的状态映射表,包括:是否有新节点加入,是否有老节点宕机 如果primary节点状态映射表发生变化则会通过心跳判断自己是否能和其他大多数节点(一半以上的节点)进行通信,如果不能则自动降级为secondary 如果secondary节点状态表发生变化则会检测自己是否需要成为primary,它会进行三项检测,如果任意一个是否定的则不会成为primary 是否集群中有其它节点认为自己是 primary 自己是否已经是primary 自己是否有资格成为 primary 其他节点收到想要成为primary节点的信息包后会进行三项检测,如果任意一个满足都会投否定票 他们是否已经知识集群中有一个 primary了 他们自己的数据是否比发送节点更新 是否有其它节点的数据比发送节点更新 如果确认后则节点宣布自己为primary并向其他节点发布,其余节点进行上面的最终一致性确认 如果第一阶段有赞成有反对则赞成成员30秒内不得投票,发起者不会为primary
  • 48. 备份及恢复MongoDB oplog说明: oplog 在replica set中存在local.oplog.rs中,是一个capped collection 可以通过--oplogSize设置大小,默认为剩余磁盘空间的5% oplog无索引,可通过db.oplog.rs.find().sort({$natural:-1})查看 { "ts" : { "t" : 1330679993000, "i" : 1 }, "h" : NumberLong("-1658698961994549576"), "op" : "i", "ns" : "db0105.info", "o" : { "_id" : ObjectId("4f509190c568791446000000"), "mobile" : 13436105336, "jing" : "006394888", "wei" : "001777359", "direction" : "000", "speed" : "0000", "date" : "110105155959", "distance" : "0044382941", "systime" : 20110105000000, "acc" : "1", "gps" : "0", "incredis" : "0000000000", "increstat" : "1", "locstat" : "2", "terminal" : "0100000000000000" } } oplog是侵入的,甚至当目前操作时间戳大于要replay的oplog最大时间戳也不会对数据库有任何影响
  • 49. 备份及恢复MongoDB oplog详解: 由五部分组成:{ts:{},h{}, op:{},ns:{},o:{},o2:{} } ts:8字节的时间戳,由4字节unix timestamp + 4字节自增计数表示 h: 一个哈希值(2.0+),确保oplog的连续性 op:1字节的操作类型 ns:操作所在的namespace。 o:操作所对应的document,即当前操作的内容 o2:在执行更新操作时o只记录id而o2记录具体内容,仅限于update op操作类型 “i”:insert操作 “u”:update操作 “d”:delete操作 “c”:db cmd操作 "db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.') "n":  no op,即空操作,其会定期执行以确保时效性 
  • 50. 备份及恢复MongoDB增量备份及恢复 数据量较小可用mongodump和mongorestore来完成数据库的备份和恢复 可采用备份机 + oplog replay方式实现增量备份。比如用备份机实时追踪生产机的oplog,例如一天一次,然后每周一统一进行一次oplog replay操作以此保证一个星期的容错窗口 也可以用delay node + oplog replay保持一定的容错窗口,注意delay的时间一定不能超过oplog的文件大小,不然会不起作用 Wordnik公司已经把用java开发的增量备份工具开源,使用很方便可到github下载:https://github.com/wordnik/wordnik-oss
  • 51. Sharding机制MongoDB Sharding机制介绍 提供auto-sharding机制,自动对数据进行均衡分片 可不停机轻松增加新片 结合Replica Set集群可进行auto-failover,保证高可用
  • 52. Sharding机制Sharding中的角色及架构 Sharding中数据分块称为chunk,每个chunk都是Collection中一段连续的数据记录,2.0+中默认大小为64MB Shard Server: mongod 实例,用于存储实际的数据块 Config Server: mongod 实例,存储了整个Cluster Metadata,其中包括chunk信息 Route Processes: mongos实例,前端路由,客户端由此接入,且让整个集群看上去像单一进程数据库
  • 53. Sharding机制MongoDB Sharding配置 Shard Server:启动时加入--shardsvr选项,注意默认shard server使用27018端口,可利用--port选项更改端口 Config Server:启动时加入--configsvr选项指定为config server Route  Processes:启动时加入--configdb指定多个config server,可指定chunk 块大小,用--chunkSize 选项 连接到Route进行配置: db.adminCommand({addShard:“set/172.21.1.197:27017,172.21.1.198:27017,172.21.1.200:27017”});添加要分片的集群 db.runCommand({enablesharding:“db0101”});设置允许分片的数据库 db.runCommand({shardcollection:“db0101.info”,key:{mobile:1}})设置分片的collection和sharding key,Sharding Key系统会自动建立索引
  • 54. Sharding机制MongoDB Sharding管理 查看分片集合信息db.collections.find() 查看所有存在的分片db.runCommand({listshards:1}) 查看哪些数据库被分片 config = db.getSisterDB("config") config.system.namespaces.find() 查看分片的详细信息 db.printShardingStatus(); printShardingStatus(db.getSisterDB("config"),1);
  • 55. Sharding机制Sharding平衡机制 MongoDB Sharding 操作有两个关键环节--split & migrate,split是轻量级操作,迁移却需要大量的服务端数据移动 两大目标,第一保持不同片间的数据平衡,第二尽量最小化不同片间交互的数据块大小 后台自动运行的任务,客户端不必关心数据如何拆分与迁移 当数据超过一个chunk时会根据sharding key被split到两个新的chunk中,当两个分片的数据量达到一定差异,平衡器开始工作 数据迁移的基本单元是chunk,目前版本默认值为64M,可调整大小 迁移时先把要迁移的数据从磁盘读到内存,再进行迁移,所以会引起热数据被剔除,形成swap颠簸 Sharding前选取良好的Sharding Key非常重要
  • 56. Sharding机制Sharding Key选取原则 避免小基数热点片键,如{server:1},如果server压力是不均衡的,一个大数据量server所有数据会集中在同一chunk中,这样导致chunk不易拆分,当chunk满时会出现整块迁移,{server:1,time:1}是优质方案 写可扩展性,如果用{time:1}作为片键,导致所有的写操作都会落在最新的一个Chunk上去,这样就形成了一个热点区域选择{server:1,time:1}来作为片键,那么每一个Server上的数据将会写在不同的地方 避免随机片键,此方案chunk迁移时的数据有可能是冷数据,不在内存中,会不断进行磁盘交换,严重影响性能。此方案查询也会出现随机性,结果合并会增加mongos负担 查询隔离,理想的情况是一个查询直接路由到一个实例上去,并且这个实例拥有该次查询的全部数据,如果分散到各个片查询并汇总,会增加mongos主机负载、响应时间与网络成本 由于Sharding key必须建立索引,有排序查询时以Sharding Key的最左边的字段为依据最佳 选择片键时尽量能保持良好的数据局部性而又不会导致过度热点的出现,组合片键是一种比较常用的做法
  • 57. Sharding机制Manual sharding 手动split操作,MongoDB平衡机制用同样方式对待split操作,无论手工还是自动 数据存在切分: db.runCommand({split:"test.foo",find:{_id:99}}) 预先切分: db.runCommand({split:"test.foo",middle:{_id:99}}) 编程切分: for ( var x=97; x<97+26; x++ ) { for( var y=97; y<97+26; y+=6 ) { var prefix = String.fromCharCode(x) + String.fromCharCode(y); db.runCommand({ split :”test.user”, middle : {email : prefix } } ); } } 手动move chunk db.adminCommand({moveChunk:"test.user",find:{id:{$gt:13520663641}},to:"shard0001"}) 关闭平衡器: db.settings.update({_id:"balancer"},{$set:{stopped:true}},true) 定义平衡器窗口: db.settings.update({_id:"balancer"},{$set:{activeWindow:{start:"21:00",stop:"9:00"}}},true)
  • 58. 监控及诊断Database Profiler: Profiling 级别:0-不开启;1-记录慢命令(默认>100ms);2-记录所有命令 设置:db.setProfilingLevel(1,200) 查看:db.getProfilingStatus() { "was" : 1, "slowms" : 200 } 查询慢命令:db.system.profile.find()   { "ts" : ISODate("2012-03-06T02:43:15.992Z"), "op" : "query", "ns" : "db0105.system.profile", "query" : { }, "nscanned" : 3, "nreturned" : 3, "responseLength" : 515, "millis" : 0, "client" : "127.0.0.1", "user" : "“ } db.system.profile.find( { millis : { $gt : 50 } } ) 查看大于50ms的命令 db.system.profile.find().sort({$natural:-1}) 查看最新的profile命令 
  • 59. 监控及诊断Profiling部分内容介绍: ts:该命令在何时执行 millis:该命令执行耗时,以毫秒记 op:本命令的操作信息 query:具体的查询条件 nscanned:本次查询扫描的记录数 nreturned:本次查询实际返回的结果集 responseLength:返回结果大小,以字节记 update:表明这是一个update更新操作 upsert:表明update的upsert参数为true moved:表明本次update是否进行数据迁移 insert:这是一个insert插入操作 getmore:通常发生结果集比较大的查询时,第一个query返回了部分结果,后续的结果是通过getmore来获取的
  • 60. 监控及诊断Mongostat查看实例统计信息
  • 61. 监控及诊断Mongostat信息说明: insert: 每秒插入量 query: 每秒查询量 update: 每秒更新量 delete: 每秒删除量 getmore :每秒的getmore次数 command:每秒命令执行次数 flushes:每秒执行fsync系统调用将数据写入硬盘的次数 mapped:映射总量大小,单位是MB visze:虚拟内存总量大小,单位是MB res:物理内存总量大小,单位是MB faults:每秒的缺页数 (linux only),不要超过100,否则会频繁swap写入 locked:被锁的时间百分比,尽量控制在50%以下吧20以下为佳 idx miss:B树索引缺失率 q t|r|w:全局锁等待队列长度(total|read|write)高并发时队列值会升高 conn:当前连接打开数
  • 62. 监控及诊断其他常用监控工具 db.serverStatus()数据库实例各项详细信息 db.stats()数据文件大小及索引大小等信息 iostat/vmstat MMS/Munion/Nagios…
  • 63. 测试及总结Replica Set插入测试 172.21.1.197和172.21.1.198分别部署mongodb存储实例,172.21.1.200部署仲裁实例和测试脚本 无索引时,写入速度维持平均约12000条/秒,34分钟灌完,2500万条数据,继续写入数据速度下降,当内存用满后速度降低至一万以下 提前声明一个复合索引,在内存吃满的情况下继续灌数据,刚开始速度维持在8000~9000,随着数据量增大,递减到6000左右
  • 64. 测试及总结Replica Set 查询测试 单进程,17000次/秒 20个并发,27000次/秒,机器负载4左右,73完成20个100000次查询 50个并发,28900次/秒,机器负载4左右,35秒完成50个20000次查询 200个并发,27900次/秒,机器负载6左右,70秒完成200个10000次查询 500个并发,开始出错,同样处理速度27500次/秒,机器负载6~7
  • 65. 测试及总结Sharding 测试: 以mobile为Sharding key,网络环境不稳定,两个片有Replication资源竞争,插入数据很不稳定,2500W数据用时约1.5个小时,写入速度平均3500条/秒 Shard1:set1/172.21.1.197:27017,172.21.1.198:27017 Shard2:set2/172.21.1.198:27018,172.21.1.197:27018 以mobile为Sharding key,网络环境不稳定,非RS环境,2500W数据用时33分钟,与单机灌入速度基本一致 Shard1:172.21.1.197:27018 Shard2:172.21.1.198:27018
  • 66. 测试及总结关于连接: Linux默认打开文件数为1024个,为了保证大并发有连接可用建议修改该配置 /etc/security/limits.conf中增加nofile配置 * soft nofile 10240 * hard nofile 10240 /etc/pam.d/login中增加配置 session required /lib/security/pam_limits.so Linux默认为每个进程分配Stack空间为10M(ulimit -a | grep stack),由于每个连接都会占用一个stack空间,如果连接过多则会消耗大量内存资源,据测试MongoDB最大连接为2000左右,最新版会进行连接优化或手工设置到1M (ulimit -s 1024),同时建议使用连接池使连接不超过1000
  • 67. 测试及总结关于Sharding 若非必须,可考虑多库 & 多RS & no-sharding方案,有以下好处 不必担心Route和Config的可靠性问题 冷热数据分库,部署不同的ReplicaSet Replica Set粒度可控,扩展性可控 从现有非shard环境迁移是非常痛苦的事情 若考虑sharding部署一定要认真设计好sharding key,更改很难
  • 68. 测试及总结关于NUMA问题 NUMA是多核CPU架构中的一种,简单来说就是在多核心CPU中,机器的物理内存是分配给各个核的 四种访问控制策略 缺省(default):总是在本地节点分配(分配在当前进程运行的节点上) 绑定(bind):强制分配到指定节点上 交叉(interleave):在所有节点或者指定的节点上交织分配 优先(preferred):在指定节点上分配,失败则在其他节点上分配 采用默认策略导致核心只能从指定内存块节点上分配内存,即使总内存还有富余,也会由于当前节点内存不足时产生大量的swap操作 可在启动前加numactl --interleave=all解决问题
  • 69. 测试及总结关于文件系统: 推荐XFS或EXT4,不建议用EXT3,XFS优势 可用空间比高,大文件性能佳,恢复速度快 xfs_growfs 动态扩展空间方便至极 数据文件建议进行人工预分配,否则在大量写入时预分配文件期间造成短暂不可用,可一次性将数据文件通过脚本建立好 head -c 2146435072 /dev/zero > db0101.10 cp db0101.10 db0101.11 …
  • 70. 测试及总结关于维护: 如果不预设paddingFactor,数据更新操作会导致碎片,时刻关注碎片情况,定期reindex可减少索引碎片 Repair & Compact都能对碎片进行整理 Compact速度快可单独处理collection,但无法释放空间,实用性有限 Repair速度慢但整理彻底,paddingFactor会重置,更新速度下降 需定期对Primary进行repair,P -> stepDown -> repair -> 切回
  • 71. 测试及总结关于空间及碎片: 注意数据类型设计,使用短字段做key优于长字段,节省数据及索引空间 避免碎片的几种方法: 创建文档时设置paddingFactor > 1 字段占位,虽然是反MongDB的,但建议预先设计schema 变长字段放置最后,如String类型 BinData可用于占位,便于扩展
  • 72. 测试及总结关于FindAndModify及写锁 MongoDB采用的全局写锁一直被诟病, FindAndModify会加写锁以保证原子性 尽量保证FindAndModify小范围更新 普通更新采用Read-Update模式,提高索引命中,加热索引纪录 更新多个文档时循环并一个个更新优于批量更新,减少全局写锁伤害
  • 73. 测试及总结关于javascript及MapReduce mongodb使用javascript做shell, mongodb的db.eval可以提供给数据驱动与这种javascript shell类似的js接口 目前mongodb的js引擎使用SpiderMonkey,性能不是十分理想,目前没有改成V8的消息,所以目前数据库的执行逻辑(类似存储过程)暂时不推荐使用MongoDB的js环境实现 由于JS引擎原因,目前对于Mapreduce模式,Mongodb使用一个单独的进程来跑的,目前开发团队正在设计解决这一问题。而且目前测试效果来看对于单台机器不是很理想,执行job周期过长。
  • 74. (本页无文本内容)