从本节起进入 MongoDB 的分布式篇。复制集(Replica Set)解决的是 3.1 节提出的第一个分布式问题:单节点宕机服务不能停、数据不能丢。它的三个机制——oplog 同步、多数派选举、读写关注——分别对应第 3 章的复制、一致性与读写配额思想,对照着读收获最大。
复制集由若干数据节点(典型三节点:一主两从)构成,主节点(Primary)受理全部写入,从节点(Secondary)持续同步并参与选举;可选的仲裁节点(Arbiter)只投票不存数据,用最低成本凑够选举多数——但它不增强数据安全,反而稀释了数据副本,生产环境建议用三数据节点替代"两数据 + 一仲裁"。
// 查看复制集状态:成员角色与健康度 rs.status() // 输出要点:members[] 中 PRIMARY 与 SECONDARY 标记、 // 每个成员的 optimeDate(同步位点)与 lastHeartbeat(心跳延迟) // 读写偏好:读请求发往从节点以分摊主库压力 db.orders.find({ status: "PAID" }) .readPref("secondaryPreferred")
主节点的每一次写操作都会记入操作日志 oplog(一个有上限的固定集合),从节点拉取 oplog 并在本地重放,实现异步复制。oplog 是一个环形空间,空间满了旧条目被覆盖——由此推出一个经典故障:从节点宕机太久,恢复时它需要的 oplog 已被覆盖,只能全量重新初始化同步。监控 oplog 窗口(最旧与最新条目的时间差)是复制集运维的日常功课。
主节点失联时,存活的数据节点投票选出新主(要求多数派存活——三节点复制集最多容忍一节点故障)。多数派规则同时给了写关注(Write Concern)一个清晰的语义基础:w: "majority" 要求写入在多数数据节点确认后才向客户端返回成功。这是数据安全的锚点——被多数确认的写入,即使原主宕机也不会因选举回滚而丢失。
| 写关注配置 | 语义 | 风险/代价 |
|---|---|---|
| w: 1(默认) | 主节点确认即返回 | 主库宕机窗口内可能丢写入 |
| w: "majority" | 多数节点确认 | 延迟略升,安全级别最高 |
| w: 1 + j: true | 主节点落日志确认 | 折中:单机掉电不丢 |
读侧对称的旋钮是读偏好(Read Preference):primary(只读主,强一致)、primaryPreferred、secondary(只读从,分摊压力但可能旧数据)、secondaryPreferred、nearest(就近)。结合 3.3 的档位观:读己之写场景必须 primary,浏览类列表用 secondary 分流。
背景:三节点复制集(PRIMARY + 两个 SECONDARY),业务写关注 w:1,某天主节点所在可用区网络抖动,主节点与其他节点心跳中断 15 秒。
操作:推演时间线。第 0 秒心跳中断;第 10 秒(超时阈值)两个从节点发起选举,秒级选出新主;新主接收写入期间,原主其实还活着——它降级为从节点并回滚自己未被新主确认的写入(回滚条目写入回滚文件,需要人工比对合并);业务侧第 12 秒起恢复写入(驱动自动发现新主,期间报连接错误重试)。
结果:服务中断约 12 秒自愈;因 w:1 配置,中断前最后 200 毫秒内的部分写入被回滚,出现少量"订单已创建但库存未扣"的悬挂数据,人工比对回滚文件后修复。
解读:两个要点。w:1 的丢失窗口真实存在——若业务要求订单写入绝不丢失,写关注必须 majority,代价是写入延迟略增;回滚文件是人工义务——复制集自动保住的是多数派确认的数据,单主确认的数据进回滚文件等人工处理。这就是 3.1 CAP 在 MongoDB 里的具体价格表。
变式:把写关注改为 majority 重演同一故障,回滚的只有未达多数确认的极少量写入,业务层再配重试幂等,即可做到"无感切换"。配置没有对错,业务定级决定配置。
第一个是把仲裁节点当数据节点用:"两数据 + 一仲裁"架构里数据只有两份,一份数据节点损坏后既无冗余也无容错余量。第二个是读偏好滥用 secondary:读从节点换吞吐却引入复制延迟读旧数据,会话敏感页面读己之写失效(3.3 的读己之写路由),使用前分清哪些读可以旧。第三个是忽视 oplog 窗口监控:大事务或批量写入会瞬间吃掉 oplog 空间,从节点一旦落后出窗即触发全量同步,雪上加霜。
复制集是一组维护相同数据的 mongod 进程:一个主节点接收写入,若干从节点异步复制,另有可选的仲裁节点只投票不存数据。
演练:主节点失联后发生了什么
// 查看复制集状态的关键字段 rs.status().members.forEach(m => print(m.name, m.stateStr, m.health, m.optimeDate)) // PRIMARY / SECONDARY / ARBITER,health 为 1 表示可达 // 查看复制延迟(主从 optime 的差值) rs.printSecondaryReplicationInfo() // 输出示例:source: 10.0.0.12:27017 syncedTo: ... 3 secs (0.05 hrs) behind the primary
复制集最容易被忽视、也最该配的两个参数是写关注(writeConcern)与读关注(readConcern),它们直接决定了这个复制集在 CAP 里站在哪一边。
| 配置 | 语义 | 一致性 | 延迟 |
|---|---|---|---|
| w:1(默认) | 主节点写入即返回 | 弱,主节点宕机可能丢 | 最低 |
| w:majority | 多数节点确认后返回 | 强,不会因切换丢失 | 中等 |
| w:majority + readConcern:majority | 只读取被多数确认的数据 | 强一致读 | 较高 |
| readConcern:snapshot | 事务内的快照读 | 最强 | 最高 |
生产建议:涉及资金、库存、配额的写入显式指定 w:majority;日志、行为埋点这类可容忍少量丢失的写入用默认配置换取吞吐。读的侧同理——对账类读用 majority,普通展示用 local。
一条经验:默认配置(w:1)在单机房小规模部署下问题不大,但一旦上了云或跨机房,网络抖动频率显著上升,此时"主节点写入即返回"意味着一次切换就可能丢掉最后几百毫秒的写入。这个风险窗口值不值得消除,取决于业务——但必须先知道它的存在。
高可用有了,规模还没解——下一节分片集群,把数据摊到更多机器上。