分布式专题(6)之MongoDB复制(副本)集实战及其原理分析

一、MongoDB复制集结构

在生产环境中,不建议使用单机版的MongoDB服务器。原因如下:

  • 单机版的MongoDB无法保证可靠性,一旦进程发生故障或是服务器宕机,业务将直接不可用。
  • 一旦服务器上的磁盘损坏,数据会直接丢失,而此时并没有任何副本可用。

1.1 复制集介绍

Mongodb复制集(Replication Set)由一组Mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点,Mongodb Driver(客户端)的所有数据都写入Primary,Secondary从Primary同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。复制集提供冗余和高可用性,是所有生产部署的基础。它的现实依赖于两个方面的功能:

  • 数据写入时将数据迅速复制到另一个独立节点上
  • 在接受写入的节点发生故障时自动选举出一个新的替代节点

在实现高可用的同时,复制集实现了其他几个附加作用:

  • 数据分发: 将数据从一个区域复制到另一个区域,减少另一个区域的读延迟
  • 读写分离: 不同类型的压力分别在不同的节点上执行
  • 异地容灾: 在数据中心故障时候快速切换到异地

早期版本的MongoDB使用了一种Master-Slave的架构,该做法在MongoDB 3.4版本之后已经废弃。

1.2 三节点复制集模式

常见的复制集架构由3个成员节点组成,其中存在几种不同的模式。

1.2.1 PSS模式(官方推荐的模式)

PSS模式由一个主节点和两个备节点所组成,即Primary+Secondary+Secondary。

此模式始终提供数据集的两个完整副本,如果主节点不可用,则复制集选择备节点作为主节点并继续正常操作。旧的主节点在可用时重新加入复制集。

1.2.2 PAS模式

PSA模式由一个主节点、一个备节点和一个仲裁者节点组成,即Primary+Secondary+Arbiter。

其中,Arbiter节点不存储数据副本,也不提供业务的读写操作。Arbiter节点发生故障不影响业务,仅影响选举投票。此模式仅提供数据的一个完整副本,如果主节点不可用,则复制集将选择备节点作为主节点。

1.3 环境搭建

关于硬件:

  • 因为正常的复制集节点都有可能成为主节点,它们的地位是一样的,因此硬件配置上必须一致;
  • 为了保证节点不会同时宕机,各节点使用的硬件必须具有独立性。

准备配置文件:复制集的每个mongod进程应该位于不同的服务器。我们现在在一台机器上运行3个进程,因此要为它们各自配置:

  • 不同的端口(28017/28018/28019)

  • 不同的数据目录

    mkdir -p /data/db{1,2,3}

  • 不同日志文件路径(例如:/data/db1/mongod.log)

创建配置文件/data/db1/mongod.conf,内容如下:

# /data/db1/mongod.conf
systemLog:
  destination: file
  path: /data/db1/mongod.log # log path
  logAppend: true
storage:   
  dbPath: /data/db1 # data directory      
net:
  bindIp: 0.0.0.0
  port: 28017 # port
replication:
  replSetName: rs0  
processManagement:
  fork: true

分别修改logpath路径,db路径以及端口。

启动 MongoDB 进程:

mongod -f /data/db1/mongod.conf 
mongod -f /data/db2/mongod.conf 
mongod -f /data/db3/mongod.conf

然后进入任意一个节点中:

复制集通过mongosh的rs.initiate()进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。

bash 复制代码
test> rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "47.94.171.130:28017" }, { _id: 1, host: "47.94.171.130:28018" }, { _id: 2, host: "47.94.171.130:28019" }] })
{ ok: 1 }
rs0 [direct: other] test> rs.isMaster()
{
  topologyVersion: {
    processId: ObjectId("676115b603702c78c8740196"),
    counter: Long("6")
  },
  hosts: [
    '47.94.171.130:28017',
    '47.94.171.130:28018',
    '47.94.171.130:28019'
  ],
  setName: 'rs0',
  setVersion: 1,
  ismaster: true,
  secondary: false,
  primary: '47.94.171.130:28017',
  me: '47.94.171.130:28017',
  electionId: ObjectId("7fffffff0000000000000001"),
  lastWrite: {
    opTime: { ts: Timestamp({ t: 1734416435, i: 7 }), t: Long("1") },
    lastWriteDate: ISODate("2024-12-17T06:20:35.000Z"),
    majorityOpTime: { ts: Timestamp({ t: 1734416435, i: 7 }), t: Long("1") },
    majorityWriteDate: ISODate("2024-12-17T06:20:35.000Z")
  },
  maxBsonObjectSize: 16777216,
  maxMessageSizeBytes: 48000000,
  maxWriteBatchSize: 100000,
  localTime: ISODate("2024-12-17T06:20:44.043Z"),
  logicalSessionTimeoutMinutes: 30,
  connectionId: 2,
  minWireVersion: 0,
  maxWireVersion: 17,
  readOnly: false,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1734416435, i: 7 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1734416435, i: 7 }),
  isWritablePrimary: true
}
  • MongoDB 从节点进行读
bash 复制代码
# mongo --port 28018
# 指定从节点可读
rs0:SECONDARY> rs.secondaryOk()
rs0:SECONDARY> db.user.find()

1.3.1 查询复制集状态

rs.status()可查看各成员当前状态,包括是否健康,是否在全量同步,心跳信息,增量同步信息, 选举信息,上一次的心跳时间等。

bash 复制代码
rs0 [direct: secondary] test> rs.status()
{
  set: 'rs0',
  date: ISODate("2024-12-17T07:24:32.369Z"),
  myState: 2,
  term: Long("1"),
  syncSourceHost: '47.94.171.130:28017',
  syncSourceId: 0,
  heartbeatIntervalMillis: Long("2000"),
  majorityVoteCount: 2,
  writeMajorityCount: 2,
  votingMembersCount: 3,
  writableVotingMembersCount: 3,
  optimes: {
    lastCommittedOpTime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
    lastCommittedWallTime: ISODate("2024-12-17T07:24:24.713Z"),
    readConcernMajorityOpTime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
    appliedOpTime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
    durableOpTime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
    lastAppliedWallTime: ISODate("2024-12-17T07:24:24.713Z"),
    lastDurableWallTime: ISODate("2024-12-17T07:24:24.713Z")
  },
  lastStableRecoveryTimestamp: Timestamp({ t: 1734420214, i: 1 }),
  electionParticipantMetrics: {
    votedForCandidate: true,
    electionTerm: Long("1"),
    lastVoteDate: ISODate("2024-12-17T06:20:34.571Z"),
    electionCandidateMemberId: 0,
    voteReason: '',
    lastAppliedOpTimeAtElection: { ts: Timestamp({ t: 1734416423, i: 1 }), t: Long("-1") },
    maxAppliedOpTimeInSet: { ts: Timestamp({ t: 1734416423, i: 1 }), t: Long("-1") },
    priorityAtElection: 1,
    newTermStartDate: ISODate("2024-12-17T06:20:34.612Z"),
    newTermAppliedDate: ISODate("2024-12-17T06:20:35.331Z")
  },
  members: [
    {
      _id: 0,
      name: '47.94.171.130:28017',
      health: 1,
      state: 1,
      stateStr: 'PRIMARY',
      uptime: 3848,
      optime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
      optimeDurable: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
      optimeDate: ISODate("2024-12-17T07:24:24.000Z"),
      optimeDurableDate: ISODate("2024-12-17T07:24:24.000Z"),
      lastAppliedWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      lastDurableWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      lastHeartbeat: ISODate("2024-12-17T07:24:31.628Z"),
      lastHeartbeatRecv: ISODate("2024-12-17T07:24:30.629Z"),
      pingMs: Long("0"),
      lastHeartbeatMessage: '',
      syncSourceHost: '',
      syncSourceId: -1,
      infoMessage: '',
      electionTime: Timestamp({ t: 1734416434, i: 1 }),
      electionDate: ISODate("2024-12-17T06:20:34.000Z"),
      configVersion: 1,
      configTerm: 1
    },
    {
      _id: 1,
      name: '47.94.171.130:28018',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 4463,
      optime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
      optimeDate: ISODate("2024-12-17T07:24:24.000Z"),
      lastAppliedWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      lastDurableWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      syncSourceHost: '47.94.171.130:28017',
      syncSourceId: 0,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1,
      self: true,
      lastHeartbeatMessage: ''
    },
    {
      _id: 2,
      name: '47.94.171.130:28019',
      health: 1,
      state: 2,
      stateStr: 'SECONDARY',
      uptime: 3848,
      optime: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
      optimeDurable: { ts: Timestamp({ t: 1734420264, i: 1 }), t: Long("1") },
      optimeDate: ISODate("2024-12-17T07:24:24.000Z"),
      optimeDurableDate: ISODate("2024-12-17T07:24:24.000Z"),
      lastAppliedWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      lastDurableWallTime: ISODate("2024-12-17T07:24:24.713Z"),
      lastHeartbeat: ISODate("2024-12-17T07:24:31.605Z"),
      lastHeartbeatRecv: ISODate("2024-12-17T07:24:31.603Z"),
      pingMs: Long("0"),
      lastHeartbeatMessage: '',
      syncSourceHost: '47.94.171.130:28017',
      syncSourceId: 0,
      infoMessage: '',
      configVersion: 1,
      configTerm: 1
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1734420264, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1734420264, i: 1 })
}
rs0 [direct: secondary] test> 

members一列体现了所有复制集成员的状态,主要如下:

health:成员是否健康,通过心跳进行检测。

state/stateStr:成员的状态,PRIMARY表示主节点,而SECONDARY则表示备节点,如果节点出现故障,则可能出现一些其他的状态,例如RECOVERY。

uptime:成员的启动时间。

optime/optimeDate:成员同步最后一条oplog的时间。

optimeDurable/optimeDurableDate:成员同步最后一条oplog持久化的时间。

pingMs:成员与当前节点的ping时延。

syncingTo:成员的同步来源。

1.3.2 查看当前节点的角色

db.isMaster()除了当前节点角色信息,是一个更精简化的信息,也返回整个复制集的成员列表,真正的Primary是谁,协议相关的配置信息等,Driver 在首次连接复制集时会发送该命令。

bash 复制代码
rs0 [direct: secondary] test> db.isMaster()
{
  topologyVersion: {
    processId: ObjectId("676115c17a6fd3ace24ff61c"),
    counter: Long("4")
  },
  hosts: [
    '47.94.171.130:28017',
    '47.94.171.130:28018',
    '47.94.171.130:28019'
  ],
  setName: 'rs0',
  setVersion: 1,
  ismaster: false,
  secondary: true,
  primary: '47.94.171.130:28017',
  me: '47.94.171.130:28018',
  lastWrite: {
    opTime: { ts: Timestamp({ t: 1734420334, i: 1 }), t: Long("1") },
    lastWriteDate: ISODate("2024-12-17T07:25:34.000Z"),
    majorityOpTime: { ts: Timestamp({ t: 1734420334, i: 1 }), t: Long("1") },
    majorityWriteDate: ISODate("2024-12-17T07:25:34.000Z")
  },
  maxBsonObjectSize: 16777216,
  maxMessageSizeBytes: 48000000,
  maxWriteBatchSize: 100000,
  localTime: ISODate("2024-12-17T07:25:39.706Z"),
  logicalSessionTimeoutMinutes: 30,
  connectionId: 33,
  minWireVersion: 0,
  maxWireVersion: 17,
  readOnly: false,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1734420334, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1734420334, i: 1 }),
  isWritablePrimary: false
}
rs0 [direct: secondary] test> 

1.4 MongoDB复制集常用命令

|------------------------------------|--------------------------------|
| 命令 | 描述 |
| rs.add() | 为复制集新增节点 |
| rs.addArb() | 为复制集新增一个 arbiter |
| rs.conf() | 返回复制集配置信息 |
| rs.freeze() | 防止当前节点在一段时间内选举成为主节点 |
| rs.help() | 返回 replica set 的命令帮助 |
| rs.initiate() | 初始化一个新的复制集 |
| rs.printReplicationInfo() | 以主节点的视角返回复制的状态报告 |
| rs.printSecondaryReplicationInfo() | 以从节点的视角返回复制状态报告 |
| rs.reconfig() | 通过重新应用复制集配置来为复制集更新配置 |
| rs.remove() | 从复制集中移除一个节点 |
| rs.secondaryOk() | 为当前的连接设置 从节点可读 |
| rs.status() | 返回复制集状态信息。 |
| rs.stepDown() | 让当前的 primary 变为从节点并触发 election |
| rs.syncFrom() | 设置复制集节点从哪个节点处同步数据,将会覆盖默认选取逻辑 |

1.5 复制集连接方式

方式一:直接连接 Primary 节点,正常情况下可读写 MongoDB,但主节点故障切换后,无法正常访问。

方式二(强烈推荐):通过高可用 Uri 的方式连接 MongoDB,当 Primary 故障切换后,MongoDB Driver 可自动感知并把流量路由到新的 Primary 节点。

1.6 复制集成员角色

复制集里面有多个节点,每个节点拥有不同的职责。在看成员角色之前,先了解两个重要属性:

属性一:Priority = 0

当 Priority 等于 0 时,它不可以被复制集选举为主,Priority 的值越高,则被选举为主的概率更大。通常,在跨机房方式下部署复制集可以使用该特性。假设使用了机房A和机房B,由于主要业务与机房A更近,则可以将机房B的复制集成员Priority设置为0,这样主节点就一定会是A机房的成员。

属性二:Vote = 0

不可以参与选举投票,此时该节点的 Priority 也必须为 0,即它也不能被选举为主。由于一个复制集中最多只有7个投票成员,因此多出来的成员则必须将其vote属性值设置为0,即这些成员将无法参与投票。

成员角色:

  • Primary:主节点,其接收所有的写请求,然后把修改同步到所有备节点。一个复制集只能有一个主节点,当主节点"挂掉"后,其他节点会重新选举出来一个主节点。
  • Secondary:备节点,与主节点保持同样的数据集。当主节点"挂掉"时,参与竞选主节点。分为以下三个不同类型:
    • Hidden = false:正常的只读节点,是否可选为主,是否可投票,取决于 Priority,Vote 的值;
    • Hidden = true:隐藏节点,对客户端不可见, 可以参与选举,但是 Priority 必须为 0,即不能被提升为主。 由于隐藏节点不会接受业务访问,因此可通过隐藏节点做一些数据备份、离线计算的任务,这并不会影响整个复制集。
  • Delayed :延迟节点,必须同时具备隐藏节点和Priority0的特性,会延迟一定的时间(secondaryDelaySecs 配置决定)从上游复制增量,常用于快速回滚场景。
  • Arbiter:仲裁节点,只用于参与选举投票,本身不承载任何数据,只作为投票角色。比如你部署了2个节点的复制集,1个 Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加⼀个 Arbiter节点,即使有节点宕机,仍能选出Primary。 Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入⼀个Arbiter节点,以提升复制集可用性。

很多情况下将节点设置为隐藏节点是用来协助 delayed members 的。如果我们仅仅需要防止该节点成为主节点,我们可以通过 priority 0 member 来实现。

bash 复制代码
cfg = rs.conf()
cfg.members[1].priority = 0
cfg.members[1].hidden = true
rs.reconfig(cfg)

设置完毕后,该从节点的优先级将变为 0 来防止其升职为主节点,同时其也是对应用程序不可见的。在其他节点上执行 db.isMaster() 将不会显示隐藏节点。

配置延时节点:当我们配置一个延时节点的时候,复制过程与该节点的 oplog 都将延时。延时节点中的数据集将会比复制集中主节点的数据延后。举个例子,现在是09:52,如果延时节点延后了1小时,那么延时节点的数据集中将不会有08:52之后的操作。

bash 复制代码
cfg = rs.conf()
cfg.members[1].priority = 0
cfg.members[1].hidden = true
#延迟1分钟
cfg.members[1].secondaryDelaySecs = 60
rs.reconfig(cfg)

二、MongoDB复制集原理

2.1 复制集高可用

MongoDB的复制集选举使用Raft算法(Raft Consensus Algorithm)来实现,选举成功的必要条件是大多数投票节点存活。在具体的实现中,MongoDB对raft协议添加了一些自己的扩展,这包括:

  • 支持chainingAllowed链式复制,即备节点不只是从主节点上同步数据,还可以选择一个离自己最近(心跳延时最小)的节点来复制数据。
  • 增加了预投票阶段,即preVote,这主要是用来避免网络分区时产生Term(任期)值激增的问题
  • 支持投票优先级,如果备节点发现自己的优先级比主节点高,则会主动发起投票并尝试成为新的主节点。

一个复制集最多可以有50 个成员,但只有 7 个投票成员。这是因为一旦过多的成员参与数据复制、投票过程,将会带来更多可靠性方面的问题。

|-------|-----|-------|
| 投票成员数 | 大多数 | 容忍失效数 |
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |

当复制集内存活的成员数量不足大多数时,整个复制集将无法选举出主节点,此时无法提供写服务,这些节点都将处于只读状态。此外,如果希望避免平票结果的产生,最好使用奇数个节点成员,比如3个或5个。当然,在MongoDB复制集的实现中,对于平票问题已经提供了解决方案:

  • 为选举定时器增加少量的随机时间偏差,这样避免各个节点在同一时刻发起选举,提高成功率。
  • 使用仲裁者角色,该角色不做数据复制,也不承担读写业务,仅仅用来投票。

2.2 故障转移

在故障转移的场景中,我们主要关心:

  • 备节点是怎么感知到主节点已经发生故障的?
  • 如何降低故障转移对业务产生的影响?

一个影响检测机制的因素是心跳,在复制集组建完成之后,各成员节点会开启定时器,持续向其他成员发起心跳,这里涉及的参数为heartbeatIntervalMillis,即心跳间隔时间,默认值是2s。如果心跳成功,则会持续以2s的频率继续发送心跳;如果心跳失败,则会立即重试心跳,一直到心跳恢复成功。

另一个重要的因素是选举超时检测,一次心跳检测失败并不会立即触发重新选举。实际上除了心跳,成员节点还会启动一个选举超时检测定时器,该定时器默认以10s的间隔执行,具体可以通过electionTimeoutMillis参数指定:

  • 如果心跳响应成功,则取消上一次的electionTimeout调度(保证不会发起选举),并发起新一轮electionTimeout调度。
  • 如果心跳响应迟迟不能成功,那么electionTimeout任务被触发,进而导致备节点发起选举并成为新的主节点。

在MongoDB的实现中,选举超时检测的周期要略大于electionTimeoutMillis设定。该周期会加入一个随机偏移量,大约在10~11.5s,如此的设计是为了错开多个备节点主动选举的时间,提升成功率。

因此,在electionTimeout任务中触发选举必须要满足以下条件:

(1)当前节点是备节点。

(2)当前节点具备选举权限。

(3)在检测周期内仍然没有与主节点心跳成功。

2.3 复制集数据同步机制

在复制集架构中,主节点与备节点之间是通过oplog来同步数据的,这里的oplog是一个特殊的固定集合,当主节点上的一个写操作完成后,会向oplog集合写入一条对应的日志,而备节点则通过这个oplog不断拉取到新的日志,在本地进行回放以达到数据同步的目的。

什么是oplog:

  • MongoDB oplog 是 Local 库下的一个集合,用来保存写操作所产生的增量日志(类似于 MySQL 中 的 Binlog)。
  • 它是一个 Capped Collection(固定集合),即超出配置的最大值后,会自动删除最老的历史数据,MongoDB 针对 oplog 的删除有特殊优化,以提升删除效率。
  • 主节点产生新的 oplog Entry,从节点通过复制 oplog 并应用来保持和主节点的状态一致;

查看oplog:

bash 复制代码
use local
db.oplog.rs.find().sort({$natural:-1}).pretty()

ts: 操作时间,当前timestamp + 计数器,计数器每秒都被重置

v:oplog版本信息

op:操作类型:

i:插⼊操作

u:更新操作

d:删除操作

c:执行命令(如createDatabase,dropDatabase)

n:空操作,特殊用途

ns:操作针对的集合

o:操作内容

o2:操作查询条件,仅update操作包含该字段

ts字段描述了oplog产生的时间戳,可称之为optime。optime是备节点实现增量日志同步的关键,它保证了oplog是节点有序的,其由两部分组成:

  • 当前的系统时间,即UNIX时间至现在的秒数,32位。
  • 整数计时器,不同时间值会将计数器进行重置,32位

optime属于BSON的Timestamp类型,这个类型一般在MongoDB内部使用。既然oplog保证了节点级有序,那么备节点便可以通过轮询的方式进行拉取,这里会用到可持续追踪的游标(tailable cursor)技术。

每个备节点都分别维护了自己的一个offset,也就是从主节点拉取的最后一条日志的optime,在执行同步时就通过这个optime向主节点的oplog集合发起查询。为了避免不停地发起新的查询链接,在启动第一次查询后可以将cursor挂住(通过将cursor设置为tailable)。这样只要oplog中产生了新的记录,备节点就能使用同样的请求通道获得这些数据。tailable cursor只有在查询的集合为固定集合时才允许开启。

2.4 oplog的大小

oplog集合的大小可以通过参数replication.oplogSizeMB设置,对于64位系统来说,oplog的默认值为:

oplogSizeMB = min(磁盘可用空间*5%,50GB)

对于大多数业务场景来说,很难在一开始评估出一个合适的oplogSize,所幸的是MongoDB在4.0版本之后提供了replSetResizeOplog命令,可以实现动态修改oplogSize而不需要重启服务器。

bash 复制代码
# 将复制集成员的oplog大小修改为60g  
db.adminCommand({replSetResizeOplog: 1, size: 60000})
# 查看oplog大小
use local
db.oplog.rs.stats().maxSize

2.5 复制延迟

由于oplog集合是有固定大小的,因此存放在里面的oplog随时可能会被新的记录冲掉。如果备节点的复制不够快,就无法跟上主节点的步伐,从而产生复制延迟(replication lag)问题。这是不容忽视的,一旦备节点的延迟过大,则随时会发生复制断裂的风险,这意味着备节点的optime(最新一条同步记录)已经被主节点老化掉,于是备节点将无法继续进行数据同步。

为了尽量避免复制延迟带来的风险,我们可以采取一些措施,比如:

  • 增加oplog的容量大小,并保持对复制窗口的监视。
  • 通过一些扩展手段降低主节点的写入速度。
  • 优化主备节点之间的网络。
  • 避免字段使用太大的数组(可能导致oplog膨胀)。

2.6 数据回滚

由于复制延迟是不可避免的,这意味着主备节点之间的数据无法保持绝对的同步。当复制集中的主节点宕机时,备节点会重新选举成为新的主节点。那么,当旧的主节点重新加入时,必须回滚掉之前的一些"脏日志数据",以保证数据集与新的主节点一致。主备复制集合的差距越大,发生大量数据回滚的风险就越高。

对于写入的业务数据来说,如果已经被复制到了复制集的大多数节点,则可以避免被回滚的风险。应用上可以通过设定更高的写入级别(writeConcern:majority)来保证数据的持久性。这些由旧主节点回滚的数据会被写到单独的rollback目录下,必要的情况下仍然可以恢复这些数据。

当rollback发生时,MongoDB将把rollback的数据以BSON格式存放到dbpath路径下rollback文件夹中,BSON文件的命名格式如下:...bson

bash 复制代码
mongorestore --host 192.168.192:27018 --db test --collection emp -ufox -pfox 
--authenticationDatabase=admin rollback/emp_rollback.bson

2.7 同步源设置

MongoDB是允许通过备节点进行复制的,这会发生在以下的情况中:

  • 在settings.chainingAllowed开启的情况下,备节点自动选择一个最近的节点(ping命令时延最小)进行同步。settings.chainingAllowed选项默认是开启的,也就是说默认情况下备节点并不一定会选择主节点进行同步,这个副作用就是会带来延迟的增加,你可以通过下面的操作进行关闭:
bash 复制代码
cfg = rs.config()
cfg.settings.chainingAllowed = false
rs.reconfig(cfg)
  • 使用replSetSyncFrom命令临时更改当前节点的同步源,比如在初始化同步时将同步源指向备节点来降低对主节点的影响。
bash 复制代码
db.adminCommand( { replSetSyncFrom: "hostname:port" })
相关推荐
LeonNo112 小时前
k8s,理解容器中namespace和cgroups的原理
分布式·容器·kubernetes
Java 第一深情2 小时前
分布式全文检索引擎ElasticSearch-数据的写入存储底层原理
分布式·elasticsearch·全文检索
明达技术3 小时前
MR30分布式IO模块,为港口岸桥安全增效保驾护航
分布式·安全
龙哥·三年风水6 小时前
workman服务端开发模式-应用开发-gateway的onWebSocketConnect开发
分布式·gateway·php
席万里6 小时前
分布式锁【Redis场景分布式锁篇】
数据库·redis·分布式
人机与认知实验室18 小时前
人类的认知应是分布式与中心式结合的弥聚式过程
分布式
weisian15118 小时前
Redis篇-17--分布式锁1-原理篇(分布式系统,分布式锁,INCR实现,SETNX实现,SET实现)
数据库·redis·分布式
weisian15118 小时前
Redis篇-18--分布式锁2-实战篇(RedisTemplate实现,Redission实现)
数据库·redis·分布式
Java 第一深情21 小时前
分布式全文检索引擎ElasticSearch-文档的CRUD原理
分布式·elasticsearch·全文检索