Files
Cloud-book/数据库/MongoDB_2025/MongoDB副本集.md
2025-08-27 17:10:05 +08:00

188 lines
7.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 副本集Replica Set
为了提供高可用性和数据冗余MongoDB 使用副本集Replica Set的机制。本章节将详细介绍副本集的概念、架构、工作原理以及如何配置和管理一个副本集确保数据库服务能够抵御单点故障。
---
## 副本集概述
### 什么是副本集?
副本集是一组维护相同数据集的 `mongod` 进程。它由一个**主节点 (Primary)** 和多个**从节点 (Secondary)** 组成,共同保证了数据的冗余和高可用性。
- **主节点 (Primary)**: 接收所有的写操作。一个副本集在任何时候最多只能有一个主节点。
- **从节点 (Secondary)**: 从主节点异步复制数据。从节点可以接受读操作,从而分担主节点的读负载。
### 副本集的目标
1. **数据冗余 (Data Redundancy)**: 数据在多个服务器上有副本,防止因单台服务器硬件故障导致的数据丢失。
2. **高可用性 (High Availability)**: 当主节点发生故障时副本集会自动进行故障转移Failover从剩下的从节点中选举出一个新的主节点从而保证服务的持续可用。
3. **读写分离 (Read Scaling)**: 客户端可以将读请求路由到从节点,从而分散读负载,提高读取性能。
---
## 副本集架构与工作原理
### 副本集成员
一个典型的副本集包含以下成员:
- **主节点 (Primary)**: 唯一的写操作入口。
- **从节点 (Secondary)**: 复制主节点的数据,可以处理读请求。从节点也可以被配置为:
- **优先级为 0 的成员 (Priority 0 Member)**: 不能被选举为主节点,适合用于备份或离线分析。
- **隐藏成员 (Hidden Member)**: 对客户端不可见,不能处理读请求,通常用于备份。
- **延迟成员 (Delayed Member)**: 数据会比主节点延迟一段时间,可用于恢复误操作的数据。
- **仲裁者 (Arbiter)**: 只参与选举投票,不存储数据副本。它的存在是为了在成员数量为偶数时,打破平局,确保能够选举出主节点。
### 数据同步(复制)
- 主节点记录其所有的操作到一个特殊的 capped collection 中,称为 **oplog (operations log)**
- 从节点持续地从主节点的 oplog 中拉取新的操作,并在自己的数据集上应用这些操作,从而保持与主节点的数据同步。
- 这个过程是异步的,因此从节点的数据可能会有轻微的延迟。
### 选举过程Failover
当主节点在一定时间内(默认为 10 秒)无法与副本集中的其他成员通信时,会触发一次选举。
1. **触发选举**: 从节点发现主节点不可达。
2. **选举投票**: 剩下的健康成员会进行投票选举一个新的主节点。投票的依据包括成员的优先级、oplog 的新旧程度等。
3. **产生新的主节点**: 获得大多数票数(`(N/2) + 1`,其中 N 是副本集总成员数)的从节点会成为新的主节点。
4. **恢复同步**: 旧的主节点恢复后,会作为从节点重新加入副本集。
---
## 读写关注点Read and Write Concern
### 写关注点 (Write Concern)
写关注点决定了写操作在向客户端确认成功之前,需要被多少个副本集成员确认。
- **`w: 1` (默认)**: 写操作只需在主节点成功即可返回。
- **`w: "majority"`**: 写操作需要被大多数(`(N/2) + 1`)数据承载成员确认后才返回。这是推荐的设置,可以防止在故障转移期间发生数据回滚。
- **`j: true`**: 要求写操作在返回前写入到磁盘日志journal
### 读偏好 (Read Preference)
读偏好决定了客户端从哪个成员读取数据。
- **`primary` (默认)**: 只从主节点读取,保证数据最新。
- **`primaryPreferred`**: 优先从主节点读取,如果主节点不可用,则从从节点读取。
- **`secondary`**: 只从从节点读取,可以分担主节点负载,但可能读到稍有延迟的数据。
- **`secondaryPreferred`**: 优先从从节点读取,如果没有可用的从节点,则从主节点读取。
- **`nearest`**: 从网络延迟最低的成员读取,不关心是主节点还是从节点。
---
## 实践操作
### 需求描述
某电商公司的 MongoDB 数据库需要实现高可用性架构,要求:
1. 数据库服务 24/7 不间断运行
2. 单节点故障时自动故障转移
3. 支持读写分离以提升性能
4. 数据冗余备份防止数据丢失
我们需要搭建一个三成员副本集来满足这些需求,并验证其高可用性和读写分离功能。
### 实践细节和结果验证
```shell
# 1. 创建数据目录
mkdir -p /data/rs{1,2,3}
# 2. 启动三个 mongod 实例
# 实例 1 (Primary 候选)
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 实例 2 (Secondary 候选)
mongod --port 27028 --dbpath /data/rs2 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs2.log
# 实例 3 (Secondary 候选)
mongod --port 27029 --dbpath /data/rs3 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs3.log
# 3. 连接到第一个实例并初始化副本集
mongosh --port 27027
# 在 mongo shell 中执行以下命令
# 初始化副本集配置
config = {
_id: "myReplicaSet",
members: [
{ _id: 0, host: "localhost:27027", priority: 2 },
{ _id: 1, host: "localhost:27028", priority: 1 },
{ _id: 2, host: "localhost:27029", priority: 1 }
]
}
# 执行初始化
rs.initiate(config)
# 等待几秒后验证副本集状态
rs.status()
# 预期结果:显示一个 PRIMARY 节点和两个 SECONDARY 节点
# 4. 测试写操作(在主节点执行)
use testDB
db.products.insertOne({name: "iPhone 14", price: 999, stock: 100})
db.products.insertOne({name: "MacBook Pro", price: 2499, stock: 50})
# 验证写入成功
db.products.find()
# 预期结果:显示刚插入的两条记录
# 5. 测试读写分离
# 连接到副本集不设置读偏好,默认从主节点读取
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet"
# 数据来源一直为 27027
use testDB
myReplicaSet [primary] test> db.products.find().explain().serverInfo
# 连接到副本集并设置读偏好为从节点
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet&readPreference=secondary"
# readPreference 仅控制查询路由,不改变连接目标,但数据来源为 27028/27029
use testDB
db.products.find().explain().serverInfo
# 6. 模拟故障转移测试
# 在另一个终端中找到主节点进程并停止
ps aux | grep "mongod.*27027"
kill <主节点进程ID>
# 回到 mongo shell 观察选举过程
rs.status()
# 等待 10-30 秒后再次检查
rs.status()
# 预期结果:原来的一个从节点变成新的主节点
# 例如 localhost:27028 变成 PRIMARY 状态
# 7. 验证故障转移后的读写功能
# 在新主节点上执行写操作
mongosh --port 27028
use testDB
db.products.insertOne({name: "iPad Air", price: 599, stock: 75})
# 验证数据写入成功
db.products.find()
# 预期结果:显示包括新插入记录在内的所有数据
# 8. 恢复故障节点
# 重新启动之前停止的节点
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 验证节点重新加入副本集
rs.status()
# 预期结果localhost:27027 重新出现在成员列表中,状态为 SECONDARY因为 27027 节点优先级配置更高会主动出发选举并抢占主节点角色这是MongoDB副本集设计的正常行为
# 9. 验证数据同步
# 连接到恢复的节点
mongosh --port 27027
use testDB
db.getMongo().setReadPref("secondary")
db.products.find()
# 预期结果:显示所有数据,包括故障期间插入的记录,证明数据同步正常
# [扩展] 10. 性能测试 - 验证读写分离效果
```