一、MongoDB集群介绍MongoDB是一个基于分布式文件存储的数据库,旨在为WEB应用提供可扩展的高性能数据存储解决方案。下面以3台机器介绍最常见的集群方案。详情可以查看官网https://docs.mongodb.com/v3.4...。1、集群组件介绍mongos(路由处理):作为Client和MongoDB集群的请求入口,所有的用户请求都会通过mongos进行协调,将数据请求发送到对应的Shard(mongod)服务器,然后合并数据,然后返回给用户。configserver(配置节点):即:配置服务器;主要保存数据库的元数据,包括数据分布(分片)和数据结构。mongos收到客户端的请求后,会从configserver加载配置信息,并缓存到内存中。一般在生产环境中会配置多个configserver,因为它保存的元数据极其重要,一旦损坏会影响整个集群的运行。shard(shardinstancestoresdata):shard是分片。MongoDB利用sharding机制实现数据分布式存储和处理,达到横向扩展的目的。默认情况下,数据将在分片之间自动传输以实现平衡。这个动作是通过一种叫做平衡器的机制来实现的。副本集(replicaset):副本集实现了数据库的高可用。如果不做副本集,一旦存放数据的服务器节点挂掉,数据就会丢失。相反,如果配置了副本集,相同的数据会保存在副本服务器(副本节点)中,一般的副本集包括一个主节点和多个副本节点。如果有必要,当节点挂掉时,仲裁者(仲裁节点)将被配置为投票节点。Arbiter(仲裁节点):仲裁服务器本身不包含数据。它只能检测所有副本服务器,并在主节点发生故障时选举新的主节点。这是通过主节点、副本节点和仲裁服务器之间的心跳(Heartbeat)实现的。2、MongoDB应用场景网站数据:适用于实时插入、更新和查询,具有网站实时数据存储所需的复制性和高扩展性。缓存:由于其高性能,也适合作为信息基础设施的缓存层。系统重启后,构建的持久化缓存可以防止底层数据源过载。大容量、低价值的数据:使用传统的关系数据库存储一些数据可能成本更高。在此之前,很多程序员往往会选择传统的文件进行存储。高扩展性场景:非常适合由几十台或上百台服务器组成的数据库。用于对象和JSON数据存储:MongoDB的BSON数据格式非常适合文档格式的存储和查询。3、选择MongoDB的理由MongoDB的数据为BSON数据格式,可扩展性强,易于扩展,数据的水平扩展非常简单,支持海量数据存储,性能强大。二、集群监控1、监控数据库存储统计在docker中输入mongos或shardinstance,执行如下命令:dockerexec-itmongosbash;mongo--port20001;useadmin;db.auth("root","XXX");说明:通过该命令可以查询集群成员数、索引数等相关数据。MongodbGUI可视化管理工具一共有13款,总有一款适合你db.stats();2、查看数据库的统计信息说明:通过该命令可以查看操作次数、内存占用、网络io等db.runCommand({serverStatus:1});3、查看副本集成员状态rs.status();三、基本运维操作1、设置和查看慢查询#设置慢查询db.setProfilingLevel(1,200);#查看慢查询级别db。getProfilingLevel();#查询慢查询日志,这个命令是针对某个库设置的db.system.profile.find({ns:'dbName.collectionName'}).limit(10).sort({ts:-1})。漂亮的();2、查看执行时间较长的动作db.currentOp({"active":true,"secs_running":{"$gt":2000}});3.动态调整日志级别并设置缓存大小#设置日志级别参数db.adminCommand({"getParameter":1,"logLevel":1});#设置缓存大小参数db.adminCommand({"setParameter":1,"wiredTigerEngineRuntimeConfig":"cache_size=4G"});4.添加和删除副本集成员#查看副本集成员rs.status().members;#添加成员rs.add('127.0.0.1:20001');#删除成员rs.删除('127.0.0.1:20001');5.设置数据库和集合分片#在mongos管理库中设置库允许分片sh.enableSharding("dbName");#在mongos管理库中设置collectionshardingkeysh.shardCollection("dbName.collectionName",{filedName:1});6.添加和删除分片#查看分片状态sh.status();#执行在mongos中添加分片(可以是单实例也可以是副本集)db.runCommand({removeShard:"shardName"});db.runCommand({addshard:"rs1/ip-1:20001,ip-2:20001,ip-3:20001"});#执行mongos中分片的移除db.runCommand({removeShard:"shard3"});#在mongos中执行刷新mongos配置信息db.runCommand("flushRouterConfig"));说明:删除shard的命令至少要执行两次才能成功删除,不会删除直到状态完成才被删除,否则表示无用删除成功,分片处于{"draining":true}状态。在这种状态下,不仅分片无用,还会影响后续删除其他分片,遇到这个状态,再执行removeshard即可,删除一个分片时最好重复删除命令,直到状态完成;还有一点需要注意:如果删除成功的分片要加入再次集群,数据目录一定要清理干净才可以加入集群,否则即使加入成功,也不会存储数据,也不会创建集合。另外:在删除分片的时候,整个过程中可能会出现无限的{"draining":true}状态,要等多久还是这样,而且分片上的块都没有移动到其他分片上,解决方法是:在config中找到config数据库的shardcollection中shard的信息,将draining字段由True改为False,继续Trytodeleteoperation”,上面这句话会立即返回,真正在后台执行。在数据移除的过程中,一定要注意实例的日志信息,可能会出现数据块迁移过程中找不到边界条件,导致数据迁移失败重试的情况,解决方法是删除边界数据,重启实例,如果shard是primaryshard,需要先迁移primarysharddb.runCommand({movePrimary:"XXX",to:"other"});删除后为完成后,在所有的mongos上运行如下命令,然后对外提供服务,当然你也可以重启所有的mongos实例。7、数据导入导出#导出允许指定导出条件和字段mongoexport-h127.0.0.1--port20001-uxxx-pxxx-dxxx-cmobileIndex-oXXX.txtmongoimport-h127.0.0.1--port20001-uxxx-pxxx-dxxx-cmobileIndex--fileXXX.txt四、MongoDB数据迁移1、迁移副本集中的成员,关闭mongod实例。为确保安全关机,请使用shutdown命令;将数据目录(即dbPath)转移到新机器上;在新机器上启动mongod,其中node的data目录为copy的文件目录;连接到复制集的当前主节点;如果新节点的地址发生变化,使用rs.reconfig()更新复制集的配置文档;例如,以下命令过程将更新成员中的第二个地址:cfg=rs.conf()cfg.members[2].host="127.0.0.1:27017"rs.reconfig(cfg)使用rs.conf()确认使用了新配置。等待所有成员恢复正常,使用rs.status()检测成员状态。2.迁移副本集的主节点迁移主节点时,副本集需要选举一个新的主节点。在选举期间,副本集会读写。通常,这只会持续很短的时间。但是,Masters应该以尽可能低的影响迁移。退出主服务器,以便可以开始正常的故障转移。要降级master,连接一个master,使用replSetStepDown方法或者使用rs.stepDown()方法,下面的例子使用rs.stepDown()方法降级:rs.stepDown()等主节点降级到从节点,当另一个成员成为PRIMARY后,降级的节点可以按照“迁移副本集的成员”进行迁移。可以使用rs.status()确认状态更改。3.从副本集中的其他节点恢复数据。MongoDB通过副本集可以保证数据存储的高可靠。一般生产环境推荐使用“三节点副本集”,这样即使其中一个节点挂掉无法启动,我们也可以直接清除它的数据。重启后,用一个新的Secondary节点加入副本集,或者复制其他节点的数据,重启节点,会自动同步数据,从而达到数据恢复的目的。关闭需要数据同步的节点dockerstopnode;#在docker环境下db.shutdownServer({timeoutSecs:60});#非docker环境复制目标节点机器的数据存放目录(/dbPath)到当前机器的指定目录。scptargetnodeshard/data->currentnodeshard/data当前节点以复制的数据文件启动节点将新节点添加到复制集#进入复制集的主节点,执行命令添加新节点rs.add("hostNameNew:portNew");#等待所有成员恢复正常,查看成员状态rs.status();#移除原节点rs.remove("hostNameOld>:portOld");五、MongoDB在线问题场景解决方案1、由于MongoDB新建索引导致数据库被锁定。解释:为了优化业务,在某行直接针对千万级集合执行创建新索引的命令,导致整个数据库被锁定,应用服务不可用。解决方法:找出运行进程,kill掉。改在后台新建索引,速度会很慢,但不影响业务,新建完成后索引才会生效;#查询运行时间超过200msOperationdb.currentOp({"active":true,"secs_running":{"$gt":2000}});#杀死执行时间过长的操作db.killOp(opid)#在后台创建一个新索引db.collectionNmae.ensureIndex({filedName:1},{background:true});2.MongoDB没有限制内存,导致实例退出。问题描述:生产环境某台机器启动了多个mongod实例。运行一段时间后,进程莫名kill掉;使用WiredTiger内部缓存和文件系统缓存。从3.4开始,WiredTiger内部缓存默认使用较大的缓存:50%(RAM-1GB)或256MB。例如,在总共有4GBRAM的系统上,WiredTiger缓存将使用1.5GB的RAM()。相反,一个总共有1.25GBRAM的系统将为WiredTiger缓存分配256MB,因为这是总RAM减去1GB()的一半以上。0.5*(4GB-1GB)=1.5GB0.5*(1.25GB-1GB)=128MB<256MB。如果一台机器有多个实例,操作系统会在内存不足的情况下杀死一些进程;#调整WiredTiger内部缓存大小,调整缓存大小不需要重启服务,我们可以动态调整:db.adminCommand({"setParameter":1,"wiredTigerEngineRuntimeConfig":"cache_size=xxG"})3、MongoDB删除数据不释放磁盘空间问题描述:在删除大量数据的情况下(我操作的数据量是2000万+),而在生产环境中,请求量很大。这时候机器的CPU负载会显得很高,甚至会出现机器卡死无法运行的情况。此类操作应仔细分批;执行删除命令后,发现磁盘数据量很大。没有改变。解决方案:方案一:我们可以使用MongoDB提供的在线数据收缩功能,通过Compact命令db.collectionName.runCommand("compact")进行Collection级别的数据收缩,去除collection所在的文件碎片。该命令以在线方式提供收缩,收缩会同时影响在线服务。为了解决这个问题,可以先在从节点上执行磁盘碎片整理命令。操作完成后,切换主节点,将原来的主节点变为从节点,再次执行Compact命令。方案二:使用slave节点重同步,secondary节点重同步,删除secondary节点中的指定数据,重启与primary节点的数据同步。当副本集成员数据太旧时,也可以使用重新同步。数据重新同步不同于直接复制数据文件。MongoDB只同步数据,所以再同步完成后没有数据文件的空集合,从而实现磁盘空间的回收。对于一些特殊情况,如果从节点不能下线,可以在副本集添加一个节点,然后从节点自动开始同步数据。一般来说,再同步的方法比较好。首先,它基本上不会阻塞副本集的读写。其次,消耗的时间比前两者相对要短。如果是primary节点,先强制成为secondary节点,否则跳过这一步:rs.stepdown(120);然后删除主节点上的辅助节点:rs.remove("IP:port");删除dbpath下的secondary节点,该节点的所有文件都会重新加入集群,然后让它自动同步数据:rs.add("IP:port");数据同步完成后,循环1-4的步骤可以节省集群Release4所有节点的磁盘空间。MongoDB机器负载极高问题的解释:该场景是基于一个大客户的请求。由于MongoDB部署机器包含master和slave,MongoDB使IO100%,数据库阻塞,出现大量慢查询,进而导致机器负载极高,应用服务完全不可用.解决方案:在没有及时扩容机器的情况下,首要任务是减少机器的IO。当一台机器一主一从,写入大量数据时,会互相抢占IO资源。所以此时放弃了MongoDB的高可用特性,去掉了副本集中的slave节点,保证每台机器只有一个节点可以占用磁盘资源。之后机器负载立即下降,服务正常可用。但是,MongoDB目前无法保证数据的完整性。一旦主节点挂掉,数据就会丢失。此解决方案只是临时解决方案。根本的解决办法是增加机器的内存,使用固态硬盘,或者增加碎片集来降低单机的读写压力。#进入master节点,执行删除成员的命令rs.remove("127.0.0.1:20001");#注意:不要直接关闭实例在环境中,某个集合的shardkey使用类似的方法到_id,将包含时间序列的字段作为升序的shardkey,导致数据写入一个数据块。随着数据量的增加,会发生数据迁移到之前的分区,导致系统资源的占用,偶尔会出现查询变慢的情况。解决方案:临时方案设置数据迁移的窗口,放在正常时间段内,会影响业务。根本的解决办法是更换片键。#连接到mongos实例并执行以下命令db.settings.update({_id:"balancer"},{$set:{activeWindow:{start:"23:00",stop:"4:00"}}},真的);#查看余额窗口sh.getBalancerWindow();六、MongoDB优化建议1、应用级优化查询优化:确认你的查询是否充分利用了索引,使用explain命令检查查询执行状态,并添加必要的索引,避免表扫描操作。shardingkey的合理设计:Incrementalsharding-key:适用于可以分范围的字段,比如integer、float、date类型,查询速度更快。Randomsharding-key:适用于写操作频繁的场景。这种情况下,如果在一个shard上进行,这个shard的负载会比其他shard高,不够均衡。因此,希望可以使用哈希查询键将写入分布到多个分片。执行以上,考虑组合键作为分片键,总的原则是快速查询,尽量减少跨分片查询,balancebalance数量少;单个增量shardingkey可能会导致所有写入的数据都在最后一块上,最后一块写入压力增加,数据量增加,会导致数据迁移到之前的分区。MongoDB默认单条记录16M,尤其是使用GFS时,一定要注意shrading-key的设计。会出现不合理的sharding-key,多个文档存储在一个chunk中。同时由于GFS经常存储大文件,MongoDB在做balance时不能使用sharding-key来分隔这多个文件。到不同的分片,此时MongoDB会不断报错,最终导致MongoDB宕机。解决方案:加大chunks的大小(治标),设计合理的sharding-key(治本)。通过profile监控数据:优化查看profile功能当前是否开启,使用命令db.getProfilingLevel()返回level级别,值为0|1|2,分别代表含义:0表示关闭,1表示记录慢命令,2表示全部。启用profile功能的命令是db.setProfilingLevel(level);#level级别,当取值level为1时,slow命令默认值为100ms,改为db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)改为50毫秒即可查看通过db.system.profile.find()获取当前监控日志。2、硬件层面的优化2.1确定热点数据的大小可能你的数据集很大,但这并不是那么重要。重要的是你的热点数据集有多大,你经常访问的数据有多大(包括经常访问的数据和所有索引数据)。使用MongoDB时,最好保证你的热点数据在你机器的内存大小以下,保证内存能容纳所有的热点数据;2.2选择正确的文件系统。MongoDB的数据文件是预先分配的,在Replication中,Master和ReplicaSets的非Arbiter节点会预先创建足够的空文件来存放操作日志。这些文件分配操作在某些文件系统上可能会非常慢,从而导致进程被阻塞。所以我们应该选择那些空间分配速度快的文件系统。这里的结论是尽量不要用ext3,用ext4或者xfs;3、优化架构,使主从节点尽可能分布在不同的机器上,避免与MongoDB在同一台机器上进行IO操作;7.总结MongoDB具有高性能、易扩展、易使用等特点,在正确使用的情况下,其自身的性能还是很强的,在一些关键点如shardkey的选择、内存大小和磁盘IO,往往是限制其性能的最大瓶颈。对于shardkey,在业务系统的前期,不能先对集合进行数据分片,因为shardkey一旦确定,就无法修改。后期可以根据业务系统的情况仔细筛选字段。一般不建议使用升序分片键(随着时间稳定增长的字段,自增主键为升序键),因为这样会导致局部热读热写,无法发挥分片集群的真正实力。建议使用哈希分片键或随机分布的分片键,以确保数据在分片节点之间均匀分布。对于内存,建议内存的大小包括热点数据的大小加上索引的大小,以保证内存能够容纳所有的热点数据。对于磁盘资源,MongoDB的高速读写是基于磁盘IO的。为保证其性能,建议主从节点和高IO应用分离,尽量保证IO资源不被抢占。
