MongoDB高级应用

1 复制集

1.1 复制集机制与原理

1.1.1 复制集作用

  1. MongoDB 复制集的主要意义在于实现服务高可用

  2. 它的实现依赖于两个方面的功能:

​ • 数据写入时将数据迅速复制到另一个独立节点上

​ • 在接受写入的节点发生故障时自动选举出一个新的替代节点

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

    • 数据分发:将数据从一个区域复制到另一个区域,减少另一个 区域的读延迟

    • 读写分离:不同类型的压力分别在不同的节点上执行

    • 异地容灾:在数据中心故障时候快速切换到异地

1.1.2 复制集结构

一个典型的复制集由3个以上具有投票权的节点组成,包括:

• 一个主节点(PRIMARY):接受写入操作和选举时投票

• 两个(或多个)从节点(SECONDARY):复制主节点上的新数据和选举时投票

• 不推荐使用 Arbiter(投票节点)

1.1.3 数据是如何复制的?

● 当一个修改操作,无论是插入、更新或删除,到达主节点时,它对数据的操作将被记录下来(经过一些必要的转换),这些记录称为 oplog。

● 从节点通过在主节点上打开一个 tailable 游标不断获取新进入主节点的 oplog,并在自己的数据上回放,以此保持跟主节点的数据一致

1.1.4 故障恢复

通过选举完成故障恢复:

● 具有投票权的节点之间两两互相发送心跳; ● 当5次心跳未收到时判断为节点失联; ● 如果失联的是主节点,从节点会发起选举,选出新的主节点; ● 如果失联的是从节点则不会产生新的选举;● 选举基于 RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活; ● 复制集中最多可以有50个节点,但具有投票权的节点最多7个。

影响选举的因素:● 整个集群必须有大多数节点存活; ● 被选举为主节点的节点必须:能够与多数节点建立连接;具有较新的oplog;具有较高的优先级(如果有配置)

常见配置选项:• 是否具有投票权(v 参数):有则参与投票; • 优先级(priority 参数):优先级越高的节点越优先成为主节点。优先级为0的节点无法成为主节点; • 隐藏(hidden 参数):复制数据,但对应用不可见。隐藏节点可以具有投票仅,但优先级必须为0; • 延迟(slaveDelay 参数):复制 n 秒之前的数据,保持与主节点的时间差。

1.1.5 复制集注意事项

  1. 关于硬件

​ • 因为正常的复制集节点都有可能成为主节点,它们的地位是一样的,因此硬件配置上必须 一致;

​ • 为了保证节点不会同时宕机,各节点使用的硬件必须具有独立性。

  1. 关于软件

​ • 复制集各节点软件版本必须一致,以避免出现不可预知的问题。

  1. 增加节点不会增加系统写性能!

1.2 搭建复制集

  1. 准备

● 安装最新的 MongoDB 版本

● Windows 系统请事先配置好 MongoDB 可执行文件的环境变量

● Linux 和 Mac 系统请配置 PATH 变量

● 确保有 10GB 以上的硬盘空间

  1. 创建数据目录
1
mkdir -p /data/db{1,2,3}
  1. 准备配置文件

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

● 不同的端口。示例中将使用 28017/28018/28019

● 不同的数据目录。示例中将使用: /data/db1 /data/db2 /data/db3

● 不同日志文件路径。示例中将使用: /data/db1/mongod.log /data/db2/mongod.log /data/db3/mongod.log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /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
  1. 启动 MongoDB 进程
1
2
3
mongod -f /data/db1/mongod.conf
mongod -f /data/db2/mongod.conf
mongod -f /data/db3/mongod.conf

注意:如果启用了 SELinux,可能阻止上述进程启动。简单起见请关闭 SELinux。

  1. 配置复制集

方法1: 此方式hostname 需要能被解析

1
2
3
4
5
# mongo --port 28017
# hostname -f
> rs.initiate() # 这个时候 SECONDARY> 这个时候变成了从节点 在等几秒钟 再按一下回车就变成了PARIMARY了 可以正常负责读和写了
> rs.add(”HOSTNAME:28018")
> rs.add(”HOSTNAME:28019")

方法2:在主节点配置即可,无须重复配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mongo --port 28017
# mongo localhost:28018
rs.initiate({
_id: "rs0",
members: [{
_id: 0,
host: "localhost:28017"
},{
_id: 1,
host: "localhost:28018"
},{
_id: 2,
host: "localhost:28019"
}]
})
  1. 验证

MongoDB 主节点进行写入

1
2
3
# mongo localhost:28017
> db.test.insert({ a:1 });
> db.test.insert({ a:2 });

MongoDB 从节点进行读

1
2
3
4
5
# mongo localhost:28018
> rs.slaveOk() # secondary OK()
> db.test.find()
# --------隔一段时间
> db.test.find()

2 事务

mongodb的一致性需要借助writeConcern和readConcern的帮忙。

2.1 写操作事务-writeConcern

writeConcern 决定一个写操作落到多少个节点上才算成功。

writeConcern 的取值包括: • 0:发起写操作,不关心是否成功; • 1~集群最大数据节点数:写操作需要被复制到指定节点数才算成 功; • majority:写操作需要被复制到大多数节点上才算成功。

发起写操作的程序将阻塞到写操作到达指定的节点数为止。

journal 定义如何才算成功。取值包括: • true: 写操作落到 journal 文件中才算成功; • false: 写操作到达内存即算作成功

writeConcern 实践

  1. 在复制集测试writeConcern参数
1
2
3
db.test.insert( {count: 1}, {writeConcern: {w:"majority"}})
db.test.insert( {count: 1}, {writeConcern: {w: 3 }})
db.test.insert( {count: 1}, {writeConcern: {w: 4 }})
  1. 配置延迟节点,模拟网络延迟(复制延迟)
1
2
3
4
conf = rs.conf()
conf.members[2].slaveDelay = 5
conf.members[2].priority = 0
rs.reconfig(conf)
  1. 观察复制延迟下的写入,以及timeout参数
1
2
db.test.insert( {count: 1}, {writeConcern: {w: 3}})
db.test.insert( {count: 1}, {writeConcern: {w: 3,wtimeout:3000 }})

注意事项:

• 虽然多于半数的 writeConcern 都是安全的,但通常只会设置 majority,因为这是等待写入延迟时间最短的选择;

• 不要设置 writeConcern 等于总节点数,因为一旦有一个节点故障,所有写操作都将失败;

• writeConcern 虽然会增加写操作延迟时间,但并不会显著增加集群压力,因此无论是否等待,写操作最终都会复制到所有节点上。 设置 writeConcern 只是让写操作等待复制后再返回而已;

• 应对重要数据应用 {w: “majority”},普通数据可以应用 {w: 1} 以确保最佳性能。

2.2 读操作事务-readPreference

在读取数据的过程中需要关注以下两个问题: 从哪里读? 什么样的数据可以读? 第一个问题是是由 readPreference 来解决;第二个问题则是由 readConcern 来解决。

readPreference 决定使用哪一个节点来满足正在发起的读请求。 可选值包括: • primary: 只选择主节点; • primaryPreferred:优先选择主节点,如果不可用则选择从节点; • secondary:只选择从节点; • secondaryPreferred:优先选择从节点, 如果从节点不可用则选 择主节点; • nearest:选择最近的节点;

readPreference 与 Tag

readPreFerence 是类别,Tag 是可以做分组,tag的粒度可以做得比前者细

readPreference 只能控制使用一类节点。Tag 则可以将节点选择控制到一个或几个节点。考虑以下场景: • 一个 5 个节点的复制集; • 3 个节点硬件较好,专用于服务线上客户; • 2 个节点硬件较差,专用于生成报表; 可以使用 Tag 来达到这样的控制目的: • 为 3 个较好的节点打上 {purpose: “online”}; • 为 2 个较差的节点打上 {purpose: “analyse”}; • 在线应用读取时指定 online,报表读取时指定 reporting;

https://www.mongodb.com/docs/manual/core/read-preference/

readPreference 配置

通过 MongoDB 的连接串参数:

1
mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPreference=secondary

通过 MongoDB 驱动程序 API:

1
MongoCollection.withReadPreference(ReadPreferencereadPref)

Mongo Shell:

1
db.collection.find({}).readPref( “secondary” )

注意事项:

  1. 指定 readPreference 时也应注意高可用问题。例如将 readPreference 指定 primary,则发生故障转移不存在 primary 期间将没有节点可读。如果业务允许,则应选择 primaryPreferred;
  2. 使用 Tag 时也会遇到同样的问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。
  3. 如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表 Tag 是合理的选择;
  4. 如果线上节点失效,通常希望有替代节点,所以应该保持多个节点有同样的 Tag;
  5. Tag 有时需要与优先级、选举权综合考虑。例如做报表的节点通常不会希望它成为主节点,则优先级应为 0。

2.3 读操作事务-readConcern

数据的隔离级别

在 readPreference 选择了指定的节点后,readConcern 决定这个节点上的数据哪些是可读的,类似于关系数据库的隔离级别。可选值包括:

• available:读取所有可用的数据; (mongo 默认的级别)

• local:读取所有可用且属于当前分片的数据; (很少用)

• majority:读取在大多数节点上提交完成的数据; (解决脏读,达到读已提交,通过MVCC去解决的)

• linearizable:可线性化读取文档; (线性化读取)

• snapshot:读取最近快照中的数据; (解决脏读、不可重复读、 幻读,达到可重复读,通过快照去解决的)

2.3.1 readConcern: local 和 available

在复制集中 local 和 available 是没有区别的。两者的区别主要体现在分片集上。考虑以下场景:

• 一个 chunk x 正在从 shard1 向 shard2 迁移的场景下; • 整个迁移过程中 chunk x 中的部分数据会在 shard1 和 shard2 中 同时存在,但源分片 shard1仍然是 chunk x 的负责方: • 所有对 chunk x 的读写操作仍然进入 shard1; • config 中记录的信息 chunk x 仍然属于 shard1; • 此时如果读 shard2,则会体现出 local 和 available 的区别: • local:只取应该由 shard2 负责的数据(不包括 x); • available:shard2 上有什么就读什么(包括 x);

注意事项:

• 虽然看上去总是应该选择 local,但毕竟对结果集进行过滤会造成额外消耗。在一些无关紧要的场景(例如统计)下,也可以考虑 available;

• MongoDB <=3.6 不支持对从节点使用 {readConcern: “local”};

• 从主节点读取数据时默认 readConcern 是 local,从从节点读取数据时默认 readConcern 是 available(向前兼容原因)。

2.3.2 readConcern: majority

只读取大多数据节点上都提交了的数据,这个时候数据的不会被回滚的是安全的。

使用 writeConcern + readConcern majority 来实现安全的读写分离。

2.3.3 readConcern: linearizable

只读取大多数节点确认过的数据。和 majority 最大差别是保证绝对的操作线性顺序:在写操作自然时间后面的发生的读,一定可以读到之前的写;只对读取单个文档时有效; 可能导致非常慢的读,因此总是建议配合使用 maxTimeMS;

这种情况是为了解决,主节点在写入数据后宕机,数据没有同步到其他节点,其他节点成为主节点,在后面的时间节点恢复变为 Secondary,因为之前作为主节点执行写数据又与其他节点不一样的情况,这个时候希望他能读到正常的数据,而不是旧数据。

2.3.4 readConcern: snapshot

{readConcern: “snapshot”} 只在多文档事务中生效。 将一个事务的 readConcern 设置为 snapshot,将保证在事务中的读: • 不出现脏读; • 不出现不可重复读; • 不出现幻读。 因为所有的读都将使用同一个快照,直到事务提交为止该快照才被释放。

2.3.5 readConcern: 小结

• available:读取所有可用的数据

• local:读取所有可用且属于当前分片的数据,默认设置

• majority:数据读一致性的充分保证,可能你最需要关注的

• linearizable:增强处理 majority 情况下主节点失联时候的例外情况

• snapshot:最高隔离级别,接近于 Seriazable


MongoDB高级应用
http://www.zivjie.cn/2023/05/13/数据库/MongoDB/MongoDB高级应用/
作者
Francis
发布于
2023年5月13日
许可协议