MongoDB 基础浅谈

254次阅读  |  发布于3年以前

MongoDB 作为一款优秀的基于分布式文件存储的 NoSQL 数据库,在业界有着广泛的应用。下文对 MongoDB 的一些基础概念进行简单介绍。

1 MongoDB 特点

2 MongoDB 要素

3 MongoDB 数据库

一个 MongoDB 实例可以创建多个 database。连接时如果没开启免认证模式的话,需要连接到 admin 库进行认证。如果开启免认证模式,若不指定 database 进行连接,默认连接一个叫 db 的数据库,该数据库存储在 data 目录中。通过 show dbs 命令可以查看所有的数据库。数据库名不能包含空字符。数据库名不能为空并且必须小于 64 个字符。

MongoDB 预留了几个特殊的 database。

一个 MongoDB 实例的数据结构如下图:

4 MongoDB 集合

MongoDB 集合存在于数据库中,没有固定的结构,可以往集合插入不同格式和类型的数据。集合不需要事先创建。当第一个文档插入,或者第一个索引创建时,集合就会被创建。集合名必须以下划线或者字母符号开始,并且不能包含 $,不能为空字符串(比如 ""),不能包含空字符,且不能以 system. 为前缀。

capped collection 是固定大小的集合,支持高吞吐的插入操作和查询操作。它的工作方式与循环缓冲区类似,当一个集合填满了被分配的空间,则通过覆盖最早的文档来为新的文档腾出空间。和标准的 collection 不同,capped collection 需要显式创建,指定大小,单位是字节。capped collection 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以更新 capped collection 中的文档,不可以超过之前文档的大小,以便确保所有文档在磁盘上的位置一直保持不变。

5 MongoDB 视图

视图基于已有的集合进行创建,是只读的,不实际存储硬盘,通过视图进行写操作会报错。视图使用其上游集合的索引。由于索引是基于集合的,所以你不能基于视图创建、删除或重建索引,也不能获取视图的索引列表。如果视图依赖的集合是分片的, 那么视图也视为分片的。视图是实时计算并读取的。

6 MongoDB 索引

MongoDB 支持丰富的索引方式。如果没有索引,读操作就必须扫描集合中的每个文档并筛选符合查询条件的记录。索引能够在很大程度上提高查询速度。

7 MongoDB ObjectId

ObjectId 可以快速生成并排序,长度为 12 个字节,包括:

在 MongoDB 中,存储在集合中的每个文档都需要一个唯一的 _id 字段作为主键。如果插入的文档省略了 _id 字段,则自动为文档生成一个 _id。

8 MongoDB 复制集

MongoDB 的复制集又称为副本集(Replica Set),是一组维护相同数据集合的 mongod 进程。复制集包含多个数据节点和一个可选的仲裁节点(arbiter)。在数据节点中,有且仅有一个成员为主节点(primary),其他节点为从节点(secondary)。

一个典型的复制集架构图如下:

8.1 复制集节点类型

8.2 复制集选主

MongoDB 的副本集协议(又称为 pv1),是一种 raft-like 协议,即基于 raft 协议的理论思想实现,并且对之进行了一些扩展。当往复制集添加一个节点,或当主节点无法和集群中其他节点通信的时间超过参数 electionTimeoutMillis 配置的期限时,从节点会尝试通过 pv1 协议发起选举来推荐自己成为新主节点。

在选举前具有投票权的节点之间两两互相发送心跳,以侦测节点是否存活。复制集节点每两秒向彼此发送心跳。如果心跳未在 10 秒内返回,则发送心跳的一方将被发送方标记为不可访问,也就是说,默认当 5 次心跳未收到时判断为节点失联。如果失联的是主节点,从节点会发起选举,选出新的主节点;如果失联的是从节点则不会产生新的选举。选举基于 raft 一致性算法实现,在大多数投票节点存活下选举出主节点。只有能够与多数节点建立连接且具有较新的 oplog 的节点才可能被选举为主节点,如果集群里的节点配置了优先级,那么具有较高的优先级的节点更可能被选举为主节点。

复制集中最多可以有 50 个节点,但具有投票权的节点最多 7 个。

8.3 复制集作用

9 MongoDB 分片集

MongoDB 支持通过分片技术来支持海量数据存储。解决数据增长的扩展方式有两种:垂直扩展和水平扩展。垂直扩展通过增加单个服务器的能力来实现,比如磁盘空间、内存容量、CPU 数量等;水平扩展则通过将数据存储到多个服务器上来实现。MongoDB 通过分片实现水平扩展。

一个典型的分片集群架构如下:

9.1 分片集组件

9.2 分片键

MongoDB 集合若要采用分片,必须要指定分片键(shard key)。分片键由文档中的一个或多个字段组成。分片集合必须具有支持分片键的索引,索引可以是分片键的索引,也可以是以分片键是索引前缀的复合索引。要对已填充的集合进行分片,该集合必须具有以分片键开头的索引;分片一个空集合时,如果该集合还没有包含指定分片键的索引,则 MongoDB 会默认给分片键创建索引。

对于一个即将要分片的集合,如果该集合具有其他唯一索引,则无法分片该集合。

对于已分片的集合,不能在其他字段上创建唯一索引。

4.2 版本开始可以更改文档的分片键值,除非分片键字段为不可变的 _id 字段。更新分片键时必须在事务中或以可重试写入的方式在 mongos 上运行,不能直接在分片上执行操作。在此之前文档的分片键字段值是不可变的。

4.4 版本开始,可以向现有片键中添加一个或多个后缀字段以优化集合的片键。

5.0 版本开始,实现了实时重新分片(live resharding),可以实现分片键的完全重新选择。live resharding 机制下,数据将根据新的分片规则进行迁移,不过有一些限制,比如一个实例中有且只能有一个集合在相同的时间下 resharding 等。

数据库可以混合使用分片和未分片集合。分片集合被分区并分布在集群中的各个分片中。而未分片集合仅存储在主分片中。

设置 shard key 时应该充分考虑取值基数和取值分布。分片键应被尽可能多的业务场景用到。尽可能避免使用单调递增或递减的字段作为分片键。

9.3 分片策略

MongoDB 将分片数据拆分成块。每个分块都有一个基于分片键的上下限范围 。分片策略包括哈希分片、范围分片和自定义 zone 分片。

10 MongoDB 聚合

MongoDB 聚合框架(Aggregation Framework)是一个计算框架,功能是:

MongoDB 提供了三种执行聚合的方法:聚合管道,map-reduce 和单一目的聚合方法(如 count、distinct 等方法)。

10.1 聚合管道

在聚合管道中,整个聚合运算过程称为管道(pipeline),它是由多个步骤(stage)组成的, 每个管道的工作流程是:

  1. 接受一系列原始数据文档
  2. 对这些文档进行一系列运算
  3. 结果文档输出给下一个 stage

聚合计算基本格式如下:

pipeline = [$stage1, $stage2, ...$stageN];     db.collection.aggregate( pipeline, { options } )

10.2 map-reduce

map-reduce 操作包括两个阶段:map 阶段处理每个文档并将 key 与 value 传递给 reduce 函数进行处理,reduce 阶段将 map 操作的输出组合在一起。map-reduce 可使用自定义 JavaScript 函数来执行 map 和 reduce 操作,以及可选的 finalize 操作。通常情况下效率比聚合管道低。

10.3 单一目的聚合方法

主要包括以下三个:

11 MongoDB 一致性

分布式系统有个 PACELC 理论。根据 CAP,在一个存在网络分区(P)的分布式系统中,要面临在可用性(A)和一致性(C)之间的权衡,除此之外(E),即使没有网络分区的存在,在实际系统中,我们也要面临在访问延迟(L)和一致性(C)之间的权衡。MongoDB 的一致性模型对读写操作 L 和 C 的选择提供了丰富的选项。

11.1 因果一致性

单节点的数据库由于为读写操作提供了顺序保证,因此实现了因果一致性。分布式系统同样可以提供这些保证,但必须对所有节点上的相关事件进行协调和排序。

以下是一个不遵循因果一致性的例子:

为了保持因果一致性,必须有以下保证:

实现因果一致性的单号读写应遵循以下流程:

为了建立复制集和分片集事件的全局偏序关系,MongoDB 实现了一个逻辑时钟,称为 lamport logical clock。每个写操作在应用于主节点时都会被分配一个时间值。这个值可以在副本和分片之间进行比较。从驱动到查询路由器再到数据承载节点,分片集群中的每个成员都必须在每条消息中跟踪和发送其最新时间值,从而允许分片之间的每个节点在最新时间保持一致。主节点将最新的时间值赋值给后续的写入,这为任何一系列相关操作创建了一个因果顺序。节点可以使用这个因果顺序在执行所需的读或写之前等待,以确保它在另一个操作之后发生。

从 MongoDB 3.6 开始,在客户端会话中开启因果一致性,保证 read concern 为 majority 的读操作和 write concern 为 majority 的写操作的关联序列具有因果关系。应用程序必须确保一次只有一个线程在客户端会话中执行这些操作。

对于因果相关的操作:

  1. 客户端开启客户端会话,需满足以下条件:read concern 为 majority,数据已被大多数复制集成员确认并且是持久化的;write concern 为 majority,确认该操作已应用于复制集中大多数可投票成员。
  2. 当客户端发出 read concern 为 majority 的读操作和 write concern 为 majority 的写操作的序列时,客户端将会话信息包含在每个操作中。
  3. 对于与会话相关联的每个 read concern 为 majority 的读操作和 write concern 为 majority 的写操作,即使操作出错,MongoDB 也会返回操作时间和集群时间。
  4. 相关的客户端会话会跟踪这两个时间字段。

11.2 线性一致性

线性一致性又被称为强一致性。CAP 中的 C 指的就是线性一致性。顺序一致性中进程只关心各自的顺序一样就行,不需要与全局时钟一致。线性一致性是顺序一致性的进化版,要求顺序一致性的这种偏序(partial order)要达到全序(total order)。

在实现了线性一致性的系统中,任何操作在该系统生效的时刻都对应时间轴上的一个点。把这些时刻连接成一条线,则这条线会一直沿时间轴向前,不会反向。任何操作都需要互相比较决定发生的顺序。

以下是一个线性一致性的系统示例:

在以上系统中,写操作生效之前的任何时刻,读取值均为 1,生效后均为 2。也就是说,任何读操作都能读到某个数据的最近一次写的数据。系统中的所有进程看到的操作顺序,都遵循全局时钟的顺序。

11.3 read concern

read concern 是针对读操作的配置。它控制读取数据的新近度和持久性。read concern 选项控制数据读取的一致性,分为 local、available、majority、linearizable 四种,它们对一致性的承诺依次由弱到强。其中 linearizable 表示线性一致性,另外 3 种级别代表了 MongoDB 在实现最终一致性时,对访问延迟和一致性的取舍。

下面借用一张图展示 majority 和 linearizable 的区别:

11.4 write concern

write concern 是针对写操作的配置,表示写请求对独立 mongod 实例或复制集或分片集进行写操作的确认级别。它主要是控制数据写入的持久性。包含三个选项:

12 MongoDB WiredTiger 引擎

从 3.2 版本开始,默认使用 WiredTiger 存储引擎,每个被创建的表和索引,都对应各自独立的 WiredTiger 表。为了保证 MongoDB 中数据的持久性,使用 WiredTiger 的写操作会先写入 cache,并持久化到 WAL(write ahead log),每 60s 或日志文件达到 2 GB,就会做一次 checkpoint,定期将缓存数据刷到磁盘,将当前的数据持久化产生一个新的快照。

12.1 WiredTiger 数据结构

MongoDB 采用插件式存储引擎架构,实现了服务层和存储引擎层的解耦,可支持使用多种存储引擎。除此之外,底层的 WiredTiger 引擎还支持使用 B+ 树和 LSM 两种数据结构进行数据管理和存储,默认使用 B+ 树结构做存储。使用 B+ 树时,WiredTiger 以 page 为单位往磁盘读写数据,B+ 树的每个节点为一个 page,包含三种类型的 page,即 root page、internal page 和 leaf page。

以下是 B+ 树的结构示意图:

12.2 WiredTiger 压缩

WiredTiger 支持在内存和磁盘上对索引进行压缩,通过前缀压缩的方式减少 RAM 的使用。

12.3 WiredTiger 一致性原理

WiredTiger 使用了二级缓存 WiredTiger Cache 和 File System Cache 来保证 Disk 上 Database File 数据的最终一致性。

12.4 WiredTiger MVCC

WiredTiger 使用 MVCC 进行写操作,多个客户端可以并发同时修改集合的不同文档。事务开始时,WiredTiger 为操作提供反映内存数据的一致视图的时间点快照。MVCC 通过非锁机制进行读写操作,是一种乐观并发控制模式。WiredTiger 仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间存在冲突时,将引发写冲突,从而导致 MongoDB 自动重试该操作。

使用 WiredTiger,如果没有 journal 记录,MongoDB 能且仅能从最后一个检查点恢复。如果需要恢复最后一次 checkpoint 之后所做的更改,那么开启日志是必要的。

13 MongoDB 数据读写

13.1 读偏好 ReadPerference

默认情况下,客户端读取复制集主节点上的数据。但客户端可以指定一个 read perference 改变读取行为,以便对复制集上的其他节点进行直接读操作。可选值包括:

13.2 在复制集上进行读写操作

读操作由客户端指定的 read prefenence 选项决定。

所有的写操作都在集合的主节点上执行。主节点执行写操作并将操作记录在操作日志或 oplog 上。oplog 是 local 数据库的一个集合,叫 local.oplog.rs。这是一个 capped collection,是固定大小,循环使用的。oplog 是对数据集的可重复操作序列,其记录的每个操作都是幂等的,也就是说,对目标数据集应用一次或多次 oplog 操作都会产生相同的结果。从节点从上一次结束时间点建立 tailable cursor,不断的从同步源拉取 oplog 并重放应用到自身,且严格按照原始的写顺序对给定的文档执行写操作。mongodb 使用多线程批量执行写操作来提高并发,根据文档 id 进行分批执行。MongoDB 为了提升同步效率,将拉取 oplog 以及重放 oplog 分到了不同的线程来执行。

大致的写流程如下:

13.3 在分片集群上进行读写操作

对于分片集群,需要一个 mongos 实例提供客户端应用程序和分片集群之间的接口。在客户端看来,该 mongos 实例的行为与其他 MongoDB 实例是相同的。客户端向路由节点 mongos 发送请求,由该节点决定往哪个分片进行读写。对于读取操作,若能定向到特定分片时,效率最高。一般而言,分片集合的查询应包含集合的分片键,以避免低效的全分片查询。在这种情况下,mongos 可以使用配置数据库 config 中的集群元数据信息,将查询路由到分片。如果查询不包含分片键,则 mongos 节点必须将查询定向到集群中的所有分片,然后在 mongos 上聚合所有分片的查询结果,返回给客户端。

对于写操作, mongos 定向到负责数据集特定部分的分片,config 数据库上有集合相关的分片键信息,mongos 从中读取配置,并路由写操作到适当的分片。

14 MongoDB 事务

14.1 ACID 特性

MongoDB 在一定程度上支持了事务的 ACID 特性。MongoDB 4.0 版本开始支持复制集上的多文档事务,4.2 版本引入了分布式事务,它增加了对分片群集上多文档事务的支持。

14.2 事务的使用限制

14.3 事务与 read concern

事务中的操作使用事务级别的 read concern。事务内部忽略在集合和数据库级别设置的任何 read concern。事务支持设置 read concern 为 local、majority 和 snapshot 其中之一。

14.4 事务与 write concern

事务使用事务级别的 write concern 来进行写操作提交,可以通过配置 w 选项设置节点个数,来决定事务写入是否成功,默认情况下为 1。

15 MongoDB Change Stream

15.1 变更流使用场景

MongoDB 3.6 引入了 change stream(变更流)。它的使用场景包括:

15.2 变更流特点

change stream 允许外部程序访问实时数据更改,而不会增加 MongoDB 基础操作的复杂性,也不会导致 oplog 延迟的风险。应用程序可以使用 change stream 来订阅单个集合、数据库或整个集群中的所有数据变更。若要开启 change stream,必须使用 WiredTiger 存储引擎。

change stream 可应用于复制集和分片集。应用于复制集时,可以在复制集中任意一个节点上开启监听;应用于分片集时,则只能在 mongos 上开启监听。在 mongos 上发起监听,是利用全局逻辑时钟提供了整个分片上变更的总体排序,确保监听事件可以按接收到的顺序安全地解释。mongos 会一直检查每个分片,查看每个分片是否存在最新的变更。如果多个分片上一直很少出现变更,则可能会对 change stream 的响应时间产生负面影响,因为 mongos 仍必须检查这些冷分片保持总体有序。

15.3 变更流监听事件类型

从 change stream 中能监听到的变更事件包括:insert、update、replace、delete、drop、rename、dropDatabase 和 invalidate。

15.4 变更流故障恢复

MongoDB 4.0 之后,可以通过指定 startAtOperationTime 来控制从某个特定的时间点开启监听,但该时间点必须在所选择节点的有效 oplog 时间范围内。change stream 监听返回的字段中有个 _id 字段,表示的是 resume token,这是唯一标志 change stream 流中的位置的字段。

如果 change stream 监听比中止后需要继续监听,那么可指定 resumeAfter 恢复订阅。指定 resumeAfter 为 change stream 中断处的 _id 字段即可。

当监听的集合发生 rename、drop 或 dropDatabase 事件,就会导致 invalidate 事件;当监听的数据库出现 dropDatabase 事件,也会导致无效事件。invalidate 事件后 change stream 的游标会被关闭,这时就需要使用 resumeAfter 选项来恢复 change stream 的监听,在 4.2 版本后也可以通过 startAfter 选项创建新的更改流来恢复监听。

15.5 变更流使用限制

16 MongoDB 性能问题定位方式

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8