MongoDB高级应用
1 复制集
1.1 复制集机制与原理
1.1.1 复制集作用
MongoDB 复制集的主要意义在于实现服务高可用
它的实现依赖于两个方面的功能:
• 数据写入时将数据迅速复制到另一个独立节点上
• 在接受写入的节点发生故障时自动选举出一个新的替代节点
在实现高可用的同时,复制集实现了其他几个附加作用:
• 数据分发:将数据从一个区域复制到另一个区域,减少另一个 区域的读延迟
• 读写分离:不同类型的压力分别在不同的节点上执行
• 异地容灾:在数据中心故障时候快速切换到异地
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.2 搭建复制集
- 准备
● 安装最新的 MongoDB 版本
● Windows 系统请事先配置好 MongoDB 可执行文件的环境变量
● Linux 和 Mac 系统请配置 PATH 变量
● 确保有 10GB 以上的硬盘空间
- 创建数据目录
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 | |
- 启动 MongoDB 进程
1 | |
注意:如果启用了 SELinux,可能阻止上述进程启动。简单起见请关闭 SELinux。
- 配置复制集
方法1: 此方式hostname 需要能被解析
1 | |
方法2:在主节点配置即可,无须重复配置
1 | |
- 验证
MongoDB 主节点进行写入
1 | |
MongoDB 从节点进行读
1 | |
2 事务
mongodb的一致性需要借助writeConcern和readConcern的帮忙。
2.1 写操作事务-writeConcern
writeConcern 决定一个写操作落到多少个节点上才算成功。
writeConcern 的取值包括: • 0:发起写操作,不关心是否成功; • 1~集群最大数据节点数:写操作需要被复制到指定节点数才算成 功; • majority:写操作需要被复制到大多数节点上才算成功。
发起写操作的程序将阻塞到写操作到达指定的节点数为止。
journal 定义如何才算成功。取值包括: • true: 写操作落到 journal 文件中才算成功; • false: 写操作到达内存即算作成功
writeConcern 实践:
- 在复制集测试writeConcern参数
1 | |
- 配置延迟节点,模拟网络延迟(复制延迟)
1 | |
- 观察复制延迟下的写入,以及timeout参数
1 | |
注意事项:
• 虽然多于半数的 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 驱动程序 API:
1 | |
Mongo Shell:
1 | |
注意事项:
- 指定 readPreference 时也应注意高可用问题。例如将 readPreference 指定 primary,则发生故障转移不存在 primary 期间将没有节点可读。如果业务允许,则应选择 primaryPreferred;
- 使用 Tag 时也会遇到同样的问题,如果只有一个节点拥有一个特定 Tag,则在这个节点失效时将无节点可读。这在有时候是期望的结果,有时候不是。
- 如果报表使用的节点失效,即使不生成报表,通常也不希望将报表负载转移到其他节点上,此时只有一个节点有报表 Tag 是合理的选择;
- 如果线上节点失效,通常希望有替代节点,所以应该保持多个节点有同样的 Tag;
- 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