MongoDB 4.x高可靠1、节点部署优化1.1、硬件规划1.1.1、保证足够的内存1.1.2、使用SSD硬盘1.1.3、使用RAID101.1.4、保证网络质量1.2、系统调优1.2.1、选择合适的文件系统1.2.2、关闭NUMA1.2.3、关闭磁盘预读readahead1.2.4、关闭透明大页1.2.5、关闭atime选项1.2.6、修改资源限制1.3、数据库配置1.3.1、启用Journal日志1.3.2、日志和数据分离1.3.3、保持时钟同步1.3.4、连接数限制2、集群高可靠2.1、反亲和部署2.2、避免集中存储2.3、警惕资源超分3、应用层高可靠3.1、故障隔离3.2、故障转移/恢复4、备份可靠性4.1、逻辑备份4.1.1、mongodump命令4.1.2、mongorestore命令4.1.3、备份快照一致性4.2、物理备份4.2.1、分片集群备份4.2.2、分片集群恢复4.3、增量备份5、容灾可靠性5.1、同城灾备5.2、异地灾备5.3、异地多活1、节点部署优化务必记住一点单机模式的MongoDB实例无法保证高可用生产环境中应该使用副本集模式或分片副本集模式的集群。对于每个MongoDB实例节点的部署可以遵循一些最佳实践来提升性能及稳定性。1.1、硬件规划1.1.1、保证足够的内存MongoDB在工作集小于可用内存时性能表现最好。对提升高实时业务的读写性能而言足够的内存往往是最重要的因素。在内存不足的情况下其他优化一般所能产生的效果是有限的。如果工作集超过了单一服务器的内存则可以考虑通过分片实现水平扩展。最好的做法是提前规划工作集的大小在运行期间可以执行serverStatus命令通过检查WiredTiger缓存的淘汰频率来评估缓存大小是否足够。1.1.2、使用SSD硬盘对于以写入为主的应用建议使用固态硬盘SSD来保存数据和日志。MongoDB在大多数情况下会使用随机I/O操作因而固态硬盘可以显著提高写入密集型应用的性能。在磁盘系统中数据的读取主要由寻道时间决定。对于机械磁盘而言寻道时间约为5ms而固态硬盘的寻道时间则约为0.1ms比机械磁盘快了大概50倍。对于副本集成员中的数据节点应保持一致的配置。一种常见的问题是为备节点配置了较主节点更低性能的磁盘导致主备节点的oplog差异过大增加了复制窗口断裂的风险。对于使用WriteConcernmajority的写入则会严重影响性能。1.1.3、使用RAID10RAID独立磁盘冗余阵列是一种提高磁盘可靠性和性能的技术在所有的配置方式中推荐使用RAID10。RAID10兼备了RAID0和RAID1的优点在提供并行写入的同时也通过镜像提高了可靠性。建议在MongoDB的数据和日志存储中使用RAID10磁盘阵列。1.1.4、保证网络质量在没有其他因素的干扰下网络吞吐量与MongoDB的吞吐量是成正比的。应用上必须保证MongoDB集群内部、业务服务到MongoDB集群的网络带宽是充足的。这包括保证MongoDB集群内部以及应用侧与mongos之间能达到较高的网络吞吐量每秒千兆以上。保证MongoDB集群内部以及应用侧与mongos之间有较低的网络延迟小于100毫秒。1.2、系统调优1.2.1、选择合适的文件系统在Linux系统中必须使用EXT4或者XFS文件系统作为数据和日志的存储卷。不推荐EXT3文件系统MongoDB会定期进行文件的预分配操作如果选用了EXT3文件系统那么这些操作会造成一些难以接受的卡顿。而在EXT4和XFS文件系统中预分配将只会影响元数据层的修改这比EXT3的预分配方式要高效很多。1.2.2、关闭NUMA众所周知基于NUMA的非一致性内存架构不适合数据库的场景。NUMA适用于多核架构下高速访问少量缓存数据的场景但MongoDB倾向于访问更多的数据如果启用NUMA则可能会导致CPU本地缓存大量的溢出和置换会大大降低性能。可以在执行MongoDB相关命令前加上numactl–interleaveall以关闭NUMA功能。参考下面的命令numactl--interleaveall /opt/mongodb/bin/mongod-f/opt/mongodb/conf/mongo.conf1.2.3、关闭磁盘预读readahead对于WiredTiger存储引擎建议禁用磁盘预读readahead设置为0。为了进一步减少真实的I/O操作次数磁盘每次都会进行预读即读取数据的同时按顺序向后读取一定长度的数据并置入内存。磁盘预读的前提是大部分数据是连续读取的例如对视频文件读取一部分之后一定会读取后面的数据段。然而在大多数场景中MongoDB会随机访问磁盘因此预读对性能的提升帮助有限反而会产生一些无效的内存占用。通过下面的命令查看磁盘预读配置执行如下命令修改预读1.2.4、关闭透明大页由于数据库对内存的访问一般都是随机访问而不是连续访问。透明大页Transparent Huge Pages, THP在随机访问模式上反而制约了MongoDB的性能。因此建议在Linux系统中关闭THP特性。为MongoDB所在服务器关闭THP可以在/etc/rc.local中添加如下命令检查THP是否已经关闭代码如下不同的Linux版本所在配置文件可能不同可视情况而定。1.2.5、关闭atime选项文件系统默认会记录每个文件的访问时间由于MongoDB可能会频繁访问数据文件将访问时间记录禁用可以获得一些性能提升。在Linux系统中挂载数据分区时使用noatime选项代码如下1.2.6、修改资源限制MongoDB会视情况动态创建连接在默认的网络I/O模型中每个连接会使用一个线程同时包含一个文件描述符句柄。为了避免产生限制建议将这些参数设置得大一些。编辑/etc/sysctl.conf文件设置内核参数代码如下保存内核参数设置代码如下设置ulimit代码如下1.3、数据库配置1.3.1、启用Journal日志无论何种情况都建议启用Jounal日志功能。MongoDB采用了缓冲延迟刷盘的机制写入数据存在最高60s丢失的风险。Journal日志功能提供断电保护可将损失风险降低到100ms以内。检查MongoDB配置文件确认开启Jounal日志功能代码如下1.3.2、日志和数据分离建议将MongoDB运行日志、Journal预写日志、数据文件存放到不同的磁盘有利于提升整体I/O的吞吐量。此外可以开启directoryPerDB将不同数据库的数据文件使用单独的目录挂载。配置文件示例如下如上述配置就可以将/data/mongodb、/data/mongodb/log、/data/mongodb/journal单独挂载。1.3.3、保持时钟同步务必让集群各节点保持时钟同步最好延迟不要超过1s。MongoDB对时钟偏移做了一些兼容但分布式节点之间的时钟延迟仍然可能产生一些未知的影响。此外要谨慎发生时间跳变的情况在MongoDB 3.4版本中副本集节点在时间跳变时会导致主备节点切换。oplog使用本地时间和计数器来生成optime一些异常的时钟跳变会增加计数器溢出的风险。在Linux系统中建议使用NTP服务来保持节点间的时钟同步。1.3.4、连接数限制对MongoDB来说太高的并发连接会造成服务器被大量的资源所占用。每个连接需要占用一个文件句柄同时还包括TCP协议栈的独立读写缓冲区。默认情况下MongoDB为每个连接分配一个线程默认的线程栈最大为1MB的空间。当存在大量的并发连接时会导致MongoDB产生很高的内存压力上下文切换的开销变大。此时性能下降明显。应用上应该规划合理的连接池分布避免产生过高的连接数。除此之外还应该尽量避免使用短连接一些应用处理产生的Bug很容易产生连接泄露问题。在MongoDB服务器端通过配置net.maxIncomingConnections来限制最高的并发连接数这个值建议不大于1万。在客户端方面驱动默认为每个远程主机连接设置100的连接数上限应用可适当进行调整结合自身的吞吐量、请求时延以及具体的部署拓扑综合考量。2、集群高可靠对于集群模式的部署保持资源隔离是颇为重要的一个原则。无论是计算还是存储一旦多个节点使用了共享资源则必然会大幅度增加故障的隐患。2.1、反亲和部署如果产品使用了自建MongoDB集群的部署模式则需要仔细审视集群节点是否满足反亲和的要求。对于同一个副本集内的不同节点应保证其所在虚拟机位于不同的物理机上。一旦物理机发生电源、网络等故障其他成员仍然可以工作如图所示。2.2、避免集中存储虚拟化环境中另一个常见的做法是使用存储池RAID技术对MongoDB副本集来说将多个节点对接到同一个存储池是存在风险的如图所示。如果条件允许则应该将各个副本集成员分离到不同的存储池上对于分片集群可以考虑如图所示的部署。利用图中的部署方式假设某个存储池发生故障所有分片仍然可以保持可用。2.3、警惕资源超分超分是一种提升资源利用效率的手段宿主机物理机通常可以分配比实际情况更大的CPU、内存。但资源超分的合理性有一个前提那就是客户机在正常情况下都不会处于高负荷的状态。Ballooning技术可以让客户机虚拟机在运行时动态地调整内存资源然而这对MongoDB十分不利WiredTiger更适合独占式内存在物理内存变得紧张的情况下可能会出现意外。另外CPU超分同样会导致计算资源抢占在一些负载较重的生产环境中资源超分情况往往会让MongoDB数据库表现失常。3、应用层高可靠分布式环境带来了诸多的不确定性因而有必要在应用层面考虑实现高可用。业务上对MongoDB的读写操作需满足如下目的。故障隔离部分业务产生的故障不应该级联影响其他业务。可恢复性对于局部性故障可自动实现转移或者业务在产生一些异常抖动时可以通过重试的手段进行恢复。3.1、故障隔离连接池分离。通常单个应用微服务内部可能只会使用一个连接池MongoClient进行读写。在业务场景错综复杂时使用同一个连接池难以保证各业务彼此不受影响。例如某个订单服务同时提供了快速下单、批量查询历史日志功能对于前者通常需要实时响应如响应时延在20ms而后者则允许有一定的时延。如果仅使用一个连接池则很容易出现连接抢占的情况。MongoClient为每个获取连接的线程提供了排队机制waitQueueSize默认的队列大小为连接池的5倍。持续增加的日志查询类请求会抢占连接此时会导致大量下单请求线程进入阻塞队列队列一旦溢出就可能出现拒绝服务的问题。在有限的条件下考虑在应用内部为不同业务启用单独的连接池可以降低这类风险如图所示。主分片分离。在分片集群中对每一个启用分片功能的数据库都会指定一个唯一的主分片此时所有非分片集群都存储在主分片中。生产环境中的主分片通常承担了更大的压力并非所有集合都启用了分片机制。如果集群中存在多个逻辑库按微服务划分则应尽可能将不同逻辑库的主分片分散到不同分片上如图所示。标签tag分离。考虑按业务划分不同的标签将不同的业务数据存储到不同分片区域中如图所示。集群分离。对不同的业务使用单独的MongoDB集群如图所示。3.2、故障转移/恢复服务实例同时接入多个mongos避免单点故障。MongoClient会定时向多个mongos主机发送心跳以探测对端是否存活。如果某个mongos出现故障则MongoClient会自动屏蔽故障节点以避免业务受损如图所示。应用服务实例同时连接多个mongos同时也有利于实现mongos之间的负载平衡。实现重试。数据库节点故障、网络异常或是主备节点切换行为都可能导致业务出现失败。为了应对一些临时性的失效MongoDB Java Driver提供了可重试的读写能力来降低影响MongoDB 3.6版本提供可重试写MongoDB 4.2版本支持可重试读。在最新的版本中可重试的读写行为是默认的但当前的实现仅支持一次重试。对于一些关键性业务可以自行实现重试逻辑或使用spring-retry框架。4、备份可靠性毫无疑问数据备份是非常重要的一个环节。随时持有可用的备份数据可以在发生不可逆故障时降低业务的损失。对于部署在生产环境中的数据库集群我们通常需要定期进行数据备份。MongoDB实现备份的方式包含如下几种。使用mongodump/mongorestore进行逻辑备份、恢复。使用文件系统复制、LVMlogical volumemanager逻辑卷管理快照等方式进行物理备份。使用mongodump/mongorestore进行逻辑备份、恢复。使用文件系统复制、LVMlogical volumemanager逻辑卷管理快照等方式进行物理备份。使用管理工具进行数据备份管理。4.1、逻辑备份在MongoDB安装软件中附带了mongodump、mongorestore因此不需要单独获取。4.1.1、mongodump命令mongodump命令可以将数据库文档导出为BSON格式除了支持库级、表级备份还可以指定查询条件过滤不需要的数据。BSON格式的文件可以在任意结构的MongoDB集群中进行恢复甚至是不同的版本。执行下面的命令对本机的数据库进行dump备份mongodump--port27017-o backup该命令会将除local外的所有数据库导出到backup目录下每个集合对应一个BSON文件。通常mongodump命令导出的数据要少一些因为该命令不会导出索引数据导出结果中只会包含索引的定义文件JSON。在通过mongorestore进行恢复数据时会自动重建索引。备份指定的表可以使用-c参数代码如下mongodump--port27017--db appdb-cT_TEST_DATA-o backup这样在输出结果中所有集合都会对应一个.gz后缀的压缩后的文件。由于导出文件比较分散一种更加便捷的方式是将所有备份文件合并成一个归档文件代码如下mongodump--port27017--archive-all.archive4.1.2、mongorestore命令使用mongorestore命令可以将用mongodump命令导出的备份文件进行恢复。如下面的命令mongorestore--port27017--drop backup/–drop表示恢复时先删除存在的集合由于mongorestore只会执行insert操作为了避免冲突往往需要先清空集合。如果只希望恢复部分集合则可以使用–nsInclude选项代码如下mongorestore--port27017--nsInclude appdb.*--gzip--drop backup/4.1.3、备份快照一致性在mongodump命令执行的过程中业务服务可能会产生新的数据写入为了实现Point-In-Time时间点一致的备份需要使用–oplog选项代码如下mongodump--oplog-o backup–oplog需要对副本集成员使用加入该选项后mongodump命令会在导出过程中捕捉增量产生的oplog输出到结果文件中。同样在mongorestore命令中使用–oplogReplay选项来恢复这些oplog代码如下mongorestore--oplogReplay--drop backup/注意mongodump命令对性能的影响比较明显其会实现大量的临时内存在系统内存比较紧张时通常会明显加大I/O压力。不适合在大数据集中执行mongodump/mongorestore命令这会非常缓慢。一般小型部署或特定场景中可以使用逻辑备份。从MongoDB 4.2版本开始已经不推荐使用mongodump命令进行分片集群的数据备份因为该命令无法保证分布式事务的原子性。4.2、物理备份物理备份的原理比较简单一般通过系统工具cp或rsync对数据和日志文件进行复制。如果条件允许则可以使用LVM来创建快照备份卷基于LVM的文件系统快照效率更高。物理备份的文件不具备通用性必须在使用同一种存储引擎、同一个拓扑结构以及同一版本的MongoDB集群中进行恢复。4.2.1、分片集群备份1关闭分片均衡器。连接mongos执行如下命令use configsh.stopBalancer()2选择用于备份的备节点在config备节点和每个分片的备节点上执行fsyncLock命令代码如下db.fsyncLock()执行fsyncLock命令时mongod会将内存中的修改同步到磁盘并阻塞所有的写操作。由于fsyncLock命令会令备节点停止同步因此需要保证备份锁定的时间不能太长否则可能导致备节点脱离主节点的复制窗口。3备份config节点数据。使用文件复制或LVM快照的方式进行数据备份。备份完成后执行fsyncUnlock命令进行解锁代码如下db.fsyncUnlock()4备份每个分片的备节点数据。5为每个分片的备节点执行fsyncUnlock命令解锁。6重新开启均衡器。连接mongos执行如下代码use configsh.setBalancerState(true)4.2.2、分片集群恢复1停止所有mongos/config/shard节点。2恢复config节点备份。使用文件复制或LVM卷的方式恢复数据重新启动。如果是恢复到新的部署节点IP发生变化则需要执行删除local数据库并重新初始化副本集。更新config.shards集合将新集群的分片信息写入。3恢复shard备份使用复制或LVM卷挂载的方式恢复数据重新启动。如果是恢复到新的部署节点IP发生变化则同样需要删除local数据库并重新初始化副本集。4重启所有mongos节点。如果是恢复到新的部署节点IP发生变化则需要将配置服务器指向新的config节点地址。5检查集群状态。连接mongos节点执行sh.status命令查看状态。4.3、增量备份无论是使用mongodump命令还是文件快照备份都是对全量的系统数据进行备份。全量备份需要使用更多的空间而且恢复时间也更长这种备份一般按天或按周进行。为了达到更细粒度的控制即恢复到任意时间点PITR可以选择增量备份。增量备份需要基于oplog实现大致过程如下1执行全量备份并记录当前全量备份的时刻TSP。210分钟后使用mongodump命令对oplog进行备份选择TSP时刻到当前时间的日志数据。命令如下在查询条件中TS_START对应TSP时刻TS_END对应当前时刻可以将开始时间往前移动几秒钟尽可能保证不丢失日志。备份完成后更新TSP为当前的时刻。3重复执行步骤2这样我们可以持续获得从上一次全量备份之后的多份增量数据。如果需要恢复到某个时间点则只需要先恢复最近的全量备份然后通过mongorestore–oplogReplay恢复增量的oplog。代码如下对于生产环境中的备份管理可以遵循如下一些原则优先使用基于快照的物理备份进行全量备份。定期备份至少同时保有两份可用的全量备份。将备份数据保存到远程服务器提高可靠性。校验备份文件的完整性在条件允许的情况下对备份文件进行测试。在必要时进行增量备份使用成熟的备份管理工具或托管服务提升效率。5、容灾可靠性1. 数据库容灾我们在前面已经谈及副本集高可用的多个细节高可用的目的是当某个节点发生故障意外中断时系统能快速恢复。实现高可用的技术仍然需要依赖许多本地的基础设施例如对故障节点的检测首先需要保证基础网络的可用性。那么如果这些基础设施也发生故障了呢根据墨菲定律凡是有可能发生出错的事情终究有一天会出现。当应用系统、数据库以及运行它们所必需的基础设施发生不可抗力的损坏时我们就需要借助系统级的容灾能力来进行接管以保证业务能继续运行。数据库的容灾通常需要考虑冗余、数据复制及故障转移等多个方面的基数。对于一个容灾系统来说我们关注的SLA指标主要如下。RPORecovery Point Objective指目标恢复时间点当灾难发生时系统最多可能发生丢失的数据时长。RTORecovery Time Objective指目标恢复时间当灾难发生时需要多长的时间完成系统恢复。对于中小型应用来说常见的做法是使用定时备份例如每天将数据备份保存到异地的远程服务器。在发生灾难性故障时第一时间重建集群并拉取远程备份数据进行恢复这是一种离线式的容灾冷备。数据备份可以存储多个或者长期保存以保证持久可用。但备份数据的恢复时间通常较慢包含远程下载、本地恢复的时间而且在备份窗口内的数据都会丢失如果提供了增量备份则可以减少丢失的数据。因此基于备份的容灾只能用于对RTO、RPO要求较低的系统。为了达成高可用的容灾目的目标方案是使用热备这需要让主数据中心和备用数据中心同时工作并保持数据同步当出现问题时能及时切换。热备容灾也是本节讨论的重点对于MongoDB来说需要结合现有的基础设施来构建容灾方案。2. 理解Region、AZ云计算领域基于基础设施隔离的角度定义了Region、AZAvailable Zone两个概念。Region地域通常是物理意义上位于不同地方的数据中心地域之间的距离较远这意味着构建Region之间的高速传输网络会产生较高的成本。AZAvailable Zone即可用区可理解为同一地域内互相独立的物理机房。同一Region中的多个AZ保持独立的电力供应AZ之间一般通过高速光纤相连。相比跨Region来说跨AZ的网络时延要更短大约只有几毫秒。举个例子以深圳、上海分别作为两个Region而深圳的Region中又搭建了福田机房可用区1、观澜机房可用区2、南山机房可用区3。5.1、同城灾备对于同一个副本集或者分片集群来说可以将各个副本集成员部署到多个可用区AZ来实现同城灾备如图所示。在同城灾备架构中一个分片集群被均匀地分布到3个可用区上。每个分片包括配置副本集的主备节点都位于不同的AZ。mongos节点同样也保持均匀分布当某个AZ故障时所有的分片、配置副本集以及mongos都仍然是可用的。5.2、异地灾备如果需要支持异地灾备则可以在不同的Region中建立主备两个集群。集群之间利用oplog复制来实现数据同步如图所示。只要保证oplog的可见性容灾复制方案就是可行的。连接器所负责的工作与副本集内部的复制线程大致相同而使用批量化提交有助于提升同步的吞吐量。可以自己实现连接器代码或者使用开源框架如MongoShake。理论上连接器也可以用于两个独立的分片集群但必须小心自动均衡带来的问题。集群场景中需要为多个分片分别使用单独的连接器这可以实现并行复制。同时为了降低连接器的性能损耗一些分片均衡迁移产生的oplog需要被过滤掉。那么当chunk数据发生迁移时就有可能会出现乱序问题。尽管我们可以关闭均衡器来规避这个问题但始终不是完美的解决方案。基于新版本的Change Stream为容灾复制提供了新的思路Change Stream是基于oplog实现的而且更加简单、稳定同时也具有断点续传的能力。更重要的一点是在分片集群中获得的Change Stream是全局有序的可以不需要担心均衡器带来的困扰。从MongoDB4.0版本开始Change Stream支持库级、集群级别的监听进一步简化了应用的开发如图所示。这里还有一些可以改进的地方例如为不同的数据库使 用独立的连接器还可以使用消息队列分发来提升写入的吞吐量。异地灾备要点数据复制是实现异地灾备的关键技术但除此之外我们需要考虑的因素还有很多例如数据同步服务连接器是否稳定写入性能是否足够快。同步过程中业务性能是否会受到影响。主备节点之间的数据是否一致如何对一些关键的元数据进行校验。如何准确判定主备系统是否故障如何避免频繁切换。容灾切换是否快速、高效如何降低切换时数据的丢失率。5.3、异地多活异地多活是一种更加复杂的容灾设计它要求在同一时刻所有不同地域的子系统都是可用的而且每个子系统都同时承担了一定的流量。利用MongoDB原生的复制以及ShardZone分区特性可以实现按地理容灾多活的模式如图所示。在图中我们将一个集群中的每个分片都均匀分布到了不同的机房。其中S1.P是shard1的Primary节点S3.S是指shard3的Secondary节点依次类推例如北京机房则同时部署了shard1主节点、shard3备节点、shard2备隐藏节点。异地多活场景下还同时需要满足就近写入、读取的原则例如社交平台上的用户优先通过离自己最近的服务器进行注册、查看同城好友的互动等。利用ShardZone分区标签特性可以为数据加上地域标签代码如下sh.addShardToZone命令等同于sh.addShardTag,sh.updateZoneKeyRange命令则等同于sh.addTagRange。从MongoDB 3.4版本开始使用Zone来表达分片标签的语义。除了定义分片标签、数据范围分区不要忘记为数据表users启用分片这里分片键必须包含分片范围字段前缀匹配代码如下如此我们便获得了在不同地域存储多个数据副本的能力利用副本集自动的失效转移failover能力当某个机房发生故障时能自行切换。客户端通过指定readPreferenceprimary或primaryPerferred可优先读取本地数据。如果希望在机房故障修复后还能恢复本地local读写的能力可以通过设定成员的选举优先权priority来进行控制例如为shard1上的北京节点设置最高的优先级。然而地理分散型的容灾架构仍然存在一些挑战由于副本集采用了跨Region部署很难保证网络时延问题可能会造成数据同步差距较大。一旦发生故障只能由另一个Region接管当前业务性能、可靠性会出现降级。而且也没有较好的办法进行流量切换。
MongoDB 4.x——高可靠
MongoDB 4.x高可靠1、节点部署优化1.1、硬件规划1.1.1、保证足够的内存1.1.2、使用SSD硬盘1.1.3、使用RAID101.1.4、保证网络质量1.2、系统调优1.2.1、选择合适的文件系统1.2.2、关闭NUMA1.2.3、关闭磁盘预读readahead1.2.4、关闭透明大页1.2.5、关闭atime选项1.2.6、修改资源限制1.3、数据库配置1.3.1、启用Journal日志1.3.2、日志和数据分离1.3.3、保持时钟同步1.3.4、连接数限制2、集群高可靠2.1、反亲和部署2.2、避免集中存储2.3、警惕资源超分3、应用层高可靠3.1、故障隔离3.2、故障转移/恢复4、备份可靠性4.1、逻辑备份4.1.1、mongodump命令4.1.2、mongorestore命令4.1.3、备份快照一致性4.2、物理备份4.2.1、分片集群备份4.2.2、分片集群恢复4.3、增量备份5、容灾可靠性5.1、同城灾备5.2、异地灾备5.3、异地多活1、节点部署优化务必记住一点单机模式的MongoDB实例无法保证高可用生产环境中应该使用副本集模式或分片副本集模式的集群。对于每个MongoDB实例节点的部署可以遵循一些最佳实践来提升性能及稳定性。1.1、硬件规划1.1.1、保证足够的内存MongoDB在工作集小于可用内存时性能表现最好。对提升高实时业务的读写性能而言足够的内存往往是最重要的因素。在内存不足的情况下其他优化一般所能产生的效果是有限的。如果工作集超过了单一服务器的内存则可以考虑通过分片实现水平扩展。最好的做法是提前规划工作集的大小在运行期间可以执行serverStatus命令通过检查WiredTiger缓存的淘汰频率来评估缓存大小是否足够。1.1.2、使用SSD硬盘对于以写入为主的应用建议使用固态硬盘SSD来保存数据和日志。MongoDB在大多数情况下会使用随机I/O操作因而固态硬盘可以显著提高写入密集型应用的性能。在磁盘系统中数据的读取主要由寻道时间决定。对于机械磁盘而言寻道时间约为5ms而固态硬盘的寻道时间则约为0.1ms比机械磁盘快了大概50倍。对于副本集成员中的数据节点应保持一致的配置。一种常见的问题是为备节点配置了较主节点更低性能的磁盘导致主备节点的oplog差异过大增加了复制窗口断裂的风险。对于使用WriteConcernmajority的写入则会严重影响性能。1.1.3、使用RAID10RAID独立磁盘冗余阵列是一种提高磁盘可靠性和性能的技术在所有的配置方式中推荐使用RAID10。RAID10兼备了RAID0和RAID1的优点在提供并行写入的同时也通过镜像提高了可靠性。建议在MongoDB的数据和日志存储中使用RAID10磁盘阵列。1.1.4、保证网络质量在没有其他因素的干扰下网络吞吐量与MongoDB的吞吐量是成正比的。应用上必须保证MongoDB集群内部、业务服务到MongoDB集群的网络带宽是充足的。这包括保证MongoDB集群内部以及应用侧与mongos之间能达到较高的网络吞吐量每秒千兆以上。保证MongoDB集群内部以及应用侧与mongos之间有较低的网络延迟小于100毫秒。1.2、系统调优1.2.1、选择合适的文件系统在Linux系统中必须使用EXT4或者XFS文件系统作为数据和日志的存储卷。不推荐EXT3文件系统MongoDB会定期进行文件的预分配操作如果选用了EXT3文件系统那么这些操作会造成一些难以接受的卡顿。而在EXT4和XFS文件系统中预分配将只会影响元数据层的修改这比EXT3的预分配方式要高效很多。1.2.2、关闭NUMA众所周知基于NUMA的非一致性内存架构不适合数据库的场景。NUMA适用于多核架构下高速访问少量缓存数据的场景但MongoDB倾向于访问更多的数据如果启用NUMA则可能会导致CPU本地缓存大量的溢出和置换会大大降低性能。可以在执行MongoDB相关命令前加上numactl–interleaveall以关闭NUMA功能。参考下面的命令numactl--interleaveall /opt/mongodb/bin/mongod-f/opt/mongodb/conf/mongo.conf1.2.3、关闭磁盘预读readahead对于WiredTiger存储引擎建议禁用磁盘预读readahead设置为0。为了进一步减少真实的I/O操作次数磁盘每次都会进行预读即读取数据的同时按顺序向后读取一定长度的数据并置入内存。磁盘预读的前提是大部分数据是连续读取的例如对视频文件读取一部分之后一定会读取后面的数据段。然而在大多数场景中MongoDB会随机访问磁盘因此预读对性能的提升帮助有限反而会产生一些无效的内存占用。通过下面的命令查看磁盘预读配置执行如下命令修改预读1.2.4、关闭透明大页由于数据库对内存的访问一般都是随机访问而不是连续访问。透明大页Transparent Huge Pages, THP在随机访问模式上反而制约了MongoDB的性能。因此建议在Linux系统中关闭THP特性。为MongoDB所在服务器关闭THP可以在/etc/rc.local中添加如下命令检查THP是否已经关闭代码如下不同的Linux版本所在配置文件可能不同可视情况而定。1.2.5、关闭atime选项文件系统默认会记录每个文件的访问时间由于MongoDB可能会频繁访问数据文件将访问时间记录禁用可以获得一些性能提升。在Linux系统中挂载数据分区时使用noatime选项代码如下1.2.6、修改资源限制MongoDB会视情况动态创建连接在默认的网络I/O模型中每个连接会使用一个线程同时包含一个文件描述符句柄。为了避免产生限制建议将这些参数设置得大一些。编辑/etc/sysctl.conf文件设置内核参数代码如下保存内核参数设置代码如下设置ulimit代码如下1.3、数据库配置1.3.1、启用Journal日志无论何种情况都建议启用Jounal日志功能。MongoDB采用了缓冲延迟刷盘的机制写入数据存在最高60s丢失的风险。Journal日志功能提供断电保护可将损失风险降低到100ms以内。检查MongoDB配置文件确认开启Jounal日志功能代码如下1.3.2、日志和数据分离建议将MongoDB运行日志、Journal预写日志、数据文件存放到不同的磁盘有利于提升整体I/O的吞吐量。此外可以开启directoryPerDB将不同数据库的数据文件使用单独的目录挂载。配置文件示例如下如上述配置就可以将/data/mongodb、/data/mongodb/log、/data/mongodb/journal单独挂载。1.3.3、保持时钟同步务必让集群各节点保持时钟同步最好延迟不要超过1s。MongoDB对时钟偏移做了一些兼容但分布式节点之间的时钟延迟仍然可能产生一些未知的影响。此外要谨慎发生时间跳变的情况在MongoDB 3.4版本中副本集节点在时间跳变时会导致主备节点切换。oplog使用本地时间和计数器来生成optime一些异常的时钟跳变会增加计数器溢出的风险。在Linux系统中建议使用NTP服务来保持节点间的时钟同步。1.3.4、连接数限制对MongoDB来说太高的并发连接会造成服务器被大量的资源所占用。每个连接需要占用一个文件句柄同时还包括TCP协议栈的独立读写缓冲区。默认情况下MongoDB为每个连接分配一个线程默认的线程栈最大为1MB的空间。当存在大量的并发连接时会导致MongoDB产生很高的内存压力上下文切换的开销变大。此时性能下降明显。应用上应该规划合理的连接池分布避免产生过高的连接数。除此之外还应该尽量避免使用短连接一些应用处理产生的Bug很容易产生连接泄露问题。在MongoDB服务器端通过配置net.maxIncomingConnections来限制最高的并发连接数这个值建议不大于1万。在客户端方面驱动默认为每个远程主机连接设置100的连接数上限应用可适当进行调整结合自身的吞吐量、请求时延以及具体的部署拓扑综合考量。2、集群高可靠对于集群模式的部署保持资源隔离是颇为重要的一个原则。无论是计算还是存储一旦多个节点使用了共享资源则必然会大幅度增加故障的隐患。2.1、反亲和部署如果产品使用了自建MongoDB集群的部署模式则需要仔细审视集群节点是否满足反亲和的要求。对于同一个副本集内的不同节点应保证其所在虚拟机位于不同的物理机上。一旦物理机发生电源、网络等故障其他成员仍然可以工作如图所示。2.2、避免集中存储虚拟化环境中另一个常见的做法是使用存储池RAID技术对MongoDB副本集来说将多个节点对接到同一个存储池是存在风险的如图所示。如果条件允许则应该将各个副本集成员分离到不同的存储池上对于分片集群可以考虑如图所示的部署。利用图中的部署方式假设某个存储池发生故障所有分片仍然可以保持可用。2.3、警惕资源超分超分是一种提升资源利用效率的手段宿主机物理机通常可以分配比实际情况更大的CPU、内存。但资源超分的合理性有一个前提那就是客户机在正常情况下都不会处于高负荷的状态。Ballooning技术可以让客户机虚拟机在运行时动态地调整内存资源然而这对MongoDB十分不利WiredTiger更适合独占式内存在物理内存变得紧张的情况下可能会出现意外。另外CPU超分同样会导致计算资源抢占在一些负载较重的生产环境中资源超分情况往往会让MongoDB数据库表现失常。3、应用层高可靠分布式环境带来了诸多的不确定性因而有必要在应用层面考虑实现高可用。业务上对MongoDB的读写操作需满足如下目的。故障隔离部分业务产生的故障不应该级联影响其他业务。可恢复性对于局部性故障可自动实现转移或者业务在产生一些异常抖动时可以通过重试的手段进行恢复。3.1、故障隔离连接池分离。通常单个应用微服务内部可能只会使用一个连接池MongoClient进行读写。在业务场景错综复杂时使用同一个连接池难以保证各业务彼此不受影响。例如某个订单服务同时提供了快速下单、批量查询历史日志功能对于前者通常需要实时响应如响应时延在20ms而后者则允许有一定的时延。如果仅使用一个连接池则很容易出现连接抢占的情况。MongoClient为每个获取连接的线程提供了排队机制waitQueueSize默认的队列大小为连接池的5倍。持续增加的日志查询类请求会抢占连接此时会导致大量下单请求线程进入阻塞队列队列一旦溢出就可能出现拒绝服务的问题。在有限的条件下考虑在应用内部为不同业务启用单独的连接池可以降低这类风险如图所示。主分片分离。在分片集群中对每一个启用分片功能的数据库都会指定一个唯一的主分片此时所有非分片集群都存储在主分片中。生产环境中的主分片通常承担了更大的压力并非所有集合都启用了分片机制。如果集群中存在多个逻辑库按微服务划分则应尽可能将不同逻辑库的主分片分散到不同分片上如图所示。标签tag分离。考虑按业务划分不同的标签将不同的业务数据存储到不同分片区域中如图所示。集群分离。对不同的业务使用单独的MongoDB集群如图所示。3.2、故障转移/恢复服务实例同时接入多个mongos避免单点故障。MongoClient会定时向多个mongos主机发送心跳以探测对端是否存活。如果某个mongos出现故障则MongoClient会自动屏蔽故障节点以避免业务受损如图所示。应用服务实例同时连接多个mongos同时也有利于实现mongos之间的负载平衡。实现重试。数据库节点故障、网络异常或是主备节点切换行为都可能导致业务出现失败。为了应对一些临时性的失效MongoDB Java Driver提供了可重试的读写能力来降低影响MongoDB 3.6版本提供可重试写MongoDB 4.2版本支持可重试读。在最新的版本中可重试的读写行为是默认的但当前的实现仅支持一次重试。对于一些关键性业务可以自行实现重试逻辑或使用spring-retry框架。4、备份可靠性毫无疑问数据备份是非常重要的一个环节。随时持有可用的备份数据可以在发生不可逆故障时降低业务的损失。对于部署在生产环境中的数据库集群我们通常需要定期进行数据备份。MongoDB实现备份的方式包含如下几种。使用mongodump/mongorestore进行逻辑备份、恢复。使用文件系统复制、LVMlogical volumemanager逻辑卷管理快照等方式进行物理备份。使用mongodump/mongorestore进行逻辑备份、恢复。使用文件系统复制、LVMlogical volumemanager逻辑卷管理快照等方式进行物理备份。使用管理工具进行数据备份管理。4.1、逻辑备份在MongoDB安装软件中附带了mongodump、mongorestore因此不需要单独获取。4.1.1、mongodump命令mongodump命令可以将数据库文档导出为BSON格式除了支持库级、表级备份还可以指定查询条件过滤不需要的数据。BSON格式的文件可以在任意结构的MongoDB集群中进行恢复甚至是不同的版本。执行下面的命令对本机的数据库进行dump备份mongodump--port27017-o backup该命令会将除local外的所有数据库导出到backup目录下每个集合对应一个BSON文件。通常mongodump命令导出的数据要少一些因为该命令不会导出索引数据导出结果中只会包含索引的定义文件JSON。在通过mongorestore进行恢复数据时会自动重建索引。备份指定的表可以使用-c参数代码如下mongodump--port27017--db appdb-cT_TEST_DATA-o backup这样在输出结果中所有集合都会对应一个.gz后缀的压缩后的文件。由于导出文件比较分散一种更加便捷的方式是将所有备份文件合并成一个归档文件代码如下mongodump--port27017--archive-all.archive4.1.2、mongorestore命令使用mongorestore命令可以将用mongodump命令导出的备份文件进行恢复。如下面的命令mongorestore--port27017--drop backup/–drop表示恢复时先删除存在的集合由于mongorestore只会执行insert操作为了避免冲突往往需要先清空集合。如果只希望恢复部分集合则可以使用–nsInclude选项代码如下mongorestore--port27017--nsInclude appdb.*--gzip--drop backup/4.1.3、备份快照一致性在mongodump命令执行的过程中业务服务可能会产生新的数据写入为了实现Point-In-Time时间点一致的备份需要使用–oplog选项代码如下mongodump--oplog-o backup–oplog需要对副本集成员使用加入该选项后mongodump命令会在导出过程中捕捉增量产生的oplog输出到结果文件中。同样在mongorestore命令中使用–oplogReplay选项来恢复这些oplog代码如下mongorestore--oplogReplay--drop backup/注意mongodump命令对性能的影响比较明显其会实现大量的临时内存在系统内存比较紧张时通常会明显加大I/O压力。不适合在大数据集中执行mongodump/mongorestore命令这会非常缓慢。一般小型部署或特定场景中可以使用逻辑备份。从MongoDB 4.2版本开始已经不推荐使用mongodump命令进行分片集群的数据备份因为该命令无法保证分布式事务的原子性。4.2、物理备份物理备份的原理比较简单一般通过系统工具cp或rsync对数据和日志文件进行复制。如果条件允许则可以使用LVM来创建快照备份卷基于LVM的文件系统快照效率更高。物理备份的文件不具备通用性必须在使用同一种存储引擎、同一个拓扑结构以及同一版本的MongoDB集群中进行恢复。4.2.1、分片集群备份1关闭分片均衡器。连接mongos执行如下命令use configsh.stopBalancer()2选择用于备份的备节点在config备节点和每个分片的备节点上执行fsyncLock命令代码如下db.fsyncLock()执行fsyncLock命令时mongod会将内存中的修改同步到磁盘并阻塞所有的写操作。由于fsyncLock命令会令备节点停止同步因此需要保证备份锁定的时间不能太长否则可能导致备节点脱离主节点的复制窗口。3备份config节点数据。使用文件复制或LVM快照的方式进行数据备份。备份完成后执行fsyncUnlock命令进行解锁代码如下db.fsyncUnlock()4备份每个分片的备节点数据。5为每个分片的备节点执行fsyncUnlock命令解锁。6重新开启均衡器。连接mongos执行如下代码use configsh.setBalancerState(true)4.2.2、分片集群恢复1停止所有mongos/config/shard节点。2恢复config节点备份。使用文件复制或LVM卷的方式恢复数据重新启动。如果是恢复到新的部署节点IP发生变化则需要执行删除local数据库并重新初始化副本集。更新config.shards集合将新集群的分片信息写入。3恢复shard备份使用复制或LVM卷挂载的方式恢复数据重新启动。如果是恢复到新的部署节点IP发生变化则同样需要删除local数据库并重新初始化副本集。4重启所有mongos节点。如果是恢复到新的部署节点IP发生变化则需要将配置服务器指向新的config节点地址。5检查集群状态。连接mongos节点执行sh.status命令查看状态。4.3、增量备份无论是使用mongodump命令还是文件快照备份都是对全量的系统数据进行备份。全量备份需要使用更多的空间而且恢复时间也更长这种备份一般按天或按周进行。为了达到更细粒度的控制即恢复到任意时间点PITR可以选择增量备份。增量备份需要基于oplog实现大致过程如下1执行全量备份并记录当前全量备份的时刻TSP。210分钟后使用mongodump命令对oplog进行备份选择TSP时刻到当前时间的日志数据。命令如下在查询条件中TS_START对应TSP时刻TS_END对应当前时刻可以将开始时间往前移动几秒钟尽可能保证不丢失日志。备份完成后更新TSP为当前的时刻。3重复执行步骤2这样我们可以持续获得从上一次全量备份之后的多份增量数据。如果需要恢复到某个时间点则只需要先恢复最近的全量备份然后通过mongorestore–oplogReplay恢复增量的oplog。代码如下对于生产环境中的备份管理可以遵循如下一些原则优先使用基于快照的物理备份进行全量备份。定期备份至少同时保有两份可用的全量备份。将备份数据保存到远程服务器提高可靠性。校验备份文件的完整性在条件允许的情况下对备份文件进行测试。在必要时进行增量备份使用成熟的备份管理工具或托管服务提升效率。5、容灾可靠性1. 数据库容灾我们在前面已经谈及副本集高可用的多个细节高可用的目的是当某个节点发生故障意外中断时系统能快速恢复。实现高可用的技术仍然需要依赖许多本地的基础设施例如对故障节点的检测首先需要保证基础网络的可用性。那么如果这些基础设施也发生故障了呢根据墨菲定律凡是有可能发生出错的事情终究有一天会出现。当应用系统、数据库以及运行它们所必需的基础设施发生不可抗力的损坏时我们就需要借助系统级的容灾能力来进行接管以保证业务能继续运行。数据库的容灾通常需要考虑冗余、数据复制及故障转移等多个方面的基数。对于一个容灾系统来说我们关注的SLA指标主要如下。RPORecovery Point Objective指目标恢复时间点当灾难发生时系统最多可能发生丢失的数据时长。RTORecovery Time Objective指目标恢复时间当灾难发生时需要多长的时间完成系统恢复。对于中小型应用来说常见的做法是使用定时备份例如每天将数据备份保存到异地的远程服务器。在发生灾难性故障时第一时间重建集群并拉取远程备份数据进行恢复这是一种离线式的容灾冷备。数据备份可以存储多个或者长期保存以保证持久可用。但备份数据的恢复时间通常较慢包含远程下载、本地恢复的时间而且在备份窗口内的数据都会丢失如果提供了增量备份则可以减少丢失的数据。因此基于备份的容灾只能用于对RTO、RPO要求较低的系统。为了达成高可用的容灾目的目标方案是使用热备这需要让主数据中心和备用数据中心同时工作并保持数据同步当出现问题时能及时切换。热备容灾也是本节讨论的重点对于MongoDB来说需要结合现有的基础设施来构建容灾方案。2. 理解Region、AZ云计算领域基于基础设施隔离的角度定义了Region、AZAvailable Zone两个概念。Region地域通常是物理意义上位于不同地方的数据中心地域之间的距离较远这意味着构建Region之间的高速传输网络会产生较高的成本。AZAvailable Zone即可用区可理解为同一地域内互相独立的物理机房。同一Region中的多个AZ保持独立的电力供应AZ之间一般通过高速光纤相连。相比跨Region来说跨AZ的网络时延要更短大约只有几毫秒。举个例子以深圳、上海分别作为两个Region而深圳的Region中又搭建了福田机房可用区1、观澜机房可用区2、南山机房可用区3。5.1、同城灾备对于同一个副本集或者分片集群来说可以将各个副本集成员部署到多个可用区AZ来实现同城灾备如图所示。在同城灾备架构中一个分片集群被均匀地分布到3个可用区上。每个分片包括配置副本集的主备节点都位于不同的AZ。mongos节点同样也保持均匀分布当某个AZ故障时所有的分片、配置副本集以及mongos都仍然是可用的。5.2、异地灾备如果需要支持异地灾备则可以在不同的Region中建立主备两个集群。集群之间利用oplog复制来实现数据同步如图所示。只要保证oplog的可见性容灾复制方案就是可行的。连接器所负责的工作与副本集内部的复制线程大致相同而使用批量化提交有助于提升同步的吞吐量。可以自己实现连接器代码或者使用开源框架如MongoShake。理论上连接器也可以用于两个独立的分片集群但必须小心自动均衡带来的问题。集群场景中需要为多个分片分别使用单独的连接器这可以实现并行复制。同时为了降低连接器的性能损耗一些分片均衡迁移产生的oplog需要被过滤掉。那么当chunk数据发生迁移时就有可能会出现乱序问题。尽管我们可以关闭均衡器来规避这个问题但始终不是完美的解决方案。基于新版本的Change Stream为容灾复制提供了新的思路Change Stream是基于oplog实现的而且更加简单、稳定同时也具有断点续传的能力。更重要的一点是在分片集群中获得的Change Stream是全局有序的可以不需要担心均衡器带来的困扰。从MongoDB4.0版本开始Change Stream支持库级、集群级别的监听进一步简化了应用的开发如图所示。这里还有一些可以改进的地方例如为不同的数据库使 用独立的连接器还可以使用消息队列分发来提升写入的吞吐量。异地灾备要点数据复制是实现异地灾备的关键技术但除此之外我们需要考虑的因素还有很多例如数据同步服务连接器是否稳定写入性能是否足够快。同步过程中业务性能是否会受到影响。主备节点之间的数据是否一致如何对一些关键的元数据进行校验。如何准确判定主备系统是否故障如何避免频繁切换。容灾切换是否快速、高效如何降低切换时数据的丢失率。5.3、异地多活异地多活是一种更加复杂的容灾设计它要求在同一时刻所有不同地域的子系统都是可用的而且每个子系统都同时承担了一定的流量。利用MongoDB原生的复制以及ShardZone分区特性可以实现按地理容灾多活的模式如图所示。在图中我们将一个集群中的每个分片都均匀分布到了不同的机房。其中S1.P是shard1的Primary节点S3.S是指shard3的Secondary节点依次类推例如北京机房则同时部署了shard1主节点、shard3备节点、shard2备隐藏节点。异地多活场景下还同时需要满足就近写入、读取的原则例如社交平台上的用户优先通过离自己最近的服务器进行注册、查看同城好友的互动等。利用ShardZone分区标签特性可以为数据加上地域标签代码如下sh.addShardToZone命令等同于sh.addShardTag,sh.updateZoneKeyRange命令则等同于sh.addTagRange。从MongoDB 3.4版本开始使用Zone来表达分片标签的语义。除了定义分片标签、数据范围分区不要忘记为数据表users启用分片这里分片键必须包含分片范围字段前缀匹配代码如下如此我们便获得了在不同地域存储多个数据副本的能力利用副本集自动的失效转移failover能力当某个机房发生故障时能自行切换。客户端通过指定readPreferenceprimary或primaryPerferred可优先读取本地数据。如果希望在机房故障修复后还能恢复本地local读写的能力可以通过设定成员的选举优先权priority来进行控制例如为shard1上的北京节点设置最高的优先级。然而地理分散型的容灾架构仍然存在一些挑战由于副本集采用了跨Region部署很难保证网络时延问题可能会造成数据同步差距较大。一旦发生故障只能由另一个Region接管当前业务性能、可靠性会出现降级。而且也没有较好的办法进行流量切换。