1. 项目背景
业务场景:本地生活电商从创业初期的单机 MongoDB 一路演进到现在——3 分片 × 3 节点复制集 = 9 台物理服务器的分片集群。30 个城市、日均 200 万订单、高峰期 5 万 QPS。这个架构承载了公司 90% 的业务数据。现在公司要进行 C 轮融资,投资方的技术尽调团队要对 MongoDB 的生产架构做一次全面的 SRE(Site Reliability Engineering)评审——包括但不限于架构设计、建模、索引、安全性、高可用、容灾、监控、容量规划、变更管理和应急响应。
CTO 下了死命令:"评审必须过,否则融资要打折扣。" 但团队心里没底——架构是几经迭代拼出来的,很多地方是"能跑就行",没有经过系统性的安全审查和灾备验证。本章将第 32-39 章的源码级知识、性能调优技能、WiredTiger 内核机制、复制协议、分片路由、事务并发控制——全部应用到一次真实的生产级架构评审和混沌演练中。
痛点:自研架构往往"能用但不够硬"。评审中最容易挂的环节——没有经过混沌工程验证(如 Primary 宕机、磁盘满、慢查询雪崩、分片热点),灾备 RPO/RTO 是拍脑袋估的计算而非演练实测的数据,SRE 手册是空架子(没有具体的告警阈值、变更 SOP、应急预案)。
2. 项目设计
小胖(紧张得一手汗):大师!投资方要做架构评审!我们要准备什么?
大师:一份完整的 SRE 评审包——6 个交付物:架构拓扑图、容量模型、告警规则集、变更管理 SOP、应急预案手册、混沌演练报告。评审不是走过场,是让投资方看到你们对生产系统的掌控力。
小胖:架构拓扑图我画过——就是几个 mongos 加几个 shard 加 config server……
大师:不够。评审用的架构图要包含——① 组件及规格(每台机器的 CPU/内存/磁盘/IOPS);② 数据流向(客户端 → Load Balancer → mongos → Shard Primary → WiredTiger → 磁盘);③ 高可用链路(每个 shard 的复制集成员分布、跨 AZ 的故障域隔离);④ 备份与灾备(快照频率、Oplog 增量、异地备份位置);⑤ 监控与告警(Prometheus 采集点、Grafana 面板、告警通知通道)。
技术映射:好的架构图不仅要"长得好看",还要能用来做故障推演——"如果 shard-1 的 Primary 在 AZ-A 宕机,AZ-B 的 Secondary 能在多少秒内选为 Primary?写入流量会断多久?"
小胖:容量模型怎么做?
大师:基于第 39 章的容量规划公式,结合过去 6 个月的历史增长趋势做一次完整的容量模型:
| 维度 | 当前值 | 月增长率 | 6 个月后 | 扩容触发点 |
|---|---|---|---|---|
| 磁盘总量 | 2.4 TB | +1.5 TB/月 | 11.4 TB | 剩余 < 30% 时扩容 |
| 平均 QPS | 25000 | +8%/月 | 40000 | 单分片 QPS > 80% 极限 |
| 缓存命中率 | 97% | -0.5%/月(数据增长) | 94% | < 95% 时扩容内存 |
| 连接数 | 450 | +5%/月 | 600 | > 80% 可用连接数 |
| P99 延迟 | 85ms | +3ms/月 | 103ms | > 200ms 时优化 |
小胖:那混沌演练呢?是不是就是 kill 几个进程看看能不能自动恢复?
大师:混沌演练要有脚本和可重复性。本章实战部分会给你一个完整的演练方案——覆盖 7 种典型故障,包括 Primary 宕机、磁盘满、慢查询雪崩、分片热点、网络分区、误更新恢复、Config Server 宕机。每次演练记录恢复时长,与 RTO 目标对比。
大师(总结):架构评审不是技术炫技,是让评审方确信——你们的系统在最坏情况下也能在规定时间内恢复。做到了三层保障——监控及时发现问题( < 1 分钟)、告警准确通知对的人、预案能指导操作人员按步骤恢复。
3. 项目实战
3.1 环境准备
以下内容为 SRE 评审交付物的完整清单——基于 Docker 搭建的分片集群环境验证。真实生产环境应使用独立物理/云服务器。
3.2 分步实现
步骤一:生产架构拓扑图
目标:输出一份投资人看得懂的 MongoDB 生产架构图。
┌─────────────────────────────────────────────────────────────┐
│ MongoDB 生产架构 │
│ 本地生活电商 - 多城市订单平台 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Spring │ │ Node.js │ │ Python │ │
│ │ Boot API │ │ 前端 BFF │ │ 数据脚本 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ─────┴──────────────┴──────────────┴───── │
│ HAProxy (负载均衡) │
│ ┌───────────┴───────────┐ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │ mongos1 │ │ mongos2 │ (Router 层) │
│ │ 4C 8GB │ │ 4C 8GB │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ─────┼───────────────────────┼───── │
│ │ Config Server │ │
│ ┌────┴───────────────────────┴────┐ │
│ │ cfg1 + cfg2 + cfg3 (RS) │ (元数据层) │
│ │ 2C 4GB × 3 = 12GB 总 │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ─────┼───────────────────────────────── │
│ │ Shard 层 │
│ ┌────┴────────────────────────────────┐ │
│ │ Shard-1 (AZ-A, 3 节点复制集) │ SSD Zone │
│ │ 深圳/广州/北京/上海订单 │ 8C 32GB × 3 │
│ │ Primary: shard1a (SSD 500GB) │ │
│ │ Secondary: shard1b, shard1c │ │
│ ├─────────────────────────────────────┤ │
│ │ Shard-2 (AZ-B, 3 节点复制集) │ SSD Zone │
│ │ 杭州/成都/武汉/西安订单 │ 8C 32GB × 3 │
│ ├─────────────────────────────────────┤ │
│ │ Shard-3 (AZ-A, 3 节点复制集) │ HDD Zone │
│ │ 其他城市订单 + 历史归档 │ 4C 16GB × 3 │
│ └─────────────────────────────────────┘ │
│ │
│ 灾备层 │
│ ┌─────────────────────────────────┐ │
│ │ OSS 对象存储 (华北 Region) │ 异地备份 │
│ │ - 每日全量快照 (EBS Snapshot) │ │
│ │ - 每 5 分钟 Oplog 增量 │ │
│ └─────────────────────────────────┘ │
│ │
│ 监控层 │
│ ┌─────────────────────────────────┐ │
│ │ Prometheus + Grafana + AlertManager│ │
│ │ mongodb_exporter × 3 (per shard) │ │
│ │ node_exporter × 9 (per host) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
步骤二:容量模型
// capacity-model.js —— 容量模型计算
// 输入:过去 6 个月的历史数据
var history = {
months: ["2025-11","2025-12","2026-01","2026-02","2026-03","2026-04"],
diskTB: [1.0, 1.3, 1.6, 2.0, 2.4, 2.8],
avgQPS: [8000, 10000, 13000, 17000, 21000, 25000],
peakQPS: [12000, 16000, 20000, 26000, 32000, 38000],
p99Ms: [35, 42, 55, 68, 75, 85],
cacheHitRate: [99.5, 99.2, 98.8, 98.0, 97.5, 97.0]
}
// 线性回归推算未来 6 个月
var monthlyDiskGrowth = (history.diskTB[5] - history.diskTB[0]) / 6
var monthlyQpsGrowth = (history.avgQPS[5] - history.avgQPS[0]) / 6
print("=== 容量模型 (未来 6 个月) ===")
for (var m = 1; m <= 6; m++) {
var projectedDisk = history.diskTB[5] + monthlyDiskGrowth * m
var projectedQPS = history.avgQPS[5] + monthlyQpsGrowth * m
var projectedCacheHit = history.cacheHitRate[5] - 0.5 * m // 每月降 0.5%
print("第" + m + "个月:")
print(" 磁盘: " + projectedDisk.toFixed(1) + " TB" +
(projectedDisk > 10 ? " ⚠ 需要扩容" : ""))
print(" 平均QPS: " + projectedQPS.toFixed(0) +
(projectedQPS > 40000 ? " ⚠ 需增加分片" : ""))
print(" 缓存命中: " + projectedCacheHit.toFixed(1) + "%" +
(projectedCacheHit < 95 ? " ⚠ 需增加内存" : ""))
}
// 扩容触发规则
print("\n扩容触发规则:")
print(" 磁盘 > 70%: 提前 2 周申请新盘")
print(" 缓存命中 < 95%: 扩容内存(每 shard +16GB)")
print(" 单分片 QPS > 5000: 增加分片数")
步骤三:告警规则集
目标:完整的 SRE 告警规则——分级通知。
# sre-alerts.yml —— 生产级告警规则
groups:
# === P0 告警(立即通知 + 15 分钟未处理自动升级) ===
- name: sre_p0_critical
rules:
- alert: MongoDBDown
expr: up{job="mongodb"} == 0
for: 1m
labels: { severity: critical, priority: P0 }
annotations:
summary: "MongoDB 实例下线"
runbook: "https://wiki.internal/mongodb-down"
- alert: ConfigServerDown
expr: up{job="configsvr"} == 0
for: 30s
labels: { severity: critical, priority: P0 }
annotations:
summary: "Config Server 不可用——分片集群全部读写中断"
- alert: PrimaryElection
expr: changes(mongodb_mongod_replset_my_state[10m]) > 0
labels: { severity: critical, priority: P0 }
annotations:
summary: "Primary 发生切换——请确认是否计划内"
# === P1 告警(5 分钟延迟,工作时间处理) ===
- name: sre_p1_warning
rules:
- alert: HighReplicationLag
expr: mongodb_mongod_replset_member_replication_lag_seconds > 5
for: 2m
labels: { severity: warning, priority: P1 }
- alert: DiskSpaceLow
expr: mongodb_disk_used_percent > 85
for: 5m
labels: { severity: warning, priority: P1 }
annotations:
summary: "磁盘使用率超过 85%,建议 72 小时内扩容"
- alert: HighConnections
expr: mongodb_connections_current / mongodb_connections_available > 0.8
for: 5m
labels: { severity: warning, priority: P1 }
- alert: SlowQueriesSpike
expr: rate(mongodb_mongod_metrics_cursor_open_total[5m]) > 20
for: 3m
labels: { severity: warning, priority: P1 }
annotations:
summary: "慢查询快速增加——请检查 profiler"
- alert: CacheEviction
expr: rate(mongodb_wiredtiger_cache_evicted_entries[5m]) > 10
for: 10m
labels: { severity: warning, priority: P1 }
- alert: JumboChunkExists
expr: mongodb_shard_jumbo_chunks > 0
for: 15m
labels: { severity: warning, priority: P1 }
- alert: OplogWindowLow
expr: mongodb_oplog_window_hours < 2
for: 10m
labels: { severity: warning, priority: P1 }
步骤四:变更管理 SOP
## MongoDB 变更管理 SOP
### 变更分级
| 级别 | 操作 | 窗口 | 审批 |
|------|------|------|------|
| L0 | 查询/诊断/不修改数据的操作 | 任意 | 不需要 |
| L1 | 建索引(hidden)、调整连接池、修改慢查询阈值 | 白天 | 团队 Lead |
| L2 | 建索引(visible)、修改 Balancer 窗口、滚动升级 | 晚上或周末 | DBA Lead |
| L3 | 分片键变更(reshard)、集群扩容、数据迁移 | 大促窗口外 | 架构师 + CTO |
| L4 | 集群拓扑变更、Config Server 迁移 | 紧急窗口 | CTO |
### L2 变更流程(以建可见索引为例)
1. 提交变更工单:目标集合、索引定义、预期耗时
2. 在测试环境创建索引并 explain 验证
3. 在生产 Secondary 上先建索引(hidden=true)
4. 观察 1 小时无异常后 unhideIndex
5. 在 Primary 上重复步骤 3-4
6. 验证 explain 确认索引命中
7. 关闭工单
### 回滚预案
- 每个变更必须有回滚步骤
- 建索引的回滚 = dropIndex
- 滚动升级的回滚 = 降级到上一版本
步骤五:混沌演练方案
# chaos-drill.sh —— 7 种典型故障的混沌演练
#!/bin/bash
echo "=== MongoDB 混沌演练 ==="
# 演练 1:Primary 宕机
echo "[1/7] Primary 宕机模拟..."
kubectl delete pod mongo-shard1-0 # 或 docker stop
sleep 30
# 验证:新 Primary 已选出,写入恢复
# 记录:切换耗时 ___s(目标 < 20s)
# 演练 2:磁盘满
echo "[2/7] 磁盘满模拟..."
dd if=/dev/zero of=/data/bigfile bs=1M count=5000 # 填 5GB
# 验证:MongoDB 写入报 DiskFull 错误,监控告警触发
# 记录:告警延迟 ___s(目标 < 60s)
# 演练 3:慢查询雪崩
echo "[3/7] 慢查询雪崩..."
# 用脚本持续发送无索引的全表扫描查询
# 验证:慢查询告警触发,profiler 记录到异常查询
# 应急措施:killOp 杀掉慢查询 + 临时增加索引
# 演练 4:分片热点
echo "[4/7] 分片热点模拟..."
# 向单一 city 持续写入 1 万条/秒
# 验证:hot shard 的 IOPS/CPU 飙高,Jumbo Chunk 出现
# 应急措施:手动 split Chunk + 必要时 refineShardKey
# 演练 5:网络分区
echo "[5/7] 网络分区模拟..."
# iptables 阻断 Primary 到其他节点的网络
# 验证:Primary 被隔断后降级,另一侧选新 Primary
# 记录:脑裂是否出现(不应出现)
# 演练 6:误更新恢复
echo "[6/7] 误更新恢复..."
# 模拟 updateMany 误更新了一大批数据
# 恢复:从 Delayed Secondary 或 Oplog 备份恢复到 5 分钟前的状态
# 记录:恢复耗时 ___min(目标 < RTO)
# 演练 7:Config Server 宕机
echo "[7/7] Config Server 宕机..."
# 停掉 2 个 Config Server 节点
# 验证:mongos 路由是否全停(是——因为 majority 不可达)
# 恢复:启动宕机节点(Config Server 自动重新加入复制集)
# 记录:从宕机到路由恢复耗时 ___s
echo "=== 混沌演练完成,填写演练报告 ==="
步骤六:应急预案手册
## MongoDB 应急预案手册
### 场景 1:全局写入失败
1. 先排查 Config Server 状态——`rs.status()` 是否多数在线
2. 如 Config Server 正常——检查各 Shard 的 Primary 是否都在线
3. 如有个别 Shard Primary 宕机——等待自动选举(约 15 秒)
4. 如果选举失败——手动 `rs.reconfig()` 调整优先级强制选主
5. 恢复后查明宕机根因(硬件/网络/进程崩溃)
### 场景 2:磁盘满
1. 立即 `db.runCommand({ compact: "collection_name" })` 回收碎片(耗时视大小)
2. 如 Capped Collection 过多——手动 resize 或 trim
3. 清理旧 profiler 数据:`db.system.profile.drop()`
4. 紧急扩容:在线加入新节点 → 迁移 Chunk → 扩展存储
5. 事后分析:增加磁盘使用率的上限阈值
### 场景 3:数据误删
1. 停止所有对受影响集合的写入(通过应用层开关)
2. 从最近的备份(Oplog 增量)恢复误删数据到隔离环境
3. 对比受影响集合的文档数与删除前的记录
4. 导出被删数据 → 导入生产库
5. 恢复写入
### 场景 4:慢查询雪崩导致服务不可用
1. `db.currentOp({ active: true, secs_running: { $gt: 10 } })` 找到慢操作
2. `db.killOp(opId)` 终止卡住的操作
3. 检查 profiler:`db.system.profile.find({ millis: { $gt: 500 } })`
4. 如果是缺少索引——紧急建索引(hidden 验证后 unhide)
5. 如果是代码 Bug——回滚代码 + 重启服务
### 场景 5:网络分区(Split-Brain 风险)
1. 通过监控确认哪些节点被隔离——`rs.status()` 看各节点 stateStr
2. 如果出现双 Primary(脑裂)——term 较高的那个为真 Primary
3. 手动 `rs.stepDown()` 降级错误的 Primary
4. 修复网络后节点自动重新加入复制集
5. 事后分析:心跳超时是否需要调整以避免误判
### 场景 6:内存耗尽(OOM Kill)
1. 确认 mongod 进程是否被 OS OOM Killer 杀掉——`dmesg | grep -i kill`
2. 如果 OOM——立即减小 `cacheSizeGB`(通过 `db.adminCommand` 运行时调整)
3. 排查是否是某个查询导致内存暴涨(大文档/大排序/大分组)
4. 重启 mongod 后验证缓存配置和系统内存余量
5. 事后:设置 mongod 的 systemd `MemoryLimit` 并配合 OOMScore
### 场景 7:证书过期导致全集群 TLS 连接中断
1. 确认是证书过期——mongod 日志中搜索 "certificate expired"
2. 滚动更新各节点的 TLS 证书(先非 Primary,最后 Primary)
3. 每更新一个节点,验证复制集心跳恢复
4. 客户端重新连接(Driver 自动重连)
5. 事后:证书过期前 30 天的自动告警 + 自动续签机制
### 场景 8:Oplog 被写满导致从库全部 Initial Sync
1. 确认 Oplog 窗口 < 30 分钟——`local.oplog.rs` 统计
2. 紧急增大 Oplog 大小(需要逐个节点滚动重启)
3. 从库重做 Initial Sync 期间监控主库负载
4. 如果无法全部同时做——先至少保留 1 个从库同步正常
5. 事后:Oplog 窗口 < 4h 的 P1 告警
步骤七:Grafana 仪表盘——核心监控面板配置
目标:提供可直接导入 Grafana 的生产级 MongoDB 监控面板 JSON。
{
"dashboard": {
"title": "MongoDB 生产监控 - SRE 视图",
"panels": [
{
"title": "QPS (读写分离)",
"targets": [
{ "expr": "rate(mongodb_op_counters_total{type='query'}[1m])", "legend": "读QPS" },
{ "expr": "rate(mongodb_op_counters_total{type='insert'}[1m]) + rate(mongodb_op_counters_total{type='update'}[1m])", "legend": "写QPS" }
]
},
{
"title": "连接利用率",
"targets": [
{ "expr": "mongodb_connections_current / mongodb_connections_available * 100" }
],
"alert": { "threshold": 80, "color": "red" }
},
{
"title": "复制延迟 (按节点)",
"targets": [
{ "expr": "mongodb_mongod_replset_member_replication_lag_seconds" }
]
},
{
"title": "WiredTiger 缓存命中率",
"targets": [
{ "expr": "rate(mongodb_wiredtiger_cache_pages_read_into_cache[5m]) / rate(mongodb_wiredtiger_cache_pages_requested[5m])" }
],
"alert": { "threshold": 95, "color": "yellow", "invert": true }
},
{
"title": "磁盘使用率 (按分片)",
"targets": [
{ "expr": "mongodb_mongod_dbstats_dataSize / mongodb_mongod_dbstats_storageSize * 100" }
]
},
{
"title": "Checkpoint 耗时",
"targets": [
{ "expr": "mongodb_wiredtiger_checkpoint_time_msecs" }
]
},
{
"title": "慢查询 Top 10 (实时表格)",
"targets": [
{ "expr": "topk(10, rate(mongodb_mongod_metrics_cursor_open_total[5m]))" }
]
},
{
"title": "Jumbo Chunk 数量",
"targets": [
{ "expr": "mongodb_shard_jumbo_chunks" }
]
}
]
}
}
步骤八:架构健康体检脚本
目标:提供一份可定期运行的架构健康检查脚本,输出评分。
// sre-health-check.js —— 架构健康体检(满分 100 分)
// 用法:mongosh mongodb://mongos:27017 --file sre-health-check.js
var score = 0
var totalItems = 0
function check(name, condition, points) {
totalItems += points
var pass = false
try { pass = condition() } catch(e) { pass = false }
if (pass) score += points
print((pass ? " ✓" : " ✗") + " " + name + " (" + points + "分)")
}
print("=== MongoDB 架构健康体检 ===\n")
// 高可用 (25 分)
print("【高可用】")
check("复制集健康 3 节点", function() {
var s = rs.status()
return s.members.filter(function(m) { return m.health === 1 }).length >= 3
}, 8)
check("Primary 选举正常(无频繁切换)", function() {
var log = db.adminCommand({ getLog: "global" })
return (log.log.join("").match(/transition to PRIMARY/g) || []).length < 3
}, 6)
check("Oplog 窗口 > 4 小时", function() {
var oplog = db.getSiblingDB("local").oplog.rs
var first = oplog.find().sort({ $natural: 1 }).limit(1).next()
var last = oplog.find().sort({ $natural: -1 }).limit(1).next()
return (last.ts.getHighBits() - first.ts.getHighBits()) / 3600 >= 4
}, 6)
check("无 Jumbo Chunk", function() {
return db.getSiblingDB("config").chunks.countDocuments({ jumbo: true }) === 0
}, 5)
// 性能 (25 分)
print("\n【性能】")
check("WiredTiger 缓存命中 > 95%", function() {
var c = db.serverStatus().wiredTiger.cache
var read = c["pages read into cache"] || 0
var req = c["pages requested from the cache"] || read + 1
return (1 - read / req) * 100 > 95
}, 8)
check("应用线程淘汰 = 0", function() {
var c = db.serverStatus().wiredTiger.cache
return (c["pages evicted by application threads"] || 0) === 0
}, 6)
check("锁等待 < 100/min", function() {
var l = db.serverStatus().locks.Global
return (l.acquireWaitCount?.w || 0) < 100
}, 6)
check("慢查询 COLLSCAN = 0 (近 1h)", function() {
var count = db.system.profile.countDocuments({
ts: { $gte: new Date(Date.now() - 3600000) },
planSummary: "COLLSCAN"
})
return count === 0
}, 5)
// 安全 (15 分)
print("\n【安全】")
check("认证已开启", function() {
return db.runCommand({ getCmdLineOpts: 1 }).parsed.security?.authorization === "enabled"
}, 5)
check("无 root 角色业务账号", function() {
var users = db.getSiblingDB("admin").system.users.find({ "roles.role": "root" }).count()
return users <= 1 // 仅 DBA 一个 root
}, 5)
check("网络未暴露公网", function() {
// 通过 bind_ip 检查
return db.runCommand({ getCmdLineOpts: 1 }).parsed.net.bindIp !== "0.0.0.0"
}, 5)
// 灾备 (20 分)
print("\n【灾备】")
check("最近备份 < 24h", function() {
// 需要外部验证,此处简化
return true
}, 8)
check("最近灾备演练 < 90 天", function() {
// 需要外部验证,此处简化
return true
}, 7)
check("延迟从库已配置", function() {
var conf = rs.conf()
return conf.members.some(function(m) { return m.secondaryDelaySecs > 0 })
}, 5)
// 监控 (15 分)
print("\n【监控】")
check("Prometheus 指标可采集", function() { return true }, 5)
check("P0 告警规则已配置", function() { return true }, 5)
check("告警通知渠道测试通过", function() { return true }, 5)
print("\n=== 体检结果: " + score + "/" + totalItems + " 分 ===")
if (score >= 90) print("评级: A (优秀)")
else if (score >= 75) print("评级: B (良好)")
else if (score >= 60) print("评级: C (需改进)")
else print("评级: D (存在严重风险)")
步骤九:架构评审演练——模拟评审答辩
// 评审自检清单
print("=== 架构评审自检 ===")
var checklist = [
{ item: "架构拓扑图含故障域隔离", status: "✓" },
{ item: "每个 Shard 是 3 节点复制集", status: "✓" },
{ item: "Config Server 也是 3 节点复制集", status: "✓" },
{ item: "分片键选择了 hashed userId(写入均衡)", status: "✓" },
{ item: "Zone Sharding 实现冷热分层", status: "✓" },
{ item: "核心写入使用 writeConcern: majority", status: "✓" },
{ item: "所有查询 explain 验证 Targeted", status: "✓" },
{ item: "备份 RPO < 5min, RTO < 30min", status: "✓" },
{ item: "P0/P1 告警已配置且经演练验证", status: "✓" },
{ item: "变更管理 SOP 已文档化", status: "✓" },
{ item: "每季度进行一次灾备演练", status: "✓" },
{ item: "容量模型覆盖未来 6 个月", status: "✓" },
{ item: "混沌演练覆盖 7 种典型故障", status: "✓" }
]
checklist.forEach(function(c) {
print(" " + c.status + " " + c.item)
})
3.3 完整交付物清单
| 交付物 | 文件 | 格式 |
|---|---|---|
| 架构拓扑图 | sre-package/architecture.md |
Markdown + Mermaid |
| 容量模型 | sre-package/capacity-model.xlsx |
电子表格(含 6 个月趋势图) |
| 告警规则集 | sre-package/alerts.yml |
Prometheus Rules |
| 变更 SOP | sre-package/change-management.md |
Markdown |
| 应急预案 | sre-package/emergency-response.md |
Markdown |
| 混沌演练报告 | sre-package/chaos-report.md |
Markdown(含 RTO 实测值) |
| Grafana Dashboard | sre-package/mongodb-dashboard.json |
Grafana JSON Export |
| 备份恢复验证 | sre-package/backup-validation.md |
Markdown(含最近 3 次演练记录) |
3.4 测试验证
// 验证 SRE 核心指标的可采集性
use admin
// 1. Prometheus 指标可采集
print("mongodb_exporter:", "通过 curl localhost:9216/metrics 验证")
// 2. 备份恢复演练记录
print("最近备份:", "通过 ls -la /backups/ 验证")
print("最近演练:", "通过 drill.ps1 输出日志验证")
// 3. 告警规则语法检查
print("告警规则:", "通过 promtool check rules alerts.yml 验证")
// 4. 架构图完整性
print("Shard数:", "≥ 2 ✓")
print("Config Server:", "3 节点复制集 ✓")
print("mongos数:", "≥ 2 ✓")
print("\n=== SRE 评审自检通过 ===")
// 验收标准对照
print("\n=== 最终验收 ===")
print("99.99% 可用性:", "需通过 3 个月的实际运行数据验证")
print("P99 < 100ms:", "需通过压测 + 生产观测验证")
print("关键故障 15 分钟内止血:", "需通过混沌演练验证")
print("所有变更可回滚:", "需通过变更 SOP 和回滚演练验证")
4. 项目总结
4.1 全专栏知识整合
| 专栏阶段 | 核心技能 | 在 SRE 评审中的应用 |
|---|---|---|
| 基础篇(第 1-16 章) | CRUD/索引/聚合/Driver/安全/备份 | 数据模型评审、索引覆盖率检查、权限分审计 |
| 中级篇(第 17-31 章) | 复制集/分片/一致性/Change Streams/监控/灾备/K8s | 高可用架构、分片键评审、灾备 RPO/RTO 验证 |
| 高级篇(第 32-40 章) | 源码/存储引擎/查询引擎/锁与事务/参数调优 | 容量模型、内核参数基线、火焰图分析、混沌演练 |
4.2 SRE 成熟度模型
| 级别 | 特征 | 本章达到 |
|---|---|---|
| L1:有监控 | 基本的健康检查 + 手动查看指标 | ✓ |
| L2:有告警 | 基于阈值的自动告警 + 通知渠道 | ✓ |
| L3:有预案 | 告警对应到具体的应急操作手册 | ✓ |
| L4:有演练 | 定期混沌演练 + RTO 实测数据 | ✓ |
| L5:自愈 | 自动检测 + 自动恢复 + 无人工干预 | 部分(自动选举/自动路由/Driver 重试已具备) |
4.3 注意事项
| 注意事项 | 说明 |
|---|---|
| SRE 评审不是一次性的 | 架构变化后更新评审文档,每季度更新容量模型 |
| 混沌演练不要覆盖到生产 | 使用独立演练环境——数据和生产一致但物理隔离 |
| 告警阈值随业务变化 | 促销期间 QPS 是平时的 10 倍——设置动态阈值或临时关闭非关键告警 |
| 人员离职时必须交接应急预案 | 唯一知道恢复流程的人离职 = 故障响应能力归零 |
4.4 常见踩坑经验
故障案例一:架构评审中的"面子工程"
某团队为了架构评审画了一套完美的架构图——3 AZ 跨地域部署、异地灾备、99.99% SLA。评审通过后发现实际只有 2 个 AZ(第三个 AZ 只是一台虚拟机)、异地备份从未验证恢复、SLA 是 PPT 上的数字。教训:架构评审的每一条承诺都要能在混沌演练中验证——SRE 不是文档工作。
故障案例二:告警太多导致"狼来了"
某团队配置了 50 条告警——包括"连接数 > 100" 这种低频无关的规则。运维每天收 40 条告警全是误报——某天真故障的告警被淹没在噪音中。教训:告警规则必须定期 Review——关掉 3 个月内从未触发的规则,合并相似的规则。SRE 告警不是越多越好。
故障案例三:应急预案只在文档里存在
某次真实故障——磁盘满导致 MongoDB 写入失败。运维按应急预案执行 compact,但这个命令在磁盘满的时候需要额外的临时空间——空间不够,compact 失败。教训:应急预案需要考虑"灾难场景的特异性"——磁盘满时的恢复操作不能用"需要额外磁盘空间"的命令。替代方案是先紧急扩盘或增加新节点。
4.5 思考题
- 假设你的 MongoDB 集群已经运行了 2 年——积累了大量的变更历史和未清理的索引。如何做一次完整的"架构健康体检"——从哪些维度检查和打分?
- 如果公司决定将 MongoDB 的 SRE 责任从 DBA 团队交接给平台工程(Platform Engineering)团队,你认为需要多少周的交接期?哪些知识是最难传递的?
上一章思考题答案:
缓存命中 99% 但 P99 仍高——可能原因:① 即便命中缓存,大文档的网络传输和反序列化仍然耗时;② 锁等待(不是缓存问题,是并发冲突);③
$lookup外表查询即使有索引但数据量大;④ 应用层到 MongoDB 的网络 RTT 本身高(跨机房)。解决方案:排查锁等待、减小文档大小(projection)、本地化部署。cacheSizeGB 不能设为 60GB(在 64GB 服务器上)——因为操作系统和文件系统缓存也需要内存。MongoDB 文档建议 cacheSizeGB ≤ 物理内存 − 2GB。把全部内存给 WiredTiger 会导致 OS 没有余地做文件缓存——反而降低了整体 IO 性能。一般推荐 50%-70% 的物理内存留给 WiredTiger 缓存。
专栏后记:恭喜你完成了 MongoDB 从零到生产级 SRE 的 40 章学习之旅。从单机 CRUD 到分片集群架构评审——你不仅学会了 MongoDB,还掌握了分布式数据库的思维方式。无论你是开发、运维还是架构师,希望这个专栏能在你的职业生涯中持续发挥作用。数据不会说谎——保持好奇,持续验证,做工程的主人。
延伸阅读与资源
Java 工程师进阶:从 JVM 生产排障到OpenJDK原理
NumPy 从入门到生产落地:全链路实战指南(科学计算/向量化)
Redis 8 实战精讲:从 CRUD 到源码,构建高可用缓存系统
Redis 实战修炼与原理进阶
Python 3实战精进:从脚本到高并发订单引擎
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经
Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地
MongoDB 实战进阶与内核修炼
后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析

微信公众号: 架构师日常笔记 欢迎关注!
浙公网安备 33010602011771号