MongoDB: 副本集Replication:高可用架构
最后更新:2026-08-26
副本集是 MongoDB 高可用的基石——掌握它能构建 99.99% 可用性的生产集群。
1. 你将学到
- 副本集概念(Primary / Secondary / Arbiter)
- 选举机制(基于 Raft)
- 数据同步(initial sync / oplog tailing)
- 读写分离(readPreference)
- Docker 部署 3 节点副本集
- 故障转移与恢复
2. 副本集架构
概念说明:副本集(Replica Set)是 MongoDB 高可用的基础架构,由多个 mongod 进程组成——1 个 Primary(主节点)+ N 个 Secondary(从节点)+ 可选 Arbiter(仲裁节点)。Primary 接受所有写操作,通过 oplog 异步复制到 Secondary,实现数据冗余和故障自动转移。
工作原理:Primary 将每个写操作记录到 oplog(固定大小的 capped collection),Secondary 通过 tailing oplog 持续拉取并重放写操作,保持与 Primary 数据一致。当 Primary 故障时,剩余节点通过选举协议(基于 Raft)选出新 Primary,应用自动重连,实现 99.99% 可用性。
Raft 协议与选举机制:MongoDB 的选举基于 Raft 协议的变体,核心规则是"多数票决定"——获得超过半数节点投票的候选者才能成为 Primary。这保证同一时刻最多只有一个 Primary(避免脑裂),且新 Primary 拥有最完整的数据。
graph TB
App[应用] --> P[Primary<br/>主节点<br/>接受所有写]
P -.复制 oplog.-> S1[Secondary 1<br/>从节点<br/>可读+备份]
P -.复制 oplog.-> S2[Secondary 2<br/>从节点<br/>可读+备份]
A[Arbiter<br/>仲裁节点<br/>仅投票]
subgraph "数据流"
W[写入] --> P
P -->|oplog| S1
P -->|oplog| S2
end
subgraph "选举"
P -->|心跳| S1
P -->|心跳| S2
P -->|心跳| A
end
style P fill:#d4edda
style S1 fill:#cce5ff
style S2 fill:#cce5ff
style A fill:#fff3cd
节点角色对比:
| 节点 | 职责 | 存数据 | 可读 | 参与选举 | priority |
|---|---|---|---|---|---|
| Primary | 接受所有写操作 | ✅ | ✅ | ✅ | 默认 1(可调高) |
| Secondary | 复制 Primary 数据 | ✅ | ✅(需设置) | ✅ | 默认 1 |
| Arbiter | 仅参与选举投票 | ❌ | ❌ | ✅ | 0(不可当选) |
副本集 vs 单节点:
| 维度 | 单节点 | 副本集 |
|---|---|---|
| 可用性 | 单点故障 | 99.99%(自动故障转移) |
| 数据安全 | 磁盘损坏即丢失 | 多副本冗余 |
| 读取扩展 | 无 | Secondary 分担读负载 |
| 事务支持 | ❌ | ✅(需要副本集) |
| 运维复杂度 | 低 | 中 |
3. 启动副本集
概念说明:启动副本集需要两步:1) 以 --replSet 参数启动 mongod 进程;2) 在 mongosh 中执行 rs.initiate() 初始化副本集配置。初始化时指定所有成员的地址,MongoDB 自动选举 Primary。
工作原理:rs.initiate() 将副本集配置写入每个节点的 local 数据库,触发选举协议。第一个初始化的节点通常成为 Primary,其他节点自动同步配置并开始 tailing oplog。
# === 启动第一个节点(副本集模式)===
mongod --replSet rs0 --port 27017 --dbpath /data/db1 --bind_ip localhost
# === mongosh 初始化 ===
mongosh
> rs.initiate({
_id: 'rs0',
members: [
{ _id: 0, host: 'localhost:27017' },
{ _id: 1, host: 'localhost:27018' },
{ _id: 2, host: 'localhost:27019' }
]
});
初始化配置参数:
| 参数 | 说明 | 示例 |
|---|---|---|
_id |
副本集名称,必须与 --replSet 一致 |
'rs0' |
members |
成员列表 | [{_id: 0, host: '...'}] |
members._id |
成员编号(唯一) | 0, 1, 2 |
members.host |
成员地址 | 'localhost:27017' |
members.priority |
选举优先级(0=不可当选) | 默认 1 |
settings.electionTimeoutMillis |
心跳超时时间 | 默认 10000ms |
4. 添加/移除节点
概念说明:副本集支持在线添加和移除节点,无需停服。添加节点后,新节点自动从 Primary 进行 initial sync(全量同步),然后切换到 oplog tailing(增量同步)。
数据同步流程:
sequenceDiagram
participant New as 新节点
participant P as Primary
participant Oplog as oplog
New->>P: 加入副本集请求
P-->>New: 确认 + 当前配置
Note over New,P: Phase 1: Initial Sync(全量同步)
New->>P: 请求全量数据
P-->>New: 所有集合数据 + 索引
Note over New,P: Phase 2: Oplog Tailing(增量同步)
loop 持续
New->>Oplog: 拉取新 oplog 条目
Oplog-->>New: 增量操作
New->>New: 重放 oplog 操作
end
Note over New: 同步完成后可参与选举
| 操作 | 命令 | 说明 |
|---|---|---|
| 添加数据节点 | rs.add('host:port') |
自动 initial sync |
| 添加仲裁节点 | rs.addArb('host:port') |
仅投票,不存数据 |
| 移除节点 | rs.remove('host:port') |
节点自动降级 |
| 查看状态 | rs.status() |
所有节点状态 + 健康 |
| 查看配置 | rs.conf() |
副本集配置详情 |
// === 添加新节点 ===
rs.add('localhost:27020');
// === 添加 Arbiter(不存数据)===
rs.addArb('localhost:27021');
// === 移除节点 ===
rs.remove('localhost:27020');
// === 查看副本集状态 ===
rs.status();
要点解析:
- initial sync 期间新节点不参与选举和读服务,同步完成后自动成为 Secondary
- 大数据集 initial sync 耗时较长(100GB 可能需要数小时),建议在业务低峰期添加
- Arbiter 不存数据,仅用于凑奇数节点参与投票,优先级为 0
5. 选举机制(基于 Raft)
概念说明:选举机制是副本集高可用的核心——当 Primary 故障时,Secondary 自动发起选举,选出新 Primary。MongoDB 选举基于 Raft 协议变体,核心保证是"同一时刻最多一个 Primary"(避免脑裂)和"新 Primary 数据最完整"。
Raft 选举流程:
- 检测故障:Secondary 在 electionTimeout(默认 10 秒)内未收到 Primary 心跳
- 发起选举:优先级最高的可用 Secondary 成为候选人,自增 term
- 请求投票:候选人向所有节点发送投票请求
- 投票规则:每个 term 每个节点只投一票,投给数据最新的候选人
- 多数当选:获得超过半数票(> N/2)的候选人成为新 Primary
- 应用重连:驱动自动检测新 Primary,透明切换
sequenceDiagram
participant S1 as Secondary 1<br/>(候选者)
participant S2 as Secondary 2
participant A as Arbiter
Note over S1: Primary 心跳超时 10s<br/>发起选举
S1->>S1: 自增 term = 2<br/>投自己一票
S1->>S2: RequestVote(term=2, lastOplogTime)
S1->>A: RequestVote(term=2, lastOplogTime)
S2->>S1: GrantVote ✅<br/>(数据足够新)
A->>S1: GrantVote ✅
Note over S1: 获得多数票 (2/3)<br/>成为新 Primary
S1->>S2: Heartbeat(term=2, role=PRIMARY)
S1->>A: Heartbeat(term=2, role=PRIMARY)
Note over S1,S2: 选举完成,应用自动重连
选举关键参数:
| 选举规则 | 说明 | 默认值 |
|---|---|---|
| 触发 | Primary 失联超过 electionTimeout | 10 秒 |
| 候选 | 所有 Secondary + Arbiter | — |
| 投票 | 获得多数票(> 半数)才能成为 Primary | — |
| 优先级 | priority 高的节点优先当选 | 1 |
| 数据完整性 | 投票者只投 oplog 更新的候选人 | — |
| 选举冷却 | 防止频繁选举 | 30 秒 |
graph LR
A[Primary 宕机] --> B[Secondary 检测]
B --> C{获得多数票?}
C -->|是| D[提升为 Primary]
C -->|否| E[等待]
D --> F[应用重连]
style D fill:#d4edda
| 选举规则 | 说明 |
|---|---|
| 触发 | Primary 失联超过 electionTimeout |
| 候选 | 所有 Secondary + Arbiter |
| 投票 | 获得多数票(> 半数)才能成为 Primary |
| 优先级 | priority 高的节点优先当选 |
为什么需要奇数节点:3 节点容忍 1 故障(2/3 多数票),4 节点也只容忍 1 故障(3/4 多数票),5 节点容忍 2 故障(3/5 多数票)。偶数节点不增加容错能力反而增加成本,所以推荐奇数节点。
▶ 示例 1:选举优先级配置
// TechCorp:让更强的服务器优先当选 Primary
rs.reconfig({
_id: 'rs0',
members: [
{ _id: 0, host: 'mongo1:27017', priority: 3 }, // 最强服务器,优先当选
{ _id: 1, host: 'mongo2:27017', priority: 2 }, // 次选
{ _id: 2, host: 'mongo3:27017', priority: 1 }, // 最低优先级
{ _id: 3, host: 'mongo4:27017', priority: 0 } // 永不当选(冷备)
]
});
// priority: 0 的节点永不当选 Primary,适合做冷备或报表服务器
// 调整 priority 会触发重新选举(如果当前 Primary 不再是最优)
输出:
// 执行成功
6. 读写分离
概念说明:副本集天然支持读写分离——写操作走 Primary,读操作可分散到 Secondary。通过 readPreference 控制读路由策略,在读密集型场景下可显著减轻 Primary 压力。
工作原理:mongoose/mongo 驱动根据 readPreference 参数决定读操作发送到哪个节点。primary 保证强一致性,secondary 减轻 Primary 压力但有复制延迟。
readPreference 详解:
| 模式 | 行为 | 一致性 | 延迟 | 适用场景 |
|---|---|---|---|---|
primary |
仅读主节点 | 最强 | 最低 | 事务、关键数据 |
primaryPreferred |
优先主,不可用则从 | 较强 | 低 | 一般场景 |
secondary |
仅读从节点 | 较弱 | 较高 | 报表/分析 |
secondaryPreferred |
优先从,不可用则主 | 较弱 | 较低 | 读多写少 |
nearest |
网络延迟最低 | 不确定 | 最低 | 地理分布式 |
一致性 vs 性能权衡:
graph LR
A[primary<br/>强一致<br/>高延迟] --> B[primaryPreferred<br/>较强一致]
B --> C[secondaryPreferred<br/>较弱一致]
C --> D[secondary<br/>弱一致<br/>低延迟]
D --> E[nearest<br/>不确定<br/>最低延迟]
style A fill:#d4edda
style D fill:#fff3cd
// === 默认:所有读写都在 Primary ===
const user = await User.findById(userId);
// === 读从节点(减轻 Primary 压力)===
const products = await Product.find().read('secondary');
// === 读偏好选项 ===
await Product.find().read('primary'); // 仅主节点
await Product.find().read('primaryPreferred'); // 优先主节点
await Product.find().read('secondary'); // 仅从节点
await Product.find().read('secondaryPreferred'); // 优先从节点
await Product.find().read('nearest'); // 网络最近
要点解析:
- 从节点读取可能有复制延迟(通常 < 1 秒,网络异常时可能更长)
secondary模式下,如果所有 Secondary 都不可用,查询会报错- 报表、搜索索引构建等对一致性要求不高的场景建议
secondary或secondaryPreferred
7. Docker 部署 3 节点
概念说明:Docker Compose 是本地开发和测试部署副本集的最便捷方式。3 节点(1 Primary + 2 Secondary)是最小生产推荐配置,提供高可用和自动故障转移。
部署架构:
graph TB
subgraph "Docker Compose"
M1[mongo1:27017<br/>Primary/Secondary]
M2[mongo2:27017<br/>Primary/Secondary]
M3[mongo3:27017<br/>Primary/Secondary]
end
App[应用] --> M1
App --> M2
App --> M3
M1 <--> M2
M2 <--> M3
M1 <--> M3
style M1 fill:#d4edda
style M2 fill:#cce5ff
style M3 fill:#cce5ff
# docker-compose.yml
version: '3.8'
services:
mongo1:
image: mongo:7.0
command: mongod --replSet rs0 --port 27017
ports:
- "27017:27017"
mongo2:
image: mongo:7.0
command: mongod --replSet rs0 --port 27017
ports:
- "27018:27017"
mongo3:
image: mongo:7.0
command: mongod --replSet rs0 --port 27017
ports:
- "27019:27017"
// === 初始化副本集 ===
rs.initiate({
_id: 'rs0',
members: [
{ _id: 0, host: 'mongo1:27017' },
{ _id: 1, host: 'mongo2:27017' },
{ _id: 2, host: 'mongo3:27017' }
]
});
部署步骤:
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | docker-compose up -d |
启动 3 个容器 |
| 2 | mongosh --port 27017 |
连接第一个节点 |
| 3 | rs.initiate({...}) |
初始化副本集 |
| 4 | rs.status() |
验证状态 |
| 5 | rs.conf() |
查看配置 |
8. 故障转移
概念说明:故障转移(Failover)是副本集最重要的能力——Primary 宕机时,剩余节点自动选举新 Primary,应用透明重连。整个过程通常在 10-30 秒内完成,期间写入不可用但读取可降级到 Secondary。
故障转移流程:
sequenceDiagram
participant App as 应用
participant P as Primary (mongo1)
participant S1 as Secondary (mongo2)
participant S2 as Secondary (mongo3)
Note over P: mongo1 宕机
P-xApp: 心跳断开
P-xS1: 心跳断开
P-xS2: 心跳断开
Note over S1,S2: 10秒未收到Primary心跳
S1->>S2: 发起选举
S2->>S1: 投票同意
S1->>S1: 获得多数票(2/3)
Note over S1: mongo2 成为新 Primary
App->>S1: 自动重连 → 写入恢复正常
App->>S2: 读取继续(secondaryPreferred)
Note over App,S1: 总中断时间: 10-30秒
| 故障场景 | 影响 | 恢复时间 |
|---|---|---|
| Primary 宕机 | 写入中断 10-30 秒 | 自动选举恢复 |
| Secondary 宕机 | 无影响 | 重启后自动同步 |
| 多数节点宕机 | 副本集只读 | 需恢复多数节点 |
| 网络分区 | 少数侧不可写 | 网络恢复后自动合并 |
# === Primary 宕机模拟 ===
# 杀死 mongo1 容器
docker stop mongo1
# === 自动选举(30 秒内完成)===
# mongo2 或 mongo3 自动提升为 Primary
# === 应用自动重连(mongoose 配置)===
mongoose.connect('mongodb://mongo1,mongo2,mongo3/shopdb?replicaSet=rs0');
应用层故障处理:
| 配置 | 说明 | 推荐值 |
|---|---|---|
| 连接串 | 列出所有节点 | mongodb://m1,m2,m3/db?replicaSet=rs0 |
| 重试写入 | 自动重试网络错误 | retryWrites=true |
| 读偏好 | 故障期间降级读 | secondaryPreferred |
| 连接超时 | 连接超时时间 | connectTimeoutMS=5000 |
| 服务器选择超时 | 选择节点超时 | serverSelectionTimeoutMS=30000 |
9. 副本集最佳实践
概念说明:副本集的最佳实践涵盖节点数量、安全配置、监控告警等维度。遵循这些实践可避免生产环境常见的副本集故障。
核心实践:
| 实践 | 说明 | 原因 |
|---|---|---|
| 3 节点起步 | 1 Primary + 2 Secondary | 最小容错配置,容忍 1 节点故障 |
| 奇数节点 | 选举需要多数票,3/5/7 | 偶数节点不增加容错能力 |
| writeConcern majority | 确认写入多数节点 | 数据不丢失(即使 Primary 宕机) |
| readPreference primaryPreferred | 默认读主节点 | 保证一致性,Primary 不可用降级从节点 |
| 监控 oplog 窗口 | oplog 滚动覆盖会导致数据丢失 | 保持窗口 > 24 小时 |
| 优先级合理配置 | 强服务器 priority 更高 | 故障恢复后强服务器重新当选 |
| Arbiter 谨慎使用 | 仅在成本敏感时用 | Arbiter 不存数据,无法恢复 |
writeConcern 对比:
| writeConcern | 数据安全 | 写入延迟 | 适用场景 |
|---|---|---|---|
w: 1 |
仅 Primary 确认 | 最快 | 日志、非关键数据 |
w: majority |
大多数节点确认 | 中等 | 一般业务数据 |
w: majority, j: true |
大多数确认 + journal 写盘 | 最慢 | 金融、关键数据 |
监控清单:
| 监控项 | 命令 | 健康值 | 告警阈值 |
|---|---|---|---|
| 副本集状态 | rs.status() |
1 PRIMARY + N SECONDARY | 无 PRIMARY |
| 复制延迟 | rs.status().members[n].optimeDate |
< 1 秒 | > 10 秒 |
| oplog 窗口 | rs.printReplicationInfo() |
> 72 小时 | < 24 小时 |
| 连接数 | db.serverStatus().connections |
< 1000 | > 8000 |
| 选举次数 | rs.status().electionMetrics |
少 | 频繁选举 |
▶ 示例 2:Docker 部署 3 节点副本集 + 故障转移测试
# === 1. docker-compose.yml ===
# version: '3.8'
# services:
# mongo1:
# image: mongo:7.0
# command: mongod --replSet rs0 --bind_ip_all --port 27017
# ports: ["27017:27017"]
# networks: [mongo-net]
#
# mongo2:
# image: mongo:7.0
# command: mongod --replSet rs0 --bind_ip_all --port 27017
# ports: ["27018:27017"]
# networks: [mongo-net]
#
# mongo3:
# image: mongo:7.0
# command: mongod --replSet rs0 --bind_ip_all --port 27017
# ports: ["27019:27017"]
# networks: [mongo-net]
#
# networks:
# mongo-net:
# driver: bridge
# 启动 3 节点
docker-compose up -d
# === 2. 初始化副本集 ===
mongosh --port 27017 --eval '
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "localhost:27017" },
{ _id: 1, host: "localhost:27018" },
{ _id: 2, host: "localhost:27019" }
]
});
'
# === 3. 验证副本集状态 ===
mongosh --port 27017 --eval 'rs.status();'
# 输出:
# {
# set: 'rs0',
# members: [
# { name: 'localhost:27017', stateStr: 'PRIMARY' },
# { name: 'localhost:27018', stateStr: 'SECONDARY' },
# { name: 'localhost:27019', stateStr: 'SECONDARY' }
# ]
# }
# === 4. 测试数据写入(自动复制到 Secondary)===
mongosh --port 27017 --eval '
db.products.insertOne({ sku: "PHONE-001", title: "Smartphone X", price: 599 });
'
# === 5. 验证数据复制 ===
mongosh --port 27018 --eval '
db.setSecondaryOk(); // 允许读 Secondary
db.products.findOne({ sku: "PHONE-001" });
'
# 返回相同文档,证明复制成功
# === 6. 故障转移测试 ===
# 停止 Primary
docker stop mongo1
# 在 mongo2 上查看
mongosh --port 27018 --eval 'rs.status();'
# 输出:mongo2 自动提升为 PRIMARY(30 秒内完成)
# === 7. 应用自动重连(mongoose)===
输出:
CONTAINER ID IMAGE STATUS
abc123 latest Up 2 hours
// mongoose 连接串自动发现所有节点
const mongoose = require('mongoose');
await mongoose.connect(
'mongodb://localhost:27017,localhost:27018,localhost:27019/shopdb?replicaSet=rs0'
);
// 即使 Primary 挂了,应用自动连接新的 Primary
const products = await Product.find(); // 自动重试,不报错
// === 8. 读写分离配置 ===
// 写操作走 Primary
await Product.create({ sku: 'PHONE-002', title: 'Phone 2' });
// 读操作可以走 Secondary(减轻 Primary 压力)
const products = await Product.find().read('secondaryPreferred');
// === 9. 恢复故障节点 ===
docker start mongo1
# mongo1 自动以 Secondary 身份加入副本集,从新 Primary 同步数据
输出:3 节点副本集提供高可用,Primary 故障 30 秒内自动切换,应用无感知。
▶ 示例 3:读写分离策略 + writeConcern 调优实战
副本集的核心价值之一是读写分离——写操作走 Primary,读操作分散到 Secondary,减轻 Primary 压力。但读写分离引入了最终一致性问题——Secondary 的数据可能滞后于 Primary。本示例演示如何根据业务场景选择合适的 readPreference 和 writeConcern,在性能与一致性之间取得平衡。
// === 1. mongoose 连接配置读写分离 ===
const mongoose = require('mongoose');
// 主连接:写操作 + 强一致性读
const primaryConn = await mongoose.createConnection(process.env.MONGODB_URI, {
readPreference: 'primary',
writeConcern: { w: 'majority', j: true, wtimeout: 5000 },
maxPoolSize: 20
});
// 只读连接:读操作分散到 Secondary
const secondaryConn = await mongoose.createConnection(process.env.MONGODB_URI, {
readPreference: 'secondaryPreferred',
maxPoolSize: 30
});
// === 2. 按业务场景选择 readPreference ===
// 场景 A:金融交易——必须读最新数据
const Transaction = primaryConn.model('Transaction', txSchema);
app.get('/api/balance/:userId', async (req, res) => {
const user = await Transaction.findById(req.params.userId)
.read('primary'); // 强制从 Primary 读取
res.json({ balance: user.balance });
});
// 场景 B:商品列表——允许秒级延迟,优先 Secondary
const Product = secondaryConn.model('Product', productSchema);
app.get('/api/products', async (req, res) => {
const products = await Product.find()
.read('secondaryPreferred') // 优先 Secondary,不可用时回退 Primary
.lean();
res.json(products);
});
// 场景 C:报表统计——可容忍分钟级延迟,强制 Secondary
const Order = secondaryConn.model('Order', orderSchema);
app.get('/api/reports/daily', async (req, res) => {
const report = await Order.aggregate([
{ $match: { createdAt: { $gte: yesterday } } },
{ $group: { _id: null, total: { $sum: '$total' }, count: { $sum: 1 } } }
]).read('secondary'); // 强制从 Secondary 读取
res.json(report);
});
// === 3. writeConcern 按数据重要性分级 ===
// 关键数据(支付、余额):w:majority + journal
app.post('/api/payments', async (req, res) => {
const payment = await Payment.create(
{ ...req.body, userId: req.user._id },
{ writeConcern: { w: 'majority', j: true } } // 确保多数节点写入 + 日志落盘
);
res.status(201).json(payment);
});
// 普通数据(评论、浏览记录):w:1(只需 Primary 确认)
app.post('/api/comments', async (req, res) => {
const comment = await Comment.create(
req.body,
{ writeConcern: { w: 1 } } // 只需 Primary 确认,性能优先
);
res.status(201).json(comment);
});
// 批量导入(历史数据迁移):w:0(不等待确认,最快)
app.post('/api/import', async (req, res) => {
await HistoricalData.insertMany(req.body.records, {
writeConcern: { w: 0 }, // 发送后不等确认,批量场景适用
ordered: false // 并行写入,不按顺序
});
res.json({ imported: req.body.records.length });
});
// === 4. 监控复制延迟 ===
app.get('/api/health/replication', async (req, res) => {
const status = await primaryConn.db.adminCommand({ replSetGetStatus: 1 });
const primary = status.members.find(m => m.stateStr === 'PRIMARY');
const secondaries = status.members.filter(m => m.stateStr === 'SECONDARY');
const replicationLag = secondaries.map(s => ({
host: s.name,
lagSeconds: primary.optimeDate.getTime() - s.optimeDate.getTime(),
health: s.health
}));
const maxLag = Math.max(...replicationLag.map(s => s.lagSeconds / 1000));
const healthy = maxLag < 10; // 延迟 > 10 秒视为不健康
res.json({ healthy, maxLagSeconds: maxLag, secondaries: replicationLag });
});
输出:金融操作从 Primary 读写保证强一致;商品列表优先读 Secondary 减轻 Primary 压力;writeConcern 按数据重要性从 w:majority 到 w:0 分级;复制延迟监控确保 Secondary 不落后太多。
读写分离的注意事项:1. readPreference 选项——primary(只读主)、primaryPreferred(优先主)、secondary(只读从)、secondaryPreferred(优先从)、nearest(最低延迟);2. 复制延迟陷阱——Secondary 数据可能落后 Primary 数秒,关键业务(余额、库存)必须从 Primary 读取;3. writeConcern 的 w:majority 保证至少 N/2+1 个节点确认写入——3 节点集群需要 2 个节点确认;4. j:true 要求数据写入 journal 才返回——最安全但最慢,仅用于关键数据;5. 读写分离不是银弹——读多写少场景收益大,读写均衡场景收益有限。
❓ 常见问题
📖 小节
- 副本集:Primary + Secondary + Arbiter
- 选举机制:基于 Raft,需多数票
- 数据同步:initial sync + oplog tailing
- 读写分离:readPreference
- Docker 部署 3 节点
- 故障转移:自动选举 + 应用重连
📝 作业
- 基础题(⭐):用 Docker Compose 部署 3 节点副本集。
- 基础题(⭐):初始化副本集并验证状态(rs.status())。
- 进阶题(⭐⭐):测试读写分离(readPreference: secondary)。
- 进阶题(⭐⭐):模拟 Primary 宕机,观察自动选举。
- 挑战题(⭐⭐⭐):完整副本集部署(3 节点 + 副本集监控 + 故障恢复测试)。