MongoDB 4.2——管理

📅 2026/7/28 4:03:19
MongoDB 4.2——管理
管理1、以单机模式启动成员2、副本集配置2.1、创建副本集2.2、更改副本集成员2.3、创建比较大的副本集2.4、强制重新配置3、控制成员状态3.1、把主节点变为从节点3.2、阻止选举4、监控复制4.1、获取状态4.2、可视化复制图谱4.3、复制循环4.4、禁用复制链4.5、计算延迟4.6、调整oplog大小4.7、创建索引4.8、在预算有限的情况下进行复制1、以单机模式启动成员许多维护任务不能在从节点上执行因为涉及了写操作​也不应该在主节点上执行因为这会对应用程序性能造成影响。因此以下各节经常会提到以单机模式启动服务器。这意味着需要重新启动成员使其成为单机运行的服务器而不再是一个副本集的成员只是临时的​。在以单机模式启动成员之前首先需要查看一下用于启动的命令行选项。假设是下面这样的db.serverCmdLineOpts(){argv:[mongod,-f,/var/lib/mongod.conf],parsed:{replSet:mySet,port:27017,dbpath:/var/lib/db},ok:1}要对这台服务器进行维护可以在不使用 replSet 选项的情况下对其进行重启。这会使其作为一个独立的 mongod进程来进行读写。我们不希望副本集中的其他服务器联系到它因此会让它监听不同的端口这样其他成员就无法找到它了​。最后要保持 dbpath 不变因为以这种方式重启是为了对这台服务器的数据进行一些操作。首先从 mongo shell 中关闭服务器db.shutdownServer()然后在操作系统的 shell如 bash中从另一个端口重启 mongod无须使用 replSet 参数$ mongod--port30000--dbpath/var/lib/db它现在将作为独立的服务器运行在端口 30000 上监听连接。副本集中的其他成员还在尝试从 27017 端口上连接它发现连接失败并假设其已停止运行。当完成了对服务器的维护后可以使用原始的选项重新启动它。重启之后它会自动与副本集的其余成员进行同步复制它在“离开”期间错过的所有操作。2、副本集配置副本集配置总是保存在 local.system.replset 集合的文档中。这个文档在副本集的所有成员上都是相同的。不要使用 update 更新这个文档应该使用 rs 辅助函数或replSetReconfig 命令。2.1、创建副本集要创建一个副本集首先需要启动副本集成员的 mongod进程然后通过 rs.initiate() 将配置传递给其中一个成员。varconfig{..._id:setName,...members:[...{_id:0,host:host1},...{_id:1,host:host2},...{_id:2,host:host3}...]}rs.initiate(config)应该总是传递一个配置对象给 rs.initiate()否则MongoDB 会尝试自动生成一个单成员副本集的配置。它可能没有使用你想要的主机名或者没有对副本集进行正确的配置。只需对副本集中的一个成员调用 rs.initiate()。接收配置的成员将把配置传递给其他成员。2.2、更改副本集成员当添加一个新的副本集成员时要么它的数据目录应该是空的在这种情况下它将执行初始化同步​要么它拥有来自另外一个成员的数据副本​。连接到主节点并添加一个新成员如下所示rs.add(spock:27017)或者可以以文档的形式指定一个更复杂的成员配置rs.add({host:spock:27017,priority:0,hidden:true})同样可以通过 “host” 字段来对成员进行删除rs.remove(spock:27017)可以通过重新配置来修改成员的设置。修改成员设置时有一些限制不能更改成员的 “_id” 字段不能将接收重新配置命令的成员通常是主节点的优先级设置为 0不能把仲裁者变成非仲裁者反之亦然不能将成员的 “buildIndexes” 字段从 false 更改为 true。值得注意的是可以更改成员的 “host” 字段。因此如果错误地指定了主机名比如使用了公共 IP 而不是私有 IP​则可以在稍后简单地更改配置以使用正确的IP。要更改主机名可以像下面这样varconfigrs.config()config.members[0].hostspock:27017spock:27017rs.reconfig(config)同样的方法也适用于更改任何其他选项用 rs.config()获取配置修改其中的某些部分并通过将新配置传递给rs.reconfig() 来重新配置副本集。2.3、创建比较大的副本集副本集最多只能有 50 个成员其中只有 7 个成员拥有投票权。这是为了减少每个成员发送心跳所需的网络流量并限制选举所需的时间。如果要创建一个超过 7 个成员的副本集那么每个额外的成员都必须被赋予 0 投票权。可以在成员的配置中对其进行指定rs.add({_id:7,host:server-7:27017,votes:0})这样可以使这些成员无法在选举中投赞成票。2.4、强制重新配置当永久丢失一个副本集的大多数成员时你可能希望在没有主节点的情况下重新配置副本集。这有点儿麻烦因为通常需要将重新配置命令发送给主节点。在这种情况下可以向从节点发送重新配置命令来强制重新配置副本集。在 shell 中连接到一个从节点并使用 “force” 选项对其进行重新配置rs.reconfig(config,{force:true})强制重新配置与普通的重新配置遵循相同的规则必须使用正确的选项将有效且格式完好的配置发送给成员。“force” 选项不允许无效的配置它的作用只是让从节点接受重新配置命令。强制重新配置会使副本集 “version” 字段的数字显著增加。你可能会看到它猛增了数万或数十万。这些都是正常的这是为了防止版本号冲突以防网络分区的两边都在进行重新配置​。当从节点接收到重新配置时它会更新自身的配置并将新配置传递给其他成员。副本集的其他成员只有在识别出配置的发送者为当前配置中的一员时才会对配置的更改有所察觉。因此如果一些成员已经改变了主机名则应该在一个保持着旧主机名的成员上进行强制重新配置。如果每个成员都有一个新的主机名则应该关闭副本集中的每个成员在单机模式下启动手动更改local.system.replset 文档然后重新启动成员。3、控制成员状态有多种方式可以手动更改成员的状态以进行维护或应对负载的变化。但需要注意无法强制一个成员成为主节点只能对副本集进行适当的配置即为副本集成员设置高于任何其他成员的优先级。3.1、把主节点变为从节点可以使用 stepDown 函数将主节点降级为从节点rs.stepDown()这会使主节点降级为 SECONDARY 状态并维持 60 秒。如果在这段时间内没有其他主节点被选举出来那么这个节点可以尝试重新进行选举。如果想让它保持SECONDARY 状态更长或更短的时间则可以自己指定一个以秒为单位的时间。rs.stepDown(600)// 10分钟3.2、阻止选举如果需要对主节点进行一些维护但不想让任何其他符合条件的成员在这段过渡期间成为主节点则可以对每个成员执行 freeze 来强制它们保持为从节点rs.freeze(10000)同样这个命令也接受一个以秒为单位的时间。如果在这段时间之内完成了主节点上的维护并希望释放其他成员则只需在每个成员上再次运行命令将时间指定为 0 秒rs.freeze(0)这样未冻结的成员就可以在需要时进行选举了。也可以运行 rs.freeze(0) 将已经退位的主节点解冻。4、监控复制能够监控副本集的状态是很重要的不仅要监控是否所有成员都已启动还要监控它们所处的状态以及数据的新旧程度。可以使用一些命令来查看副本集信息。包括Atlas、Cloud Manager 和 Ops Manager在内的 MongoDB 托管服务和管理工具也提供了针对复制关键指标的监控机制。与复制相关的故障通常是暂时的比如一台服务器之前无法连接到另一台服务器但现在可以了。查看此类问题最简单的方法就是查看日志。确保自己知道日志的保存位置以及它们确实被保存下来了并且可以访问到它们。4.1、获取状态replSetGetStatus 是一个非常有用的命令它可以获取副本集中每个成员的当前信息从正在运行此命令的成员的视角​。可以在 shell 中使用这个命令的辅助函数rs.status(){set:replset,date:ISODate(2019-11-02T20:02:16.543Z),myState:1,term:NumberLong(1),heartbeatIntervalMillis:NumberLong(2000),optimes:{lastCommittedOpTime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},readConcernMajorityOpTime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},appliedOpTime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},durableOpTime:{ts:Timestamp(1478116934,1),t:NumberLong(1)}},members:[{_id:0,name:m1.example.net:27017,health:1,state:1,stateStr:PRIMARY,uptime:269,optime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},optimeDate:ISODate(2019-11-02T20:02:14Z),infoMessage:could not find member to sync from,electionTime:Timestamp(1478116933,1),electionDate:ISODate(2019-11-02T20:02:13Z),configVersion:1,self:true},{_id:1,name:m2.example.net:27017,health:1,state:2,stateStr:SECONDARY,uptime:14,optime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},optimeDurable:{ts:Timestamp(1478116934,1),t:NumberLong(1)},optimeDate:ISODate(2019-11-02T20:02:14Z),optimeDurableDate:ISODate(2019-11-02T20:02:14Z),lastHeartbeat:ISODate(2019-11-02T20:02:15.618Z),lastHeartbeatRecv:ISODate(2019-11-02T20:02:14.866Z),pingMs:NumberLong(0),syncingTo:m3.example.net:27017,configVersion:1},{_id:2,name:m3.example.net:27017,health:1,state:2,stateStr:SECONDARY,uptime:14,optime:{ts:Timestamp(1478116934,1),t:NumberLong(1)},optimeDurable:{ts:Timestamp(1478116934,1),t:NumberLong(1)},optimeDate:ISODate(2019-11-02T20:02:14Z),optimeDurableDate:ISODate(2019-11-02T20:02:14Z),lastHeartbeat:ISODate(2019-11-02T20:02:15.619Z),lastHeartbeatRecv:ISODate(2019-11-02T20:02:14.787Z),pingMs:NumberLong(0),syncingTo:m1.example.net:27018,configVersion:1}],ok:1}下面是一些最有用的字段。self这个字段只会出现在运行 rs.status() 的成员中。在本例中是 server-1 (m1.example.net:27017)。stateStr描述服务器状态的字符串。请参阅 11.2 节以了解关于各个状态的描述。uptime从成员可被访问一直到现在所经历的秒数或 self成员从服务器端启动到现在的时间。因此server-1 已经启动了 269 秒server-2 和 server-3 已经启动了 14秒。optimeDate每个成员的 oplog 中最后一个操作发生的时间也就是成员被同步到的地方​。注意这是每个成员通过心跳报告上来的状态因此这个时间可能会有几秒的偏差。lastHeartbeat此服务器最后一次收到来自 “self” 这个成员心跳的时间。如果出现了网络故障或服务器一直处于忙碌状态那么这个时间可能是两秒之前。pingMs心跳到达此服务器的平均时间。这用于确定要从哪个成员进行同步。errmsg成员在心跳请求中选择返回的状态消息。这通常仅仅是一些信息而不是错误消息。有几个字段提供的信息是重复的。“state” 与 stateStr相同它仅仅是状态的内部 ID。“health” 只反映了给定的服务器是可访问的1还是不可访问的0​这也可以由 “state” 和 “stateStr” 字段得到。​如果服务器不可访问它们的值会是 UNKNOWN 或 DOWN。​类似地“optime” 和 “optimeDate” 也是相同的只是表示方式不同一种是用从新纪元开始的毫秒数表示的“t” :135…​另一种是用更适合阅读的方式表示的。注意该报告是从运行此命令的副本集成员的角度得出的由于网络问题它包含的信息可能是不正确或者过时的。4.2、可视化复制图谱如果在从节点上运行 rs.status()则会有一个名为syncingTo 的顶级字段。它表示这个成员正在从哪个成员处复制数据。通过在副本集的每个成员上运行replSetGetStatus 命令可以描绘出一个复制图谱。假设 server1 表示一个到 server1 的连接server2 表示一个到 server2 的连接以此类推可以得到如下内容server1.adminCommand({replSetGetStatus:1})[syncingTo]server0:27017server2.adminCommand({replSetGetStatus:1})[syncingTo]server1:27017server3.adminCommand({replSetGetStatus:1})[syncingTo]server1:27017server4.adminCommand({replSetGetStatus:1})[syncingTo]server2:27017因此server0 是 server1 的复制源server1 是 server2和 server3 的复制源server2 是 server4 的复制源。MongoDB 会根据 ping 的时间来决定同步源。当一个成员向另一个成员发送心跳时它会计算请求所花费的时间。MongoDB 维护着这些时间的滑动平均值。当一个成员必须选择与之同步的另一个成员时它会查找离它最近并且数据比它新的成员。​因此不会出现循环复制的问题成员只能从主节点或者数据比它新的从节点处进行复制。​这意味着如果在从节点数据中心添加一个新成员那么它更有可能从该数据中心的另一个成员处而不是主节点数据中心的成员处进行复制这样可以最小化网络流量​如图所示。然而自动复制链automatic replication chaining有一个缺点更多的复制链节点意味着将写操作复制到所有服务器需要更长的时间。假设所有数据都在一个数据中心但是由于添加成员时网络速度的不稳定MongoDB 的复制路径最终会变成一条线如图 所示。这种情况发生的可能性很低但是并非不可能。然而这通常是不可取的复制链中的每个从节点都必须比它“前面”的从节点落后一些。可以使用 replSetSyncFrom 命令或 rs.syncFrom() 辅助函数修改成员的复制源来解决这个问题。连接到想要改变其复制源的从节点并运行这个命令将希望该成员进行同步的服务器传递进去secondary.adminCommand({replSetSyncFrom:server0:27017})切换同步源可能需要几秒如果在该成员上再次运行rs.status()应该可以看到 “syncingTo” 字段现在显示为server0:27017。这个成员server4现在会从 server0 继续进行复制直到 server0 变得不可用或者远远落后于其他成员为止。4.3、复制循环当几个成员彼此进行复制的时候就发生了复制循环例如A 从 B 处进行同步B 从 C 处进行同步C 又从 A处进行同步。由于复制循环中的这些成员没有一个是主节点因此这些成员将不会接收到任何新的操作从而就落在了后面。当成员自动选择同步源时复制循环是不可能发生的。不过使用 replSetSyncFrom 命令可能会强制复制循环发生。在手动更改同步目标之前请仔细检查 rs.status()输出并注意不要造成循环。当选择同步的成员并不比自身领先时replSetSyncFrom 命令会给出警告但仍然允许这样做。4.4、禁用复制链链式复制是指一个从节点从另一个从节点而不是主节点进行同步。如前所述一些成员可以决定自动与其他成员同步。可以禁用复制链通过将 chainingAllowed设置为 false如果没有指定则默认为 true​强制每个成员从主节点进行同步varconfigrs.config()// 如果设置子对象不存在则进行创建config.settingsconfig.settings||{}config.settings.chainingAllowedfalsers.reconfig(config)当 “chainingAllowed” 设置为 false 时所有成员都会从主节点进行同步。如果主节点变得不可用那么它们就会从其他从节点同步数据。4.5、计算延迟对于复制来说需要跟踪的最重要的指标之一就是从节点与主节点之间的延迟情况。延迟lag是指从节点相对于主节点的落后程度也就是主节点执行的最后一个操作的时间戳与从节点应用的最后一个操作的时间戳之间的差值。可以使用 rs.status() 来查看成员的复制状态也可以运行 rs.printReplicationInfo() 或rs.printSlaveReplicationInfo() 来快速地获取一份摘要。rs.printReplicationInfo() 给出了主节点 oplog 的简要信息包括它的大小和操作的日期范围rs.printReplicationInfo();configured oplog size:10.48576MB log length start to end:3590secs(1.00hrs)oplog first event time:Tue Apr10201809:27:57GMT-0400(EDT)oplog last event time:Tue Apr10201810:27:47GMT-0400(EDT)now:Tue Apr10201810:27:47GMT-0400(EDT)在本例中oplog 大约有 10MB (10MiB)只能包含一个小时的操作。在实际的部署中oplog 应该更大。我们希望日志的长度至少和进行一次完整的重新同步所花费的时间一样长。这样就不会遇到从节点在完成初始化同步之前从 oplog 末端脱离的情况。日志长度的计算方法是在 oplog 被填满后取 oplog中第一个操作和最后一个操作之间的时间差。如果服务器刚刚启动oplog 中没有任何内容那么最早的操作会距离现在很近。在这种情况下日志长度会很小即使oplog 可能仍然有可用的空闲空间。对于那些运行时间足够长的服务器来说日志长度是一个非常有用的度量指标因为它们至少一次写满了整个 oplog。也可以使用 rs.printSlaveReplicationInfo() 函数来获取每个成员的 syncedTo 值以及最后一条 oplog 被写入每个从节点的时间如下面的例子所示rs.printSlaveReplicationInfo();source:m1.example.net:27017syncedTo:Tue Apr10201810:27:47GMT-0400(EDT)0secs(0hrs)behind the primarysource:m2.example.net:27017syncedTo:Tue Apr10201810:27:43GMT-0400(EDT)0secs(0hrs)behind the primarysource:m3.example.net:27017syncedTo:Tue Apr10201810:27:39GMT-0400(EDT)0secs(0hrs)behind the primary记住副本集成员的延迟是相对于主节点而不是“墙上时间”计算的。这通常没什么关系但在写入频率非常低的系统中可能会造成延迟过大的幻觉。假设每小时执行一次写入。在写入完成但还没进行复制时从节点看起来会比主节点落后一小时。然而它能够在几毫秒内追上这“一小时”的操作。在监控低吞吐量系统时这有时会造成困惑。4.6、调整oplog大小应该将主节点的 oplog 长度视为维护工作的时间窗口。如果主节点的 oplog 长度是一小时那么就只有一小时的时间来修复所有的问题否则可能会导致从节点落后过多不得不从头开始重新同步。因此你通常会希望oplog 可以保存几天到一周的数据以便在出现问题时给自己一些应对的空间。不幸的是在 oplog 被写满之前没有简单的方法来得出它的长度。WiredTiger 存储引擎允许在服务器端运行时在线调整 oplog 的大小。应该首先在每个从节点成员上执行这些步骤。只有完成了从节点上的变更后才可以对主节点进行更改。记住每个可能成为主节点的服务器都应该拥有足够大的 oplog以便提供足够的时间窗口进行维护。要增加 oplog 的大小请执行以下步骤。连接副本集成员。如果启用了身份验证则要确保使用的用户具有修改 local 数据库的权限。检查 oplog 的当前大小。use localdb.oplog.rs.stats(1024*1024).maxSize这将以 MB 为单位显示集合大小。更改副本集成员的 oplog 的大小。db.adminCommand({replSetResizeOplog:1,size:16000})随后的操作会将副本集成员的 oplog 的大小更改为 16GB也就是 16 000MB。最后如果减少了 oplog 的大小则可能需要运行compact 命令来回收被分配出来的磁盘空间。不要对主节点运行此命令。要获得关于这种场景以及整个过程的更多细节请参阅 MongoDB 文档中关于“更改 oplog 的大小”的教程。一般情况下不应该减小 oplog 的大小即使它可能有几个月那么长但通常总是有足够的磁盘空间来容纳它而且 oplog 不会占用任何有价值的像 RAM 或 CPU 这样的资源。4.7、创建索引如果向主节点发送创建索引的命令那么主节点会正常创建索引然后从节点会在复制“创建索引”这条操作时进行索引的创建。尽管这是最简单的创建索引的方法但索引创建是资源密集型操作可能会导致成员不可用。如果所有从节点同时创建索引那么副本集中的大部分成员将处于离线状态直到索引创建完成。这个过程只适用于副本集。关于如何在分片集群上创建索引请参阅 MongoDB文档中的相关教程。在创建 “unique” 索引时必须停止对集合的所有写操作。如果没有停止写操作那么整个副本集成员的数据可能会不一致。因此你可能希望一次只在一个成员上创建索引以最小化对应用程序的影响。要做到这一点请遵循以下步骤。关闭一个从节点。将其以单机模式重新启动。在单机服务器上创建索引。当索引创建完成后以副本集成员的身份重新启动服务器。重新启动该成员时如果命令行选项或配置文件中存在 disableLogicalSessionCacheRefresh 参数则需要将该参数移除。对副本集中的每个从节点重复步骤 1 到 4。副本集中除了主节点以外的每个成员都成功创建了索引。现在你有两个选择应该根据实际情况选择对生产环境影响最小的那个。在主节点上创建索引。如果系统有一段流量较少的“空闲期”​那么这可能是一个很好的创建索引的时机。你还可能希望修改读偏好以便在创建过程中临时将更多负载分流到从节点。主节点仍然会把索引创建命令复制到从节点但是由于从节点中已经有了这些索引因此这不会产生任何操作。将主节点退位为从节点然后按照前面描述的步骤 2到步骤 4 进行操作。这会发生故障转移但是当旧的主节点正在创建索引时你可以拥有一个正常运行的主节点。在索引创建完成之后可以将其重新添加进副本集。注意还可以使用这种技术在从节点上创建不同的索引。这对于离线处理可能很有用但要确保具有不同索引的成员永远不能成为主节点它的优先级应该始终为 0。如果要创建唯一索引请确保主节点中没有插入重复的数据或者应该首先在主节点上创建索引。否则主节点中可能会插入重复数据这将导致从节点上的复制错误。如果发生这种情况那么从节点会自动关闭。你必须将其作为单机服务器重新启动删除唯一索引然后重新启动它。4.8、在预算有限的情况下进行复制如果预算有限不能购买多台高性能服务器则可以考虑将从节点服务器只用于灾难恢复这样的服务器不需要太大的 RAM 和太好的 CPU也不需要太高的磁盘 I/O。始终将高性能服务器作为主节点比较便宜的服务器不处理任何客户端流量配置客户端将所有读请求发送到主节点​。可以为这样的从节点设置以下选项。priority : 0这个节点永远不会成为主节点。hidden : true客户端不会向这个从节点发送读请求。buildIndexes : false这个选项是可选的但可以大大减少这个节点必须处理的负载。如果需要从该节点进行恢复则需要重新创建索引。votes : 0在只有两台机器的情况下如果将从节点上的votes 设置为 0那么可以在从节点停止运行后让主节点仍能保持为主节点。如果还有第三台服务器即使只是一台应用程序服务器​那么应该在该服务器上运行一个仲裁者成员而不是将 “votes” 设置为 0。这可以为你提供拥有从节点的安全性而不必投资于两台高性能服务器。