复制集、分片与事务

副本集(Replica Set)

副本集是一组保存相同数据的 mongod 实例,包含:

  • 1 个主节点(Primary):接收所有写操作。
  • 多个从节点(Secondary):异步复制 oplog,可承担读请求。
  • 可选仲裁节点(Arbiter):只参与投票,不存数据。
// 启动三个 mongod 后,初始化副本集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1:27017" },
    { _id: 1, host: "mongo2:27017" },
    { _id: 2, host: "mongo3:27017" }
  ]
})

rs.status()          // 查看成员状态
rs.stepDown()        // 主动让主节点降级(演练切换)

选举与故障切换

  • 主节点故障时,其余节点在 electionTimeoutMillis(默认 10s)后发起选举。
  • 多数派(majority)通过即成为新主,保证同一时刻只有一个主。
  • 成员数应为奇数,或添加仲裁者,避免脑裂与投票僵局。

读写关注(Write/Read Concern)

写关注决定「写操作需要被多少个节点确认」:

writeConcern含义
{ w: 1 }主节点确认即可(快,可能丢)
{ w: "majority" }多数派确认(安全,延迟略高)
{ w: 0 }不等待确认(最快,不可靠)
{ j: true }等待写入 journal

读关注决定读操作能看到的一致性级别:

readConcern含义
local默认,读本地最新数据
majority只读已多数派确认的数据(不会因回滚丢失)
snapshot快照隔离,用于事务
db.orders.insertOne(
  { userId: 7, amount: 99.5, status: "paid" },
  { writeConcern: { w: "majority", j: true } }
)

db.orders.find({ userId: 7 }).readConcern("majority")

读偏好(readPreference) 决定从哪个节点读:primaryprimaryPreferredsecondarysecondaryPreferrednearest

Tip

关键写入统一使用 { w: "majority", j: true },否则主节点故障切换时可能丢失已返回成功的写入。对一致性敏感的读取使用 readConcern: "majority" + readPreference: "primary",避免读到尚未确认或将被回滚的数据。

分片集群(Sharding)

当单机容量或写入达到瓶颈,用分片水平扩展。分片集群由三类角色组成:

              ┌───────────────┐
   客户端 ──► │  mongos 路由   │
              └───────┬───────┘

      ┌───────────────┼───────────────┐
      ▼               ▼               ▼
  ┌────────┐     ┌────────┐      ┌────────┐
  │ shard1 │     │ shard2 │      │ shard3 │   ← 每个分片是一个副本集
  └────────┘     └────────┘      └────────┘

          ┌───────────┴───────────┐
          │   config servers      │   ← 存放元数据
          └───────────────────────┘
  • mongos:查询路由,客户端只连它。
  • shard:实际存储分片数据(每个分片是高可用副本集)。
  • config server:保存集群元数据与分片键。

分片键选择

sh.enableSharding("demo")
sh.shardCollection("demo.orders", { userId: "hashed" })   // 哈希分片(均匀)
sh.shardCollection("demo.events", { createdAt: 1 })        // 范围分片(易热点)
sh.status()
分片策略优点缺点
范围分片范围查询高效递增键易造成写热点
哈希分片写入均匀范围查询需广播

分片键一旦选定不可更改,且需满足:高基数、写入分散、查询常带该键(否则广播到所有分片)。

多文档事务

MongoDB 4.0+ 支持副本集上的多文档事务,4.2+ 支持分片集群事务:

const session = db.getMongo().startSession()
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
})
try {
  const accounts = session.getDatabase("demo").accounts
  accounts.updateOne({ _id: 1 }, { $inc: { balance: -100 } }, { session })
  accounts.updateOne({ _id: 2 }, { $inc: { balance: 100 } }, { session })
  session.commitTransaction()
} catch (e) {
  session.abortTransaction()
  throw e
} finally {
  session.endSession()
}
session, err := client.StartSession()
if err != nil {
    return err
}
defer session.EndSession(ctx)

_, err = session.WithTransaction(ctx, func(sc mongo.SessionContext) (any, error) {
    if _, err := accounts.UpdateOne(sc, bson.M{"_id": 1}, bson.M{"$inc": bson.M{"balance": -100}}); err != nil {
        return nil, err
    }
    if _, err := accounts.UpdateOne(sc, bson.M{"_id": 2}, bson.M{"$inc": bson.M{"balance": 100}}); err != nil {
        return nil, err
    }
    return nil, nil
})
Warning

MongoDB 事务有时间与开销限制(默认 60 秒),且性能低于单文档操作。能用单文档原子操作($inc$set)或合理嵌入解决时,不要轻易上多文档事务。

小结

  • 副本集提供高可用与读写分离,写关注 majority 是防丢数据的关键。
  • 分片突破单机容量,分片键选择决定集群的扩展性与查询效率。
  • 多文档事务可用但有代价,优先用单文档原子操作与合理建模替代。
  • 成员数取奇数、关键写入走多数派,是稳定集群的基本要求。