第17章:复制集基础——从单机到高可用
1. 项目背景业务场景本地生活电商上线三个月单台 MongoDB 开始力不从心。某天凌晨 2 点服务器突然宕机——只是普通的内存故障但因为只有一台 MongoDB整个系统直接不可用直到早上 8 点运维重启服务器才恢复。6 小时的停服直接导致 3000 个订单丢失、用户投诉爆表。CTO 拍板必须做高可用。但团队的困扰在于——“MongoDB 的主从复制怎么做一个 Primary 两个 Secondary 够不够如果 Primary 挂了客户端怎么自动切到新 Primary应用代码需要改吗”痛点没有复制集Replica Set的单机 MongoDB任意故障进程崩溃、系统重启、磁盘满、网络断都会导致业务全部中断。更糟糕的是——很多人第一次搭复制集就踩坑节点之间网络不通导致选举死锁writeConcern: majority不懂含义导致写入性能和一致性之间做错取舍误以为读 Secondary 能分摊读压力结果延时导致数据不一致Arbiter 节点不懂其作用直接部署了 3 个数据节点花冤枉钱。2. 项目设计小胖拿着高可用架构图大师我听说过主从复制——一个主库写入从库同步数据。但 MongoDB 不是还搞了个什么复制集吗和 MySQL 的主从复制有啥不同大师区别蛮大的。MySQL 的主从复制是单向的——主库写从库只读主库死了你需要手动把从库提成主库。MongoDB 的复制集是自管理的——节点之间通过心跳Heartbeat互相监控Primary 挂了剩下的节点自动投票选举出新的 Primary整个过程无需人工干预。小胖自动选举这不是跟选班长一样吗——班长不在剩下的同学投票选新班长大师贴切。选举遵循 Raft 协议——MongoDB 的复制集本质上是一个基于 Raft 的分布式一致性集群。每个节点都有 term任期号每次选举 term 加 1获得多数票过半数的节点当选 Primary。技术映射MongoDB 复制集是自管理的分布式状态机——Primary 负责所有写入Oplog 记入日志Secondary 通过拉取 Oplog 不断地重放操作来追赶 Primary 的状态。当 Primary 不可达时存活的节点通过 Raft 共识协议选举新 Primary。小白那复制集有哪几种节点角色我知道 Primary 和 Secondary还有什么 Arbiter、Hidden、Delayed 的各有啥用大师MongoDB 复制集中的 5 种角色角色存储数据参与选举用途Primary是是唯一接收写入的节点Secondary (priority 0)是是同步数据 可参与选举Arbiter仲裁节点否是只投票不存数据打破平票僵局Hidden隐藏节点是否priority0备份 / 报表专用不接收读流量Delayed延迟节点是否priority0, 延迟 slaveDelay 秒误删恢复的后悔药小胖Arbiter 不存数据只投票那不是省了一个服务器的钱我直接用 Arbiter 组一主一从一 Arbiter 不就好了大师Arbiter 确实省钱但也有代价。Arbiter 不存数据意味着——如果 Primary 挂了Secondary 当选但如果 Primary 挂了且 Arbiter 也不可用了Secondary 独自得不到多数票无法成为 Primary。所以 Arbiter 只是用低成本凑足 3 票选举需要超过半数但不增加数据的容灾副本数。技术映射复制集选举要求获得 大多数选票。3 节点2 数据 1 Arbiter的多数是 2任意 1 个数据节点挂掉仍能选举。但 2 个数据节点都挂掉Arbiter 独木难支——因为 Raft 要求 quorum 中必须有包含最新日志的节点。小白追问Oplog 是什么我老是听到这个词但不太理解。大师OplogOperation Log是 MongoDB 复制的核心。你可以把它理解成主节点的施工清单——Primary 执行的每一个写操作insert/update/delete都会生成一条 Oplog 条目Secondary 不断地拉取这个清单在自己的数据库上按顺序重放一遍。Oplog 是一个固定大小的 Capped Collection环形缓冲区大小通过oplogSizeMB设置。如果 Secondary 断开连接太久Oplog 窗口里已经没有它错过的那些操作——此时 Secondary 无法追赶必须做 Initial Sync全量重新同步。技术映射Oplog 窗口 Oplog 大小 / 写入速率。比如 Oplog 2GB写入速率 100MB/h窗口就是 20 小时。因此写入高峰期 Oplog 窗口会缩短需要监控。大师总结复制集是 MongoDB 高可用的最小单元——3 个数据节点PSS 或 PSA是最常见的生产配置。今天记住三点自动选举靠 Raft 协议写入复制靠 Oplog 拉取数据一致性靠 writeConcern 控制。3. 项目实战3.1 环境准备用 Docker Compose 搭建 3 节点复制集PSS 架构# mongodb-lab/replicaset/docker-compose-rs.ymlversion:3.8services:rs1:image:mongo:8.0container_name:mongo-rs1ports:-27017:27017command:mongod--replSet myRS--bind_ip_all--port 27017volumes:-./data/rs1:/data/dbnetworks:-rs-netrs2:image:mongo:8.0container_name:mongo-rs2ports:-27018:27017command:mongod--replSet myRS--bind_ip_all--port 27017volumes:-./data/rs2:/data/dbnetworks:-rs-netrs3:image:mongo:8.0container_name:mongo-rs3ports:-27019:27017command:mongod--replSet myRS--bind_ip_all--port 27017volumes:-./data/rs3:/data/dbnetworks:-rs-netnetworks:rs-net:driver:bridge3.2 分步实现步骤一初始化复制集目标启动 3 个 mongod 并初始化复制集。# 启动容器dockercompose-fdocker-compose-rs.yml up-ddockercomposeps# 进入 rs1 初始化复制集dockerexec-itmongo-rs1 mongosh--eval rs.initiate({ _id: myRS, members: [ { _id: 0, host: rs1:27017, priority: 2 }, { _id: 1, host: rs2:27017, priority: 1 }, { _id: 2, host: rs3:27017, priority: 1 } ] }) # 期望输出: { ok: 1 }// 查看复制集状态rs.status()// 关键字段// members[*].stateStr → PRIMARY / SECONDARY// members[*].health → 1// members[*].optime → 该节点的最新 Oplog 时间步骤二观察复制行为目标在 Primary 上写入数据观察 Secondary 是否实时同步。// 连接到 Primary可能是 rs1 的 27017// 确认当前是 Primaryuse test_rs db.products_rs.insertOne({name:复制集测试商品,price:199,createdAt:newDate()})print(写入成功等待同步...)// 连接到 Secondary27018 或 27019需要先允许从库读// mongosh mongodb://localhost:27018// 从库默认不允许读需要设置db.getMongo().setReadPref(secondary)// 或者在连接串中mongosh mongodb://localhost:27018/?readPreferencesecondaryuse test_rsconstdocdb.products_rs.findOne({name:复制集测试商品})print(从库中的商品:,doc?doc.name:未同步稍等几秒)// 刚插入可能还没同步 50ms 延迟稍等后应出现步骤三模拟 Primary 宕机 自动选举目标停止 Primary 容器观察自动选主过程。# 找到当前 Primarydockerexecmongo-rs1 mongosh--quiet--evalrs.isMaster().primary|grep-v^$# 假设 Primary 是 rs1:27017# 模拟宕机停止 rs1 容器dockerstop mongo-rs1# 查看 rs2 的复制集状态dockerexecmongo-rs2 mongosh--quiet--eval const status rs.status(); print(Primary:, status.members.find(m m.stateStr PRIMARY)?.name || 无); # 期望rs2 或 rs3 中的一个被选举为 Primary# 选举时间通常在 5-12 秒之间// 查看选举日志docker exec mongo-rs2 cat/var/log/mongodb/mongod.log2/dev/null|grepelection|tail-5步骤四恢复宕机节点——重新加入复制集目标将被 Kill 的 rs1 重新启动观察它如何追回数据。# 重启 rs1dockerstart mongo-rs1# 等待 10 秒查看 rs1 的角色dockerexecmongo-rs1 mongosh--quiet--eval const status rs.status(); const me status.members.find(m m.name.includes(rs1)); print(rs1 状态:, me.stateStr); print(rs1 健康:, me.health); # 期望rs1 以 SECONDARY 身份重新加入原 Primary 已降级步骤五配置节点的优先级和延迟目标设置 Hidden 节点和 Delayed 延迟节点。// 在 Primary 上执行// 添加 Hidden 节点不接收客户端读流量rs.add({host:rs4:27017,priority:0,// 0 不能成为 Primaryhidden:true// 隐藏节点})// 添加 Delayed 节点同步延迟 1 小时误删恢复用rs.add({host:rs5:27017,priority:0,hidden:true,secondaryDelaySecs:3600// 1 小时延迟})// 查看配置rs.conf().members.forEach(m{print(${m.host}: priority${m.priority}, hidden${m.hidden||false}, delay${m.secondaryDelaySecs||0}s)})步骤六writeConcern 实战——对比写入确认级别use test_rs// 场景 1w:1只 Primary 确认最快但不安全constt1Date.now()db.orders_rs.insertOne({orderNo:WC_TEST_1,amount:100},{writeConcern:{w:1}})print(w:1 耗时:,Date.now()-t1,ms)// 约 2-5ms// 场景 2w:majority多数成员确认安全但稍慢constt2Date.now()db.orders_rs.insertOne({orderNo:WC_TEST_2,amount:200},{writeConcern:{w:majority,wtimeout:5000}})print(w:majority 耗时:,Date.now()-t2,ms)// 约 10-50ms取决于副本之间的网络延迟// 场景 3w:3所有 3 个数据节点都确认最慢constt3Date.now()db.orders_rs.insertOne({orderNo:WC_TEST_3,amount:300},{writeConcern:{w:3,j:true,wtimeout:5000}}// j:true 需要持久化到 Journal)print(w:3j 耗时:,Date.now()-t3,ms)3.3 完整代码清单文件用途mongodb-lab/replicaset/docker-compose-rs.yml3 节点复制集 Docker 配置mongodb-lab/replicaset/init-rs.js复制集初始化脚本mongodb-lab/replicaset/failover-test.js故障切换验证脚本mongodb-lab/replicaset/writeconcern-demo.jswriteConcern 性能对比3.4 测试验证// 连接到 Primary 运行// 1. 确认复制集状态conststatusrs.status()print(复制集名称:,status.set)print(成员数:,status.members.length)print(Primary:,status.members.filter(mm.stateStrPRIMARY).length1?PASS:FAIL)// 2. 验证写入复制db.test_rs.validation.insertOne({msg:验证复制,ts:newDate()},{writeConcern:{w:majority}})// 在 Secondary 上验证// mongosh mongodb://localhost:27018/?readPreferencesecondary --eval // const doc db.getSiblingDB(test_rs).validation.findOne({msg:验证复制});// print(doc ? PASS: 数据已同步 : FAIL: 未同步);// // 3. 验证自动选举// 记下当前 Primary停掉它// 等待 15 秒在另一个节点查看应有新 Primary// 4. 清理// docker compose -f docker-compose-rs.yml down -v4. 项目总结4.1 复制集 vs 单机 vs 分片集群维度单机复制集分片集群高可用无自动故障切换每分片独立高可用数据冗余无多副本每分片多副本写性能最高略降writeConcern 开销水平扩展读性能单机可读 Secondary 分担多分片并行读部署复杂度低中高适用数据规模 100GB 500GB500GB4.2 适用场景复制集适用所有生产环境——单机仅限本地开发和 CI。需要业务无感知的自动故障转移。读多写少——读写分离读流量分担到 Secondary。异机房容灾——跨机房部署 Secondary 节点。不适用场景单机开发环境——复制集增加 3 倍资源消耗。写入优先且可接受停机的批量处理——单机更快。4.3 注意事项注意事项说明奇数个选举节点3/5/7 个有投票权的节点避免偶数需要过半票Oplog 大小默认是磁盘的 5%生产建议设为 24-48 小时的写入量Secondary 读不是免费的Secondary 追不上 Primary 时会产生读延迟网络分区被隔离的少数派节点无法选举服务不可用rs.stepDown()不是重启会触发安全的 Primary 降级让出位置给其他节点4.4 常见踩坑经验故障案例一偶数节点导致网络分区后无法选举某团队部署了 4 节点2 个完整节点 2 个 Arbiter机房断网后 22 各在一侧每一侧都只有 2 票需要过半 3 票——两侧都无法选举 Primary导致服务完全中断。根因偶数节点 对称分布导致分裂脑Split Brain。解决始终使用奇数个投票节点关键节点放在奇数侧。故障案例二Oplog 窗口太小导致从库追不上某促销期间订单量暴增 10 倍写入速率从 50MB/h 飙升 500MB/h。Oplog 2GB 的窗口从 40 小时压缩到 4 小时。一个做全量备份后重启的从库发现自己需要追 6 小时前的数据但 Oplog 只保留了 4 小时的——必须做 Initial Sync耗时 3 小时。解决增大 Oplog 至 10GB监控 Oplog 窗口时间低于 12 小时告警。故障案例三writeConcern: majority在 2 节点复制集下写不进去某团队用 1 Primary 1 Secondary2 节点设置writeConcern: majority。多数 ceil(3/2)2——需要两个节点都确认。其中一个节点挂了写入因为多数无法达成直接报超时。根因2 节点复制集的多数等于全部任意一个节点故障写入全停。解决始终用至少 3 个数据节点或用writeConcern: 1牺牲一致性保可用性。4.5 思考题如果 Primary 写入 Oplog 后立刻宕机Oplog 还没传到任何 Secondary新选出的 Primary 会丢失这些写入吗对客户端而言这些写入的返回结果是什么Arbiter 节点不存数据为什么也能参与选举如果 Arbiter 被网络隔离到少数派一侧会发生什么答案将在第 18 章末尾揭晓上一章思考题答案两个需要事务的跨集合场景①下单 库存预扣——创建订单和扣减库存必须在同一事务中如果不用事务替代方案是先预扣库存原子$inc 后创建订单订单创建失败后补偿恢复库存Saga 模式代码复杂且补偿失败有数据不一致风险。②订单完成 更新用户统计——订单标记完成和用户总消费金额更新。替代方案是 Change Streams 异步更新统计数据接受短暂的不一致。Spring Boot 自动管理索引spring.data.mongodb.auto-index-creationtrueIndexed的优点开发效率高不需要手写 DDL 脚本风险① 启动时同步建索引会阻塞应用启动大集合建索引需要数分钟② 生产环境Indexed注解的修改会意外地在数据库中创建非预期索引③ 无法控制background/partialFilterExpression等高级参数。推荐方案开发环境用自动建索引快速迭代生产环境关闭自动建索引通过变更管理流程执行索引变更脚本。延伸阅读与资源python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析

相关新闻

G-Helper终极指南:三步释放华硕笔记本隐藏性能的免费轻量工具

G-Helper终极指南:三步释放华硕笔记本隐藏性能的免费轻量工具

G-Helper终极指南:三步释放华硕笔记本隐藏性能的免费轻量工具 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenb…

2026/10/9 12:58:49 阅读更多 →
Ccache:编译加速器的幕后英雄,如何让开发者的等待时间减少90%

Ccache:编译加速器的幕后英雄,如何让开发者的等待时间减少90%

Ccache:编译加速器的幕后英雄,如何让开发者的等待时间减少90% 【免费下载链接】ccache ccache – a fast compiler cache 项目地址: https://gitcode.com/gh_mirrors/cc/ccache 作为一名C/C开发者,你是否曾盯着终端屏幕上缓慢滚动的编…

2026/10/8 22:13:27 阅读更多 →
3步免费激活IDM:解锁完整下载功能的终极指南

3步免费激活IDM:解锁完整下载功能的终极指南

3步免费激活IDM:解锁完整下载功能的终极指南 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 还在为Internet Download Manager(IDM&#…

2026/10/8 3:39:02 阅读更多 →

最新新闻

.NET ILogger日志体系详解:从结构化日志到生产级配置实战

.NET ILogger日志体系详解:从结构化日志到生产级配置实战

做后端服务这些年,日志一直是我觉得最值得花心思打磨的基础设施。很多兄弟可能觉得日志嘛,不就是到处打Console.WriteLine或者Debug.WriteLine,出了问题打开控制台看一下就行。但等你真正把一个服务跑上生产,几千个请求涌进来的时…

2026/10/9 12:58:38 阅读更多 →
深入解析.NET ILogger:解锁稳健日志系统的核心机制

深入解析.NET ILogger:解锁稳健日志系统的核心机制

日志这件事,在.NET的开发圈子里一直是老生常谈的热门话题。以前我们用Log4Net或NLog,都是直接引用具体日志库,代码里到处是 LogManager.GetCurrentClassLogger() 这种静态调用。日志系统一旦绑定到具体实现,后面想换方案简直像脱…

2026/10/9 12:58:38 阅读更多 →
二次曲面分类记忆法:从方程结构快速判断曲面类型

二次曲面分类记忆法:从方程结构快速判断曲面类型

1. 为什么二次曲面总让人记不住1.1 从“背了忘、忘了背”的死循环说起但凡学过空间解析几何或者高等数学下册的人,大概率都有过这么一段经历:翻开课本,二次曲面那一章扑面而来的是椭球面、单叶双曲面、双叶双曲面、椭圆抛物面、双曲抛物面、二…

2026/10/9 12:58:38 阅读更多 →
AI科研味觉评估:TasteVal框架原理与五维诊断实践

AI科研味觉评估:TasteVal框架原理与五维诊断实践

1. 项目概述:这不是在给AI打分,而是在校准它的“味觉神经”最近在几个AI研究社区里,反复看到一个词被拎出来讨论:“TasteVal”。不是美食测评,也不是咖啡品鉴,而是把“taste”这个词硬生生拽进AI评估的实验…

2026/10/9 12:58:38 阅读更多 →
基于SpringBoot+Vue+MySQL的学生宿舍管理系统设计与实现

基于SpringBoot+Vue+MySQL的学生宿舍管理系统设计与实现

每年到这个节点,总有同学在群里问宿舍管理系统怎么做。市面上的教程要么只有零散代码,要么数据库脚本和实体类对不上,要么压根跑不起来。我去年完整做了一套基于SpringBootVueMySQL的学生宿舍管理系统,从设计数据库到写接口、做页…

2026/10/9 12:58:38 阅读更多 →
Transformer生成式文本摘要实战:从环境配置到Beam Search调优

Transformer生成式文本摘要实战:从环境配置到Beam Search调优

简介:本资源是一份面向NLP初学者与进阶开发者的Transformer生成式文本摘要实战代码包,聚焦自然语言处理中的核心任务——自动摘要,适用于新闻提炼、论文速读、报告精简等实际场景。压缩包共10个文件,含5个Python源码(涵…

2026/10/9 12:57:34 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →