MongoDB10-副本集
1、副本集概述
- MongoDB中的副本集是一组mongod进程,它们维护相同的数据集。副本集提供冗余和高可用性,是所有生产部署的基础。
1.1、冗余和数据高可用
- 副本集提供了冗余,提高了数据的可用性。由于在不同的数据库服务器上有多个数据副本,副本集提供了一定程度的容错能力,可以防止单个数据库服务器丢失。
- 在某些情况下,副本集可以增加读容量,因为客户端可以将读操作发送到不同的服务器。还可以为特定的目的维护额外的副本,例如灾难恢复、报告或备份。
1.2、副本集架构
- 一个副本集最多有50个节点。一个副本集最多有7个投票节点,其余节点必须是没有投票权的节点。
- 副本集的最小推荐配置是三个节点:
- 一个主节点和两个从节点。
- 一个主节点、一个从节点和仲裁节点。
1.3、异步复制
- 从节点复制主节点的oplog,并异步地将操作应用到它们的数据集。通过让从节点的数据与主节点保持一致,副本集可以在一个或多个节点发生故障的情况下继续工作。
1、Slow操作
- 从版本4.2开始(从4.0.6开始也可以使用),副本集的从节点会记录耗时超过慢操作阈值的oplog条目。这些缓慢的oplog消息:
- 记录在诊断日志。
- 是否记录在REPL组件下的文本(applied op: <oplog entry> took <num>ms)
- 不依赖于日志级别(在系统或组件级别)
- 不要依赖分析级别。
- 可能会受到影响slowOpSampleRate,取决于MongoDB版本:
- 在MongoDB 4.2和更早的版本中,这些慢的oplog条目不受slowOpSampleRate的影响。MongoDB记录所有慢的oplog条目,而不考虑采样率。
- 在MongoDB 4.4及更高版本中,这些慢的oplog条目会受到slowOpSampleRate的影响。
- 分析器不捕获慢的日志记录条目。
2、复制延迟和流量控制
- 复制延迟指是将主节点上的写操作复制到从节点上所需的时间。一些小延迟是可以接受的,但是随着复制延迟的增加,会出现严重的问题,包括在主节点上构建缓存压力。
- 从MongoDB 4.2开始,管理员可以限制主节点写操作的速率,目的是将延迟保持在最大值flowControlTargetLagSeconds(可配置)以下。默认情况下,流量控制处于开启状态。
- 注意,要进行流控制,副本集/分片集群必须具有:featureCompatibilityVersion (FCV) 4.2,并启用读关注多数。也就是说,如果FCV不是4.2,或者读关注多数被禁用,则启用的流控制没有效果。
- 启用了流控制后,当延迟增长到接近flowControlTargetLagSeconds时,主节点上的写操作必须先获取票据,然后才能进行写操作。通过限制每秒发出的票据数量,流量控制机制试图将延迟保持在目标以下。
1.4、读操作
1、读偏好
- 默认情况下,客户端从主节点读取。但是,客户端可以指定一个读偏好将读操作发送到从节点。
- 异步复制到从节点意味着从从节点读取的数据可能与主节点不一致。
- 如果多文件交易(Multi-document transactions)包含读取操作,必须将读偏好设置为primary。事务中的所有操作必须路由到同一个节点上。
- 读偏好的模式有:
- primary模式:
- 所有读操作都只使用当前的主节点(默认的读偏好)。如果主节点不可用,则读操作会产生错误或抛出异常。
- primary模式与使用标记集列表或maxStalenessSeconds的读偏好模式不兼容。如果使用primary指定标记集列表或maxStalenessSeconds值,驱动程序将产生错误。
- 包含读操作的多文档事务必须使用primary模式。给定事务中的所有操作都必须路由到相同的成员。
- primaryPreferred模式:
- 在大多数情况下,读操作只使用当前的主节点。但是,如果主节点不可用(就像故障转移情况下的情况一样),则从满足读优先级的maxStalenessSeconds和标记集列表的从节点中读取操作。
- 当primaryPreferred读首选项包含maxStalenessSeconds值且没有要读的主节点时,客户端通过比较从节点与主节点的最近一次写操作来估计每个从节点的延时程度。然后,客户端将读操作定向到一个延迟小于或等于maxStalenessSeconds的从节点。
- 当读取首选项包含标记集列表(标记集数组)且没有要读取的主节点时,客户端将尝试查找具有匹配标记的从节点(按顺序尝试标记集,直到找到匹配的标记)。如果找到匹配的从节点,则客户端从最近的匹配从节点组中随机选择一个从节点。如果没有匹配标记的从节点,则读取操作将产生错误。
- 当读取首选项包含maxStalenessSeconds值和标记集列表时,客户端首先根据过时度进行过滤,然后根据指定的标记进行过滤。
- 使用primaryPreferred模式的读操作可能会返回陈旧的数据。使用maxStalenessSeconds选项来避免从客户端估计的过于陈旧的辅助端读取数据。
- secondary模式:
- 仅从副本集的从节点中读取的操作。如果没有可用的从节点,则此读取操作将产生错误或异常。
- 大多数副本集至少有一个从节点,但在某些情况下可能没有可用的从节点。例如,如果一个节点处于恢复状态或不可用,则具有主节点、从节点和仲裁节点的副本集可能没有任何从节点。
- 当从节点读优先级包含maxStalenessSeconds值时,客户端通过比较从节点与主节点最近一次写操作来估计每个从节点的延时程度。然后,客户端将读操作定向到一个延迟小于或等于maxStalenessSeconds的从节点。如果没有主节点,则客户端使用最近写入的从节点进行比较。
- 当读首选项包含标记集列表(标记集数组)时,客户端尝试查找具有匹配标记的从节点(按顺序尝试标记集,直到找到匹配)。如果找到匹配的从节点,则客户端从最近的匹配从节点组中随机选择一个从节点。如果没有匹配标记的从节点,则读取操作将产生错误。
- 当读取首选项包含maxStalenessSeconds值和标记集列表时,客户端首先根据过时度进行过滤,然后根据指定的标记进行过滤。
- 使用secondary模式的读操作可能会返回陈旧的数据。使用maxStalenessSeconds选项来避免从客户端估计的过于陈旧的辅助端读取数据。
- secondaryPreferred模式:
- 在大多数情况下,读操作只使用从节点。但在集合只有一个主节点(而没有其他节点)的情况下,读取操作将使用副本集的主节点。
- 当secondaryPreferred读首选项包含maxStalenessSeconds值时,客户端通过比较从节点与主节点最近一次写操作来估计每个从节点的延时程度。然后,客户端将读操作定向到一个延迟小于或等于maxStalenessSeconds的从节点。如果没有主节点,则客户端使用最近写入的从节点进行比较。如果没有估计滞后小于或等于maxStalenessSeconds的从节点,客户端将读取操作引导到副本集的主节点。
- 当读首选项包含标记集列表(标记集数组)时,客户端尝试查找具有匹配标记的从节点(按顺序尝试标记集,直到找到匹配)。如果找到匹配的从节点,则客户端从最近的匹配从节点组中随机选择一个从节点。如果没有从节点具有匹配的标记,则客户端忽略标记并从主节点读取。
- 当读取首选项包含maxStalenessSeconds值和标记集列表时,客户端首先根据过时度进行过滤,然后根据指定的标记进行过滤。
- 使用secondaryPreferred模式的读操作可能会返回陈旧的数据。使用maxStalenessSeconds选项来避免从客户端估计的过于陈旧的辅助端读取数据。
- nearest模式:
- 驱动程序从网络延迟在可接受延迟窗口内的节点读取。在路由读操作时,最接近模式的读不考虑节点是主节点还是从节点:主节点和从节点被等同对待。
- 设置此模式可将网络延迟对读取操作的影响最小化,而不优先考虑当前或过时的数据。
- 当读首选项包含maxStalenessSeconds值时,客户端通过比较从节点的最近一次写入(如果有主服务器的话)和从节点的最近一次写入(如果没有主服务器的话)来估计每个从节点的过时程度。然后,客户端将过滤掉估计延迟大于maxStalenessSeconds的任何从设备,并随机将读操作定向到网络延迟在可接受的延迟窗口内的剩余节点(主节点或从节点)。
- 如果指定标记集列表,客户端将尝试查找与指定标记集列表匹配的副本集节点,并将读取定向到最近组中的任意节点。
- 当读取首选项包含maxStalenessSeconds值和标记集列表时,客户端首先根据过时度进行过滤,然后根据指定的标记进行过滤。然后,客户端从剩余的mongod实例随机地将读操作定向到位于可接受的延迟窗口内的实例。读取首选项节点选择文档详细描述了该过程。
- 使用最近模式的读操作可能会返回陈旧的数据。使用maxStalenessSeconds选项来避免从客户端估计的过于陈旧的从端读取数据。
- primary模式:
2、数据的可见性
- 根据读关注,客户端可以在写操作被确认前看到写操作的结果:
- 不管写操作的写关注点是什么,只要客户端使用"local"或者"available"读关注,那么就可以在写操作确认前看到写操作的结果。
- 客户端使用"local"或者"available"读关注读取的数据,这些数据可能在副本集故障转移期间被回滚。
- 对于多文档事务中的操作,当事务提交时,将保存事务中所做的所有数据更改,并且在事务外部可见。也就是说,事务不会在回滚其他更改时提交其中的一些更改。
- 在事务提交之前,事务中所做的数据更改在事务外部是不可见的。
- 但是,当一个事务写入多个分片时,并非所有外部读取操作都需要等待已提交事务的结果在各个分片中可见。例如,如果事务已提交并且写入1在分片A上可见,但写入2在分片B上尚不可见,则外部读取关注"local"可以读取write 1的结果,而不会看到write 2。
1.5、写关注
- 写关注是写操作成功返回之前必须确认写操作的数据承载节点(即主节点和从节点,但不是仲裁者)的数量。节点只有在成功接收并应用写操作后才能确认写操作。
- 写关注是w: "majority",要求大多数投票节点确认该写操作。默认的写关注。
- 对于节点启用了日志记录的集群,将“majority”写关注与j: true结合起来可以防止回滚写关注确认的数据。
- 写关注是w: 1,只要求主节点确认该写操作即可。
- 如果大于1,要求主节点和尽可能多的从节点进行确认,以满足指定的值,直到副本集中承载数据的节点的总数。
- 发出写操作的应用程序需要对写关注进行确认,它会一直等待,直到主节点收到所需数量的确认。
- 使用insertOne()函数的writeConcern参数
//该操作指定:-"majority"写关注,以及—5秒超时。 db.products.insertOne( { item: "envelopes", qty : 100, type: "Clasp" }, { writeConcern: { w: "majority" , wtimeout: 5000 } } )
2、副本集成员
- 副本集的成员有:
- Primary(主节点):主节点接收所有写操作。
- Secondaries(从节点):从节点从主节点复制操作,以维护相同的数据集。
- 还有三种特殊的从节点:优先级为0的从节点、隐藏从节点、延迟从节点。
- Arbiter(仲裁节点):当法定人数不是奇数,又不想添加一个从节点,可以添加一个仲裁节点。仲裁节点只参与选举,但不持有数据。
2.1、主节点
- 主节点接收所有写操作,能够确认与{w: "majority"}写相关的写操作。主节点将所有写操作(更改数据集)记录在操作日志(即oplog)中。
- 副本集的所有节点都可以接收读操作。但是,默认情况下,应用程序将读操作定向到主节点。
- 一个副本集只能有一个主节点。如果当前主节点不可用,那么有资格的从节点将举行选举,重新选出新的主节点。
- 在某些情况下,副本集中可能出现有两个节点都认为自己是主节点,但最多只有一个节点能够完成{w: "majority"}写操作。能够完成{w: "majority"}写操作的节点是当前的主节点,另一个节点是尚未意识到降级的前主节点,这通常是由于网络分区造成的。当出现这种情况时,连接到前主节点的客户端可能会观察到过时的数据,尽管已经请求了读首选项主节点,对前主节点的新写操作最终会回滚。
2.2、从节点
- 从节点维护主节点的数据集副本。为了复制数据,从节点复制主节点的oplog,然后使用异步进程将oplog中的操作应用到自己的数据集,以便从节点的数据集与主节点的保持一致。
- 从节点不接收写操作,但可以接受读操作。
- 次节点可以变成主节点。如果当前主节点不可用,副本集将进行一次选举,在从节点中选一个做主节点。
- 一个副本集可以有一个或多个从节点。
2.2.1、优先级为0的从节点
- 优先级为0的从节点的限制:
- 优先级为0的从节点不能成为主节点,也不能触发选举。
- 优先级为0的从节点可以确认与w : <number>写相关的写操作。
- 对于“多数”写关注,优先级为0的从节点也必须是有表决权的节点(members[n].votes大于0),以确认该写操作。
- 非投从节点(members[n].votes为0)不能对具有“多数”写关注的写操作进行确认。
- 除了上述限制之外,优先级为0的从节点与普通从节点相同:它们维护数据集的副本,接受读操作,并在选举中投票。
- 如果要部署一个距离(地理)主节点较远的从节点,因为可能具有较高的延迟,则可能需要将它的优先级设置为0。这样,它可以很好地服务于本地读请求,但由于其延迟,它可能不是成为主节点的理想对象。
- 如图所示,左边的数据中心托管主节点和从节点,右边的数据中心托管从节点,右边从节点的优先级被配置为0,以防止它成为主节点。由于这种设置,只有左侧数据中心中的节点才有资格成为选举中的主节点。
- 优先级为0的从节点可以作为备用节点。在一些副本集中,不可能在合理的时间内添加一个新节点。备用节点有数据集的副本,可以替换不可用节点。
- 如果副本集中有优先级为0的从节点,在故障转移时要始终确保主数据中心既包含法定人数的投票节点,又包含有资格成为主节点的节点。
2.2.2、隐藏从节点
- 隐藏从节点维护主节点数据集的副本,但对客户端应用程序不可见。
- 隐藏从节点必须是优先级为0的从节点,因此不能成为主节点。db.hello()方法不显示隐藏从节点。
- 隐藏从节点可以在选举中投票。
- 如图所示,下面副本集中有五个节点,四个从节点都有主节点数据集的副本,但其中一个从节点是隐藏的。
- 客户端的使用读偏好时,不会将读操作分配给隐藏从节点。因此,除了基本复制之外,隐藏从节点不会收到任何流量。隐藏从节点是用于报告和备份等专门任务。
- 出于备份的目的:
- db.fsyncLock()确保使用低级备份实用程序(如cp、scp或tar)安全地复制数据文件。使用复制文件启动的mongod包含用户写入的数据,这些数据与锁定的mongod上的用户写入的数据没有区别。
- 由于日志同步或WiredTiger快照等操作,锁定的mongod的数据文件可能会更改。虽然这对逻辑数据(例如客户机访问的数据)没有影响,但一些备份实用程序可能检测到这些更改并发出警告或失败。有关MongoDB的更多信息—推荐的备份实用程序和过程。
- 写问题:
- 隐藏从节点可以确认与w : <number>写相关的写操作。
- 对于“多数”写关注,隐藏从节点也必须是有表决权的节点(members[n].votes大于0),以确认该写操作。
- 非投票从节点(members[n].votes为0)不能对具有“多数”写关注的写操作进行确认。
2.2.3、延迟从节点
- 延迟从节点包含副本集的数据集的副本。延迟从节点的数据集是主节点的更早或延迟的状态。例如,当前时间为09:52,某个延迟从节点有1小时的延迟,那么该节点在08:52之后的操作还没有执行。
- 因为延迟从节点是数据集的“滚动备份”或运行的“历史”快照,所以它们可以从各种人为错误中恢复。例如,延迟从节点可以从不成功的应用程序升级和操作员错误(包括数据库和集合丢失)中恢复。
- 延迟从节点:
- 优先级必须是0。将优先级设置为0,可以防止延迟从节点成为主节点。
- 必须是隐藏从节点。防止应用程序看到和查询延迟从节点。
- 将members[n].votes设置为1,延迟从节点参与选举。将members[n].votes设置为0,延迟从节点不参与选举,有助于提高性能。
- 如果副本集包含延迟从节点,请确保延迟从节点是隐藏的和不参与选举。
- 隐藏延迟从节点可以防止客户端应用程序在没有直接连接到该节点的情况下看到和查询延迟数据。
- 将延迟从节点设置为不投票意味着它们将不会被计入具有写关注“多数”的写操作。
- 例如,考虑Primary-Secondary-Delayed副本集配置,其中延迟的secondary以10分钟的延迟进行投票。
- 由于有一个非延迟的从节点不可用,Primary-Delayed的降级配置必须至少等待10分钟才能确认具有“majority”的写操作。大多数提交点将需要更长的时间,导致缓存压力类似于Primary、Secondary和仲裁者(PSA)副本集的性能问题。
- 延迟从节点从主节点复制oplog和应用。在选择延迟量时,需要考虑延迟量:
- 必须等于或大于预期的维护窗口时间。
- 必须小于oplog的容量。
- 写问题:
- 延迟从节点可以确认与w : <number>写相关的写操作。
- 对于“多数”写关注,延迟从节点也必须是有表决权的节点(members[n].votes大于0),以确认该写操作。
- 非投票从节点(members[n].votes为0)不能对具有“多数”写关注的写操作进行确认。
- 在分片集群中,当均衡器启用时,延迟从节点的效用有限。因为延迟从节点在复制数据块迁移时会有延迟,所以如果在延迟窗口内发生任何迁移,分片集群中延迟从节点的状态无法帮助恢复到分片集群的前一个状态。
- 延迟从节点的配置:
//延迟从节点的priority(优先级)等于0,hidden(隐藏)的等于true,secondaryDelaySecs是延迟的秒数 { "_id" : <num>, "host" : <hostname:port>, "priority" : 0, "secondaryDelaySecs" : <seconds>, "hidden" : true }
- 如图所示,下面副本集中有五个节点,四个从节点都有主节点数据集的副本,但其中一个从节点是延迟的。
2.3、仲裁节点
- 在某些情况下(比如有一个主节点和一个从节点,但由于成本限制,不允许添加另一个从节点),可以将一个mongod实例添加到副本集作为仲裁节点。仲裁节点只参与选举,但不持有数据(即不提供数据冗余)。
- 仲裁节点永远都是仲裁节点,而主节点可能会变成从节点,从节点可能在选举中成为主节点。
- 不要在副本集的主节点或从节点的服务器上运行仲裁者。
- 仲裁节点刚好有1票选举票。缺省情况下,仲裁节点的优先级为0。
- 如图所示,下面副本集中有2个承载数据的节点(主和从),再加一个仲裁节点到该副本集使其拥有奇数票数来打破平局。
3、操作日志Oplog
- oplog(操作日志)就是一个固定大小的集合,它保存了主节点修改数据集的所有操作。
- 主节点修改数据集的所有操作都会保存到它的oplog中,从节点使用异步进程复制主节点的oplog并应用其中的操作,从而保证与主节点的数据集处于一致状态。
- 副本集中的所有节点都有一个oplog副本,在local.oplog.rs集合中。
- 通常,从节点从主节点同步数据。但它们也可以从其他从节点同步数据。通过这种方式,所有从节点将组成一个“同步链”,每个节点都可以从副本集的其他从节点同步最新数据。这种行为在具有多数据中心的设置中尤其有用,可以节省复制的带宽成本,因为只有一个节点需要在数据中心之间复制。副本集链在MongoDB中是默认行为,设置chainingAllowed:false,可以改变这种行为。
- oplog中的每个操作都是幂等的。也就是说,无论对目标数据集应用一次还是多次,oplog操作都会产生相同的结果。
1、Oplog的大小
- 启动节点时,如果没有指定oplog大小,那么将创建一个默认大小的oplog。oplog的默认大小取决于存储引擎:
- In-Memory存储引擎:物理内存的%5。下限是50MB,上限是50GB。
- WiredTiger存储引擎:可用磁盘空间是5%。下限是990MB,上限是50GB。
- 在大多数情况下,默认的oplog大小就足够了。例如,如果oplog的大小是空闲磁盘空间的5%,可能需要24小时才能将这5%磁盘空间使用完,那么从节点可以停止复制oplog的时间,最长可达24小时。
- 在启动节点时,可以使用oplogSizeMB选项设置它的大小。
- 在启动节点后,可以使用replSetResizeOplog管理命令动态更改oplog的大小,无需重新启动mongod进程。
- 从MongoDB 4.0开始,与其他固定大小的集合不同,oplog可以超过其配置的大小限制,以避免删除大多数提交点。
2、Oplog最小保留时间
- 从MongoDB 4.4开始,支持以小时为单位的最小日志保留周期,其中MongoDB只在以下情况下删除日志项:
- oplog已达到配置的最大大小,并且日志条目的时间比配置的小时数早。
- 默认情况下,MongoDB不设置最小日志保存周期。
- 启动mongod时,配置Oplog的最小保留时间的方法有两种:
- (1)在启动命令行中使用--oplogMinRetentionHours命令参数。
- (2)在配置文件中设置oplogMinRetentionHours。
- 在运行MongoDB时,使用replSetResizeOplog命令动态更改Oplog的最小的保持时间,将覆盖启动时设置的值。
3、Oplog窗口
- Oplog的每个条目都有一个时间戳。
- oplog窗口是oplog中最新和最早条目的时间戳之间的时间差。如果一个从节点失去了与主节点的连接,当它恢复连接时它只能在oplog窗口内进行复制。
- 如果可以预测副本集的工作负载类似于以下模式之一,那么可能需要创建一个比默认值更大的oplog。相反,如果主要是读操作,写操作的数量最少,那么较小的oplog可能就足够了。
- 一次更新多个文档:为了保持幂等性,oplog必须将多个更新转换为单个操作。这可能会使用大量的oplog空间,而不会相应增加数据大小或磁盘使用量。
- 删除与插入的数据量大致相等:数据库的磁盘使用量不会显著增加,但oplog的大小可能相当大。
- 大量的就地更新:如果工作负载的很大一部分是不增加文档大小的更新,则oplog将记录大量操作,但不会改变磁盘上的数据量。
4、查看Oplog状态
- 可以使用rs.printReplicationInfo()函数查看oplog的状态,包括操作的大小和时间范围。
- 在各种特殊情况下,对从节点的oplog的更新可能会滞后于所需的性能时间。可以使用db.getReplicationInfo()来评估当前的复制状态,并确定是否存在任何意外的复制延迟。
5、Oplog收集行为
- 如果是WiredTiger存储引擎,不能从任何副本集节点中删除local.oplog.rs集合。
- 从v4.2开始,不能从独立的MongoDB实例中删除local.oplog.rs集合,建议不要从独立的MongoDB v4.0实例中删除该集合。
- 从MongoDB 5.0开始,在副本集上不能对oplog执行手动写操作。
4、同步数据
- MongoDB进行数据同步有两步:
- (1)初始同步:新节点同步完整的数据集。
- (2)复制:初始同步后,从节点复制源oplog,并使应用这些操作。。
4.1、初始同步
- 初始同步将所有的数据从副本集的一个节点复制到另一个节点。
1、初始同步源的选择
- 从MongoDB 4.4开始,可以使用initialSyncSourceReadPreference参数指定初始同步源。只能在启动时设置此参数,使用setParameter配置文件设置或--setParameter命令行参数。
- 初始同步源可以使用以下读偏好之一:
- primary
- primaryPreferred (可以投票的节点的默认值)
- secondary
- secondaryPreferred
- nearest (默认为新添加的或非投票副本集成员)
//命令行参数 --setParameter initialSyncSourceReadPreference=secondary //配置文件 setParameter: initialSyncSourceReadPreference: secondary
- 初始同步源选择取决于mongod启动参数initialSyncSourceReadPreference的值(4.4中新增):
- 如果将initialSyncSourceReadPreference设置为primary(如果禁用链接则默认),则选择主节点作为同步源。如果主节点不可用或不可达,记录错误并定期检查主节点是否可用。
- 对于将initialSyncSourceReadPreference设置为primaryPreferred(投票副本集成员的默认值),尝试选择主节点作为同步源。如果主节点不可用或不可达,则从剩余的副本集节点中执行同步源选择。
- 对于所有其他支持的读模式,从副本集节点中选择同步源。
- 在筛选同步源时,可能会对副本集中的节点进行两次遍历:
- 第一次筛选初始同步源时,使用以下条件对副本集中的节点进行遍历:
- 同步源的复制状态必须为PRIMARY或者SECONDARY。
- 同步源必须在线且可访问。
- 如果initialSyncSourceReadPreference是secondary或secondaryPreferred时,表示同步源必须是从节点。
- 同步源必须是可见的。
- 同步源的延迟必须在30秒内(与主节点相比)。
- 如果该节点创建索引,同步源必须创建索引。
- 如果该节点在副本集选举中可以投票,同步源也必须可以投票。
- 如果该节点不是延迟节点,则同步源不能是被延迟节点。
- 如果该节点为延迟节点,则同步源配置的延迟时间必须更短。
- 同步源必须比其他更快(即更低的延迟)。
- 第二次筛选初始同步源时,使用以下条件对副本集中的节点进行遍历:
- 同步源的复制状态必须为PRIMARY或者SECONDARY。
- 同步源必须在线且可访问。
- 如果initialSyncSourceReadPreference是secondary时,表示同步源必须是从节点。
- 当“initialSyncSourceReadPreference”为“secondary”时,表示同步源必须是从节点。
- 如果该节点创建索引,同步源必须创建索引。
- 同步源必须比其他更快(即更低的延迟)。
- 如果该节点两次遍历都不能选择出同步源,它将记录一个错误,并等待1秒,然后重新启动选择过程。可以在错误退出前重启初始同步源选择过程10次(最多)。
- 第一次筛选初始同步源时,使用以下条件对副本集中的节点进行遍历:
2、初始同步的过程
- 克隆除local数据库外的所有数据库。为了进行克隆,mongod扫描每个源数据库中的每个集合,并将所有数据同步自身。
- 在3.4版更改:在为每个集合复制文档时,初始同步构建所有集合索引。在MongoDB的早期版本中,这个阶段只构建_id索引。
- 在3.4版更改:初始同步在数据复制期间提取新添加的oplog记录。确保有足够的磁盘空间,以便在此数据复制阶段期间临时存储这些oplog记录。
- 将所有更改应用到数据集。使用源oplog更新数据集,以反映副本集的当前状态。
- 初始同步完成后,成员从STARTUP2过渡到SECONDARY。
3、初始同步的容错
- 如果正在执行初始同步的从节点在同步过程中遇到非短暂的(即持久的)网络错误,从节点将重新启动初始同步进程(从头开始同步)。
- 从MongoDB 4.4开始,执行初始同步的从节点可以被瞬时(即临时)网络错误、集合丢失或集合重命名中断时尝试恢复同步进程。同步源也必须是MongoDB 4.4。如果同步源是MongoDB 4.2或更早版本,从节点必须重新启动初始同步进程,就像它遇到了一个非短暂的网络错误一样。
- 缺省情况下,从节点完成初始同步的时间为24小时。MongoDB 4.4增加了initialSyncTransientErrorRetryPeriodSeconds服务器参数,用于控制从节点完成初始同步的时间。如果从节点在配置的时间段内无法成功完成初始同步进程,它将从副本集中选择一个新的健康源,并从头重新启动初始同步进程。
- 在返回致命错误之前,从节点会尝试重启初始同步10次。
4.2、复制
- 从节点在初始同步后持续复制数据。从节点从其源同步中复制oplog,并使用异步进程中应用这些操作。
- 从MongoDB 4.4开始,从同步源发送一个连续的oplog条到从节点。流复制可以减轻高负载和高延迟网络中的复制延迟。它还:
- 减少从从节点读取的陈旧性。
- 降低由故障转移而丢失写操作的风险。
- 减少写操作的延迟(即需要等待复制的任何写操作)。
5、高可用性
副本集使用从节点投票选举主节点来支持高可用性。
5.1、选举主节点
- 当主节点不可用时,副本集中的从节点会自动选举出新的主节点。
- 可以触发选举的事件有:
- 添加一个新节点
- 初始化副本集
- 使用rs.stepDown()或rs.reconfig()等方法维护副本集
- 从节点与主节点的连接中断时间超过了配置的超时时间(默认为10秒)。
5.1.1、自动故障转移
- 当一个主节点与该副本集中的其他节点之间的通信时间超过electionTimeoutMillis(默认为10秒,可配置)时,符合条件的从节点要求进行选举并提名自己为新的主节点。集群尝试完成新主节点的选举并恢复正常操作。
- 在选举成功完成之前,副本集无法处理写操作。如果将读操作配置为在主节点脱机时可以在从节点上运行,则副本集可以继续提供读操作。
- 默认情况下,集群选择新主节点的的时间中位数通常不应该超过12秒。这包括主节点被要标记为不可用、调用选举和完成选举。可以通过修改settings.electionTimeoutMillis选项来优化这个时间。网络延迟等因素可能会延长完成副本集选举所需的时间,这反过来又会影响集群在没有主节点的情况下运行的时间。
- 降低electionTimeoutMillis(默认值是10000,10秒)可以更快地检测到主节点故障。但是,由于临时网络延迟等因素,集群可能更频繁地调用选举,即使主节点是健康的。这可能导致增加写操作(w: 1)的回滚。
- 在应用程序连接逻辑中应该包括对自动故障转移和后续选举的容忍。从MongoDB 3.6开始,MongoDB驱动程序可以检测到主节点丢失并自动重试某些写操作,提供了额外的内置自动故障转移和选举处理:
- MongoDB 4.2+兼容驱动默认启用可重试写入
- MongoDB 4.0和3.6兼容的驱动程序必须通过在连接字符串中包含retryWrites=true来显式启用可重试写。
5.1.2、影响选举的因素和条件
1、选举协议
- MongoDB 4.0移除已废弃的选举协议protocolVersion 0。
- protocolVersion: 1协议减少了副本集故障转移时间,加速了多个同时发生的主节点的检测。
- protocolVersion: 1协议可以使用catchUpTimeoutMillis在更快的故障转移和w:1写入之间设置优先级。
2、心跳
- 副本集节点每两秒互相发送一次心跳(ping)。如果心跳在10秒内没有返回,其他节点将该逾期节点标记为不可访问。
3、节点优先级
- 在副本集拥有稳定的主节点之后,选举算法会“尽最大努力”让具有最高优先级的从节点进行选举。节点优先级影响选举的时机和结果,优先级较高的从节点比优先级较低的从节点选举时间相对早,而且也更有可能获胜。
- 优先级为0的从节点不能成为主节点,也不能触发选举。
5.1.3、投票节点
- 副本集中的节点是否参与投票是由members[n].votes配置和自身状态决定节的。
- 无投票权(即votes是0)的节点,优先级必须是0
- 有投票权(即votes不是0)的节点,优先级必须大于0。
- 只有下节点有资格投票:
- PRIMARY
- SECONDARY
- STARTUP2(除非该成员是新添加到副本集中的)
- RECOVERING
- ARBITER
- ROLLBACK
- 一个副本集最多有50个节点。一个副本集最多有7个投票节点,其余节点必须是没有投票权的节点。
5.2、故障转移期间的回滚
- 当节点在故障转移后重新加入副本集时,回滚将恢复前主节点上的写操作。
- 当前主节点在故障转移后重新加入其副本集时,将回滚恢复前主节点上的写操作。只有当主节点接受了从节点在主节点退出前没有成功复制的写操作时,才需要回滚。当前主节点作为从节点重新加入集合时,它将还原(或“回滚”)其写操作,以保持与其他节点的一致性。
- MongoDB会尽量避免回滚。发生回滚通常是因为网络分区。从节点无法跟上前主节点上操作的吞吐量,就会增加回滚的大小和影响。
- 如果一个写操作在主节点降级之前复制到另一个节点上,且该节点仍然可用,并且大部分副本集中的节点都可以访问该节点,就不会发生回滚。
5.2.1、收集回滚数据
- 从4.0版本开始,MongoDB增加了参数createRollbackDataFiles控制是否在回滚期间创建回滚文件。
- 默认情况下,当发生回滚时,MongoDB将回滚数据写入BSON文件。
- 回滚目录更改
- 从Mongo4.4开始,集合的回滚目录以集合的UUID命名,而不是集合的命名空间。
- 对于每个回滚数据的集合,回滚文件位于<dbpath>/rollback/<collectionUUID>目录并具有如下形式的文件名:
- removed.<timestamp>.bson
- 可以使用bsondump读取回滚文件中的内容。
- 如果回滚操作是删除集合或文档,不会将删除集合或文档的回滚写入回滚文件中。
5.2.2、回滚的时间限制
- 从4.0版本开始,回滚时间限制默认为24小时,可以使用参数rollbackTimeLimitSecs进行配置:
- 在MongoDB 4.2+和4.0.13+中,回滚时间限制计算在公共点后的第一个操作到oplog中成员的最后一个回滚点之间。
- 在MongoDB 4.0.0-4.0.12中,回滚时间限制计算在成员回滚的公共点和oplog中最后一个点之间。
- 在MongoDB 3.6及更早版本中,回滚时间限制是不可配置的。对于这些版本,回滚受到数据量的限制,最多为300兆字节。
6、实现副本集
6.1、部署环境
- 软件版本
- mongodb-org-server-5.0.11
- 系统环境
- 10.1.1.11:CentOSLinuxrelease7.7.1908(Core)
- 10.1.1.12:CentOSLinuxrelease7.7.1908(Core)
- 10.1.1.13:CentOSLinuxrelease7.7.1908(Core)
6.2、准备三个节点
- 在三台机器上分别安装mongodb。
- https://www.cnblogs.com/maiblogs/p/16656465.html
- 修改mongodb配置文件(注意,其他与单机的一样)
- 副本集中的所有节点必须具有相同的副本集名称。
//以10.1.1.11为例 ]# vim /etc/mongod.conf systemLog: path: /apps/mongodb/logs/mongod.log storage: dbPath: /apps/mongodb/data/ directoryPerDB: true net: bindIp: 10.1.1.11 #副本集名称是rs1 replication: replSetName: rs1 ...
- 修改后的配置文件

cat > /etc/mongod.conf << EOF systemLog: destination: file logAppend: true path: /apps/mongodb/logs/mongod.log storage: dbPath: /apps/mongodb/data/ journal: enabled: true directoryPerDB: true processManagement: fork: true pidFilePath: /var/run/mongodb/mongod.pid timeZoneInfo: /usr/share/zoneinfo net: port: 27017 bindIp: 10.1.1.11 replication: replSetName: rs1 EOF
- 启动副本集节点
systemctl enable mongod systemctl start mongod systemctl status mongod
6.3、配置副本集
- 要搭建的架构如图所示:
1、初始化第一个节点
- 初始化的第一个节点将成为该副本集的主节点。
//链接到一个节点 ]# mongosh --host 10.1.1.11:27017 //初始化副本集中的第一个节点(会成为主节点) rs.initiate() //查看副本集的状态 rs.status()
2、添加从节点
- 默认添加的从节点是普通的从节点。
//添加一个从节点() rs.add("10.1.1.12:27017") //添加一个仲裁节点 rs.addArb("10.1.1.13:27017") //查看副本集的状态 rs.status()
3、添加仲裁节点
//添加一个仲裁节点 rs.addArb("10.1.1.13:27017") //查看副本集的状态 rs.status()
- 问题:添加仲裁节点时报错,MongoServerError: Reconfig attempted to install a config that would change the implicit default write concern. Use the setDefaultRWConcern command to set a cluster-wide write concern and try the reconfig again.
//解决该问题 db.adminCommand({ "setDefaultRWConcern" : 1, "defaultWriteConcern" : { "w" : 1 } })
6.4、查看副本集信息
1、查看副本集状态信息

rs1 [direct: other] test> rs.status() { set: 'rs1', date: ISODate("2022-09-27T13:07:13.543Z"), myState: 1, term: Long("1"), syncSourceHost: '', syncSourceId: -1, heartbeatIntervalMillis: Long("2000"), majorityVoteCount: 2, writeMajorityCount: 2, votingMembersCount: 3, writableVotingMembersCount: 2, optimes: { lastCommittedOpTime: { ts: Timestamp({ t: 1664284032, i: 1 }), t: Long("1") }, lastCommittedWallTime: ISODate("2022-09-27T13:07:12.920Z"), readConcernMajorityOpTime: { ts: Timestamp({ t: 1664284032, i: 1 }), t: Long("1") }, appliedOpTime: { ts: Timestamp({ t: 1664284032, i: 1 }), t: Long("1") }, durableOpTime: { ts: Timestamp({ t: 1664284032, i: 1 }), t: Long("1") }, lastAppliedWallTime: ISODate("2022-09-27T13:07:12.920Z"), lastDurableWallTime: ISODate("2022-09-27T13:07:12.920Z") }, lastStableRecoveryTimestamp: Timestamp({ t: 1664283992, i: 1 }), electionCandidateMetrics: { lastElectionReason: 'electionTimeout', lastElectionDate: ISODate("2022-09-27T12:59:42.798Z"), electionTerm: Long("1"), lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1664283582, i: 1 }), t: Long("-1") }, lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1664283582, i: 1 }), t: Long("-1") }, numVotesNeeded: 1, priorityAtElection: 1, electionTimeoutMillis: Long("10000"), newTermStartDate: ISODate("2022-09-27T12:59:42.815Z"), wMajorityWriteAvailabilityDate: ISODate("2022-09-27T12:59:42.822Z") }, members: [ { _id: 0, name: '10.1.1.11:27017', health: 1, state: 1, stateStr: 'PRIMARY', uptime: 736, optime: { ts: Timestamp({ t: 1664284032, i: 1 }), t: Long("1") }, optimeDate: ISODate("2022-09-27T13:07:12.000Z"), lastAppliedWallTime: ISODate("2022-09-27T13:07:12.920Z"), lastDurableWallTime: ISODate("2022-09-27T13:07:12.920Z"), syncSourceHost: '', syncSourceId: -1, infoMessage: '', electionTime: Timestamp({ t: 1664283582, i: 2 }), electionDate: ISODate("2022-09-27T12:59:42.000Z"), configVersion: 4, configTerm: 1, self: true, lastHeartbeatMessage: '' }, { _id: 1, name: '10.1.1.12:27017', health: 1, state: 2, stateStr: 'SECONDARY', uptime: 422, optime: { ts: Timestamp({ t: 1664284022, i: 1 }), t: Long("1") }, optimeDurable: { ts: Timestamp({ t: 1664284022, i: 1 }), t: Long("1") }, optimeDate: ISODate("2022-09-27T13:07:02.000Z"), optimeDurableDate: ISODate("2022-09-27T13:07:02.000Z"), lastAppliedWallTime: ISODate("2022-09-27T13:07:12.920Z"), lastDurableWallTime: ISODate("2022-09-27T13:07:12.920Z"), lastHeartbeat: ISODate("2022-09-27T13:07:11.931Z"), lastHeartbeatRecv: ISODate("2022-09-27T13:07:11.937Z"), pingMs: Long("0"), lastHeartbeatMessage: '', syncSourceHost: '10.1.1.11:27017', syncSourceId: 0, infoMessage: '', configVersion: 4, configTerm: 1 }, { _id: 2, name: '10.1.1.13:27017', health: 1, state: 7, stateStr: 'ARBITER', uptime: 55, lastHeartbeat: ISODate("2022-09-27T13:07:11.931Z"), lastHeartbeatRecv: ISODate("2022-09-27T13:07:11.945Z"), pingMs: Long("0"), lastHeartbeatMessage: '', syncSourceHost: '', syncSourceId: -1, infoMessage: '', configVersion: 4, configTerm: 1 } ], ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1664284032, i: 1 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1664284032, i: 1 }) }
2、查看副本集配置信息
rs1 [direct: other] test> rs.conf() { _id: 'rs1', version: 4, term: 1, members: [ { _id: 0, host: '10.1.1.11:27017', arbiterOnly: false, #是否为仲裁节点 buildIndexes: true, #是否要创建索引 hidden: false, #是否隐藏 priority: 1, #优先级 tags: {}, secondaryDelaySecs: Long("0"), #比主节点延迟多少秒 votes: 1 #是否参与投票 }, { _id: 1, host: '10.1.1.12:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 }, { _id: 2, host: '10.1.1.13:27017', arbiterOnly: true, buildIndexes: true, hidden: false, priority: 0, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 } ], protocolVersion: Long("1"), writeConcernMajorityJournalDefault: true, settings: { chainingAllowed: true, #是否允许从其他从节点同步数据 heartbeatIntervalMillis: 2000, heartbeatTimeoutSecs: 10, #副本集节点等待另一个节点的心跳的秒数 electionTimeoutMillis: 10000, catchUpTimeoutMillis: -1, catchUpTakeoverDelayMillis: 30000, getLastErrorModes: {}, getLastErrorDefaults: { w: 1, wtimeout: 0 }, #自定义写顾虑 replicaSetId: ObjectId("6332f3bebe5597adbad0ae88") } }
7、管理副本集
- rs.freeze(seconds)
- 冻结指定的节点,在指定的时间内无法成为主节点(单位秒)。在一个节点上把rs.freeze设置为0,会删除已有的冻结。还要注意这不会停止副本。
- rs.syncFrom()
- 使从节点从指定的节点临时同步数据。
7.1、查看副本集的信息
- rs.conf()
- 查看副本集的当前配置信息。
- rs.status()
- 查看副本集的当前状态信息。列出了每个节点及其状态信息,包括最后联系时间。该调用可提供整个集群的简单健康检查。
7.2、初始化副本集
//使用默认参数初始化副本集 rs.initiate() //使用配置描述初始化副本集 rs.initiate( { _id: "myReplSet", version: 1, members: [ { _id: 0, host : "mongodb0.example.net:27017" }, { _id: 1, host : "mongodb1.example.net:27017" }, { _id: 2, host : "mongodb2.example.net:27017" } ] } )
7.3、添加节点
//添加从节点,并指定特定的属性 rs.add( { _id: <int>, host: <string>, #主机名称(必须) arbiterOnly: <boolean>, #是否为仲裁节点 buildIndexes: <boolean>, #是否要创建索引 hidden: <boolean>, #是否隐藏 priority: <number>, #优先级 tags: <document>, #设置读偏好的标签 secondaryDelaySecs: <int>, #比主节点延迟多少秒 votes: <number> #是否参与投票,有效值是0或1 }, arbiterOnly: <boolean> #是否为仲裁节点 ) //添加仲裁节点,等价于rs.add(<host>, true) rs.addArb(host)
示例:
//使用主机名添加从节点 rs.add( "mongodbd1.example.net:27017" ) //添加从节点,并指定特定的属性 rs.add( { host: "mongodbd2.example.net:27017", priority: 0 } ) //添加仲裁节点 rs.addArb("mongodbd3.example.net:27017")
7.4、删除节点
- rs.remove(hostname)
- 从当前副本集中删除指定的节点。会短暂地断开shell连接,并在副本集重新协商哪个节点成为主节点时强制重新连接。因此,即使该命令成功,shell也会显示错误。
7.5、降级主节点
- rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs)
- 将副本集的主节点降级为从节点,并重新选举新的主节点。
- 该方法不会立即进行降级。如果没有与主节点同步的从节点,那么将等待secondaryCatchUpPeriodSecs(默认情况下为10秒)让从节点赶上主节点。一旦有了可选的从节点,该方法就会将主节点降级。
- 该方法一旦退出,原来的主节点就会变成从节点,并且在stepDownSecs时间内没有资格再次成为主节点。
7.6、修改副本集配置信息
rs.reconfig( <configuration>, { "force" : <boolean>, "maxTimeMS" : <int> } )
示例:
- 修改副本集配置信息
- 将从节点的优先级设置为2。
cfg = rs.conf() cfg.members[1].priority = 2 rs.reconfig(cfg)
- 查看修改后的配置信息
rs1 [direct: other] test> rs.conf().members[1] { _id: 1, host: '10.1.1.12:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 2, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 }
1
# #