MongoDB 是一个流行的开源文档数据库,属于 NoSQL 数据库的范畴。它以其灵活的数据模型、强大的可伸缩性和高性能而闻名。以下是其核心概念的详细解释。
1. 核心数据模型
1.1 文档(Document)
-
定义: 文档是 MongoDB 的核心数据单元,类似于关系型数据库中的“行”(Row),但其结构更加灵活和丰富。
-
结构: 文档以 BSON(Binary JSON)格式存储,其内容是键值对的有序集合。这些值可以是简单类型(如字符串、数字、日期、布尔值),也可以是数组、内嵌文档等复杂类型。
-
特点:
- 无模式(Schemaless): 同一个集合中的文档可以拥有不同的字段,没有固定的模式约束。这意味着你可以随时修改文档结构,而无需预先定义或执行复杂的 DDL 操作。
- 层次结构: 文档可以包含内嵌文档和数组,形成复杂的、面向对象的层次结构,非常适合表示复杂的业务对象。
- 自描述: 每个文档都包含其所有的数据,使得查询和理解数据更加直观。
-
示例:
{ "_id": ObjectId("60c72b2f9d7c4a001c8e4f1a"), "name": "张三", "age": 30, "email": "[email protected]", "address": { "street": "文一西路", "city": "杭州", "zip": "310000" }, "hobbies": ["reading", "hiking", "coding"], "createdAt": ISODate("2023-10-26T10:00:00Z") }
1.2 集合(Collection)
- 定义: 集合是 MongoDB 中文档的容器,类似于关系型数据库中的“表”(Table)。
- 结构: 集合没有固定的结构,它存储了一组无模式的文档。一个集合中的文档可以具有不同的字段集和结构。
- 特点:
- 动态模式: 集合中的文档不必具有相同的字段集或结构,这为数据建模提供了极大的灵活性。
- 索引: 可以在集合中的任何字段上创建索引,以提高查询性能。
- 命名: 集合名通常以字符串表示,不能包含
$
符号。
1.3 BSON 编码是什么?
- 定义: BSON(Binary JSON)是 MongoDB 内部使用的一种二进制序列化格式,它扩展了 JSON 的数据类型,并优化了存储和解析效率。
- 为什么需要 BSON?
- 效率: JSON 是文本格式,解析效率相对较低。BSON 是二进制格式,解析速度更快,且在存储时通常比文本 JSON 更紧凑。
- 更丰富的数据类型: JSON 只支持几种基本数据类型(字符串、数字、布尔、数组、对象、null)。BSON 增加了对更多数据类型的支持,如:
Date
(日期和时间)ObjectID
(文档的唯一标识符,MongoDB 自动生成)BinData
(二进制数据)Decimal128
(高精度十进制数)Timestamp
(时间戳)RegExp
(正则表达式)
- 方便遍历: BSON 格式在数据头部包含了长度信息,使得跳过或查找特定字段更加高效,而无需完全解析整个文档。
- 工作方式: 当客户端发送 JSON 数据到 MongoDB 服务器时,服务器会将其转换为 BSON 格式进行存储。当客户端从服务器接收数据时,服务器会再次将其转换为 JSON 或其他客户端支持的格式(如 BSON 驱动程序)。
2. 存储引擎核心概念
MongoDB 支持可插拔存储引擎架构,自 MongoDB 3.2 版本起,WiredTiger 成为了默认的存储引擎。
2.1 WiredTiger 存储引擎
- 定义: WiredTiger 是 MongoDB 默认的、高性能的存储引擎,负责 MongoDB 如何在磁盘上存储数据、如何管理内存中的数据以及如何处理并发访问。
- 核心功能:
- 数据持久化: 将数据从内存写入磁盘。
- 内存管理: 维护一个内部缓存,将频繁访问的数据保存在内存中。
- 并发控制: 处理多个客户端同时读写数据的请求,确保数据一致性。
- 索引管理: 创建、维护和使用索引来加速查询。
- 数据压缩: 支持多种压缩算法,以减少存储空间占用和 I/O 开销。
- 日志和检查点: 确保数据持久性和故障恢复能力。
2.2 数据页结构与 WT 文件
- 数据页(Data Page):
- 基本单位: 存储引擎与磁盘进行 I/O 的基本单位通常是数据页(或称块)。WiredTiger 默认的数据页大小为 4KB(可配置)。
- 内容: 一个数据页可以存储一个或多个文档的部分或全部内容,以及相关的元数据。
- 重要性: 所有的读写操作都以数据页为单位进行,数据页是 WiredTiger 缓存、并发控制和持久化的基本粒度。
- WT 文件:
- WiredTiger 将所有数据(包括集合数据和索引数据)存储在
.wt
后缀的文件中。 - 这些文件是 WiredTiger 内部管理的,其内容是经过 BSON 编码并可能经过压缩的二进制数据。
- 用户不应直接操作这些
.wt
文件,它们由 WiredTiger 引擎进行精细管理,包括块分配、空闲空间管理、压缩等。 - 一个 MongoDB 数据库可能包含多个
.wt
文件,每个集合和索引通常对应一个或多个内部文件。
- WiredTiger 将所有数据(包括集合数据和索引数据)存储在
2.3 变种 B+ 树索引
- 定义: WiredTiger 存储引擎使用一种高度优化的变种 B+ 树作为其索引结构。
- B+ 树基础: B+ 树是一种广泛用于数据库和文件系统的树形数据结构,其特点是:
- 所有数据都存储在叶子节点中,并通过链表连接,便于范围查询。
- 内部节点只存储键值和子节点的指针,不存储实际数据,以最大化分支因子,减少树的深度。
- WiredTiger 的变种: WiredTiger 对传统的 B+ 树进行了多项优化,以适应其并发、持久化和压缩需求:
- 写时复制(COW): 索引节点的修改也遵循 COW 原则,提高了并发性。
- Page-level Concurrency: 锁的粒度可以细化到数据页,减少锁竞争。
- 压缩: 索引数据同样可以进行压缩,减少索引大小和 I/O。
- MVCC 支持: B+ 树的变种支持多版本并发控制,确保读操作不会被写操作阻塞。
- 优势: 提供了高效的等值查找、范围查询和排序操作。
2.4 写时复制(Copy On Write - COW)并发控制
- 定义: 写时复制是一种乐观并发控制机制,WiredTiger 大量使用它来管理数据页的修改。当一个数据页需要被修改时,WiredTiger 不会直接在原地修改该页,而是创建一个该页的副本,在副本上进行修改,然后更新指向该页的指针。
- 工作流程:
- 当一个写操作需要修改某个数据页 P 时。
- WiredTiger 创建 P 的一个新版本 P’。
- 修改在 P’ 上进行。
- 一旦修改完成并持久化到 Journal,B+ 树索引中的指针会被原子性地更新,指向新的 P’。
- 旧的数据页 P 成为历史版本,在垃圾回收时会被清理。
- 优势:
- 无锁读(Reads never block writes): 读操作总是访问旧版本的数据页,因此不会被写操作阻塞。这实现了多版本并发控制(MVCC)。
- 原子性: 页面指针的更新是原子性的,保证了数据的一致性。
- 崩溃恢复: COW 使得崩溃恢复更加简单,因为总是可以回滚到最近的检查点。
- 快照: 很容易创建数据库的快照,因为旧版本的数据页仍然存在。
- 潜在缺点: 可能会导致一定程度的数据碎片化,但 WiredTiger 内部有机制进行碎片整理和优化。
2.5 缓存策略(Cache + LRU 淘汰)
- 定义: WiredTiger 维护一个内部缓存(in-memory cache)来存储频繁访问的数据页和索引页,以减少磁盘 I/O,提高读写性能。
- 缓存内容:
- 数据页: 存储文档的实际数据。
- 索引页: 存储索引的 B+ 树节点。
- 缓存大小: 可以通过
wiredTigerCacheSizeGB
参数进行配置,通常建议设置为可用 RAM 的 50%-80%。 - LRU(Least Recently Used)淘汰算法:
- 机制: 当缓存空间不足时,WiredTiger 需要决定淘汰哪些数据页。LRU 算法会优先淘汰最近最少使用的数据页。
- 原理: 假设最近访问的数据在未来也更有可能被访问。通过维护一个访问顺序列表,将最近访问的项放到列表前面,淘汰列表末尾的项。
- 优势: 是一种简单且有效的缓存淘汰策略,能够较好地保留热点数据。
2.6 持久化保障:Journal 写前日志 + Checkpoint 机制
为了确保数据的持久性(在系统崩溃时数据不丢失),WiredTiger 采用了 Journal(写前日志)和 Checkpoint(检查点)机制。
- Journal (写前日志 - Write-Ahead Log, WAL):
- 目的: 确保写操作的原子性和持久性。
- 工作原理: 在数据真正写入到数据文件之前,所有对数据的修改操作(包括文档修改和索引修改)都会先写入到 Journal 文件(一个持久化的日志文件)中。
- 事务性: Journal 记录了所有操作的意图。如果 MongoDB 在数据写入磁盘之前崩溃,系统重启时可以读取 Journal 文件,重放未完成的操作,从而恢复到崩溃前的最新一致状态。
- 频率: Journal 默认每 100 毫秒刷新一次(可配置)。
- Checkpoint (检查点) 机制:
- 目的: 减少崩溃恢复时间,并提供一个数据库的“一致性快照”。
- 工作原理: WiredTiger 会定期(例如每 60 秒或达到特定脏页数量时)执行一个检查点操作。在检查点过程中:
- WiredTiger 会将所有在内存中被修改但尚未写入磁盘的“脏页”(dirty pages)全部刷新到磁盘上的数据文件。
- 一旦所有脏页都成功写入磁盘,WiredTiger 会在数据文件中记录一个“检查点”,标记这个时刻的数据是完全一致且持久化的。
- 更新完成后,旧的 Journal 文件可以被截断或清理,因为所有在 Journal 中记录的操作都已经通过检查点持久化了。
- 优势:
- 快速恢复: 崩溃恢复时,只需从最近的检查点开始重放 Journal 日志,大大缩短了恢复时间。
- 数据一致性: 检查点保证了磁盘上的数据文件在某个时间点是完全一致的。
3. WiredTiger 与 MongoDB 服务器架构的关系
MongoDB 的架构可以分为两个主要层次:
-
Server 层(服务器层):
- 功能: 处理客户端连接、解析查询、优化查询计划、执行聚合操作、管理用户认证和授权、处理分片(Sharding)和副本集(Replica Set)的逻辑等。
- 定位: 它是 MongoDB 的“大脑”,负责理解客户端请求,并协调数据的读写。
- 与存储引擎的关系: Server 层不直接与磁盘交互,而是通过存储引擎提供的接口来存取数据。它将高层次的查询请求翻译成存储引擎可以理解的低层次数据操作。
-
Storage Engine 层(存储引擎层,例如 WiredTiger):
- 功能: 负责数据的实际存储、检索、索引管理、并发控制、缓存、持久化(Journaling, Checkpoint)、压缩等所有与磁盘交互相关的底层细节。
- 定位: 它是 MongoDB 的“手脚和内存”,负责高效、可靠地管理数据在磁盘和内存中的状态。
- 与 Server 层关系: Storage Engine 层对 Server 层来说是透明的,它提供了一组 API,允许 Server 层进行文档的插入、更新、删除、查找以及索引的创建和使用,而无需关心这些操作是如何具体在磁盘上实现的。
简而言之,Server 层决定“做什么”,而 Storage Engine 层(WiredTiger)决定“怎么做”。这种分离使得 MongoDB 可以支持不同的存储引擎(例如,早期的 MMAPv1,现在的 WiredTiger),以适应不同的工作负载和性能需求。
4. 存储引擎的定位与核心功能接口
4.1 定位
存储引擎位于数据库系统体系结构的底部,直接与操作系统文件系统交互,但对上层(查询处理器、事务管理器等)提供一个抽象的数据管理接口。
4.2 核心功能接口
一个存储引擎通常会提供以下核心功能接口:
- 数据访问接口:
insert(document)
: 插入文档。update(query, update_document)
: 更新文档。delete(query)
: 删除文档。find(query)
: 根据查询条件查找文档。read_page(page_id)
: 读取特定数据页。write_page(page_id, data)
: 写入特定数据页。
- 索引管理接口:
create_index(collection_name, field_names, index_options)
: 创建索引。drop_index(collection_name, index_name)
: 删除索引。lookup_index(index_name, key)
: 通过索引查找数据。range_scan_index(index_name, start_key, end_key)
: 通过索引进行范围扫描。
- 事务与并发控制接口:
begin_transaction()
: 开始一个事务。commit_transaction()
: 提交事务。rollback_transaction()
: 回滚事务。acquire_lock(resource, lock_type)
: 获取锁。release_lock(resource, lock_type)
: 释放锁。
- 缓存管理接口:
get_from_cache(page_id)
: 从缓存获取数据页。put_into_cache(page_id, data)
: 将数据页放入缓存。
- 持久化与恢复接口:
write_to_journal(log_record)
: 写入日志记录。flush_journal()
: 刷新日志到磁盘。checkpoint()
: 执行检查点。recover_from_crash()
: 从崩溃中恢复。
- 存储管理接口:
allocate_space(size)
: 分配存储空间。deallocate_space(address, size)
: 释放存储空间。compress_data(data)
: 压缩数据。decompress_data(data)
: 解压数据。
5. 单机 MongoDB 的本质与对比 MySQL 架构的相似性
5.1 单机 MongoDB 的本质
一个单机的 MongoDB 实例(运行着一个 mongod
进程)本质上是一个独立的数据库服务器,它:
- 管理所有数据: 存储一个或多个数据库中的所有集合和索引。
- 处理所有请求: 监听客户端连接,接收并处理所有查询、写入、更新和删除请求。
- 进行所有维护操作: 包括数据持久化、内存管理、并发控制、索引构建和维护等。
在单机模式下,MongoDB 提供了完整的数据存储和查询功能,但其可扩展性和高可用性是有限的。
5.2 对比 MySQL 架构的相似性
虽然 MongoDB 和 MySQL 在数据模型(文档 vs 关系表)和查询语言(JSON-like vs SQL)上有显著差异,但在底层架构和概念上,它们有许多相似之处:
特性 | 单机 MongoDB (mongod 进程) |
单机 MySQL (mysqld 进程) |
---|---|---|
核心进程 | mongod (主数据库进程) |
mysqld (主数据库进程) |
数据组织 | 数据库 → 集合 → 文档 | 数据库 → 表 → 行 |
存储引擎 | WiredTiger (默认,可插拔) | InnoDB (默认,可插拔), MyISAM 等 |
索引结构 | 变种 B+ 树 | B+ 树 |
并发控制 | MVCC (COW), 锁粒度细致 (页级或文档级) | MVCC (InnoDB), 锁粒度细致 (行级) |
缓存机制 | WiredTiger 缓存 (数据页、索引页) + LRU | InnoDB Buffer Pool (数据页、索引页) + LRU |
持久化机制 | Journal (WAL) + Checkpoint | Redo Log (WAL) + Checkpoint |
数据模型 | 文档模型 (无模式,嵌套) | 关系模型 (严格模式,表结构) |
查询语言 | BSON/JSON 查询 (MongoDB Query Language) | SQL (结构化查询语言) |
横向扩展 | 原生支持分片集群 (Sharding) | 通常需要借助外部工具或方案 (如 Shard-it) |
高可用性 | 原生支持副本集 (Replica Set) | 通过主从复制、MGR 等实现 |
核心相似之处:
- 进程模型: 两者都由一个核心守护进程(
mongod
/mysqld
)管理数据库的所有功能。 - 存储引擎层: 都采用可插拔的存储引擎架构,将底层数据管理与上层查询处理分离。
- 索引: 都广泛使用 B+ 树及其变种来加速数据检索。
- MVCC: 现代版本都支持多版本并发控制(InnoDB 也有 MVCC),以提高并发读写性能。
- WAL 和 Checkpoint: 都使用写前日志(Journal/Redo Log)和检查点机制来保证数据持久性和崩溃恢复。
- 内存缓存: 都通过管理一个大的内存区域(WiredTiger Cache / InnoDB Buffer Pool)来缓存热点数据,提高性能。
核心区别:
- 数据模型灵活性: MongoDB 的文档模型天然支持无模式和复杂嵌套,而 MySQL 的关系模型强调严格的表结构。
- 原生分布式能力: MongoDB 从设计之初就考虑了分布式部署(分片和副本集),这些是其核心功能。MySQL 则需要额外的配置和工具来实现类似的大规模分布式特性。
6. MongoDB 高扩展性设计:分片集群 (Sharding)
分片(Sharding)是 MongoDB 实现水平扩展(Horizontal Scaling)的核心机制,用于处理大数据量和高吞吐量的应用。
6.1 分片(Shard)数据切分逻辑
- 目的: 将大量数据分散存储到多个独立的服务器(或服务器组)上,从而突破单台服务器的存储和处理能力限制。
- 分片(Shard):
- 定义: 一个分片是一个独立的
mongod
实例,它存储了整个数据集的一个子集。 - 高可用性: 在生产环境中,每个分片通常是一个副本集(Replica Set),以提供数据冗余和自动故障转移。
- 定义: 一个分片是一个独立的
- 数据切分逻辑:
- 分片键(Shard Key): 这是分片的核心。你需要在集合中的一个或多个字段上定义分片键。MongoDB 使用这个键的值来决定将文档存储到哪个分片上。
- 块(Chunk): 数据是按照分片键的范围或哈希值被分成若干个逻辑上的“块”。每个块包含一个分片键范围内的所有文档。
- 块的迁移(Chunk Migration): MongoDB 的平衡器(Balancer)进程会自动监控各分片之间的数据分布情况。当某个分片的数据量过大或分布不均时,平衡器会将一些块从繁忙的分片迁移到空闲的分片,以保持数据均匀分布。
- 分片策略:
- 范围分片(Ranged Sharding): 依据分片键的范围进行数据切分。例如,按用户 ID 的范围分片。
- 优点: 范围查询效率高。
- 缺点: 容易出现热点问题(例如,新数据总是写入某个特定范围的分片)。
- 哈希分片(Hashed Sharding): 依据分片键的哈希值进行数据切分。MongoDB 会计算分片键的哈希值,然后根据哈希值进行分片。
- 优点: 数据分布更均匀,避免热点问题。
- 缺点: 范围查询可能需要查询所有分片,效率较低。
- 复合分片键(Compound Shard Key): 使用多个字段作为分片键。
- 范围分片(Ranged Sharding): 依据分片键的范围进行数据切分。例如,按用户 ID 的范围分片。
6.2 路由服务(mongos)与配置中心(Config Server)是什么?
分片集群由三个主要组件构成:分片(Shard)、路由服务(mongos)和配置中心(Config Server)。
-
路由服务(mongos):
- 定义:
mongos
是 MongoDB 分片集群的查询路由器(Query Router)。它是客户端与分片集群交互的唯一入口点。 - 功能:
- 查询路由: 接收客户端的请求,根据配置中心存储的元数据,将请求路由到一个或多个分片上。
- 结果聚合: 如果查询需要跨多个分片执行(例如,没有包含分片键的查询),
mongos
会将查询发送到所有相关分片,然后收集并合并各个分片返回的结果,最终返回给客户端。 - 元数据缓存:
mongos
会缓存配置中心的元数据,以减少与配置中心的交互次数。 - 负载均衡: 在一定程度上,
mongos
也可以在多个mongos
实例之间进行负载均衡(通过外部负载均衡器)。
- 特点:
mongos
本身是无状态的,可以部署多个实例来提高可用性和吞吐量。
- 定义:
-
配置中心(Config Server):
- 定义: 配置服务器存储了整个分片集群的元数据(metadata),包括分片键定义、块范围、分片信息、各个分片的地址等。
- 功能:
- 存储集群元数据: 这是集群运行的“蓝图”。
- 提供元数据服务:
mongos
和分片都会查询配置服务器以获取最新的集群状态信息。 - 管理块迁移: 平衡器进程(通常在 Primary Config Server 上运行)通过配置服务器的数据来管理和执行块的迁移。
- 高可用性: 配置服务器本身也是一个副本集(Config Server Replica Set),以确保元数据的持久性和高可用性。如果配置服务器不可用,集群的元数据就无法获取,整个集群将停止工作(但已连接的客户端仍然可以访问缓存数据)。
7. 副本集(Replica Set)是什么?
副本集(Replica Set)是 MongoDB 实现高可用性(High Availability)和数据冗余的核心机制。
- 定义: 一个副本集是由一组
mongod
实例组成的,它们维护相同的数据集。其中一个实例被指定为 Primary (主节点),其他实例被指定为 Secondary (从节点)。 - 组件:
- Primary (主节点):
- 接收所有写操作。
- 所有读操作默认发送到 Primary (也可配置发送到 Secondary)。
- 将其所有数据变更记录到操作日志(oplog)中。
- Secondary (从节点):
- 异步地从 Primary 节点复制数据变更(通过 oplog)。
- 保持数据与 Primary 节点同步。
- 可以为读请求提供服务(如果配置允许),从而分担 Primary 的读负载,但可能存在数据延迟。
- 在 Primary 节点故障时,参与选举新的 Primary 节点。
- Arbiter (仲裁节点 - 可选):
- 不存储任何数据,也不接收读写请求。
- 唯一的任务是参与 Primary 节点的选举。
- 当副本集成员数量为偶数时,添加仲裁节点可以确保选举总能获得多数票,避免脑裂(Split-Brain)问题,同时不增加数据存储成本。
- Primary (主节点):
- 工作原理:
- 数据同步: Primary 节点的所有写操作都会记录到其操作日志(oplog)中。Secondary 节点会持续从 Primary 节点拉取并应用这些 oplog 条目,以保持数据同步。
- 自动故障转移(Automatic Failover):
- 当 Primary 节点因为某种原因(如硬件故障、网络中断)变得不可用时,Secondary 节点会检测到 Primary 失联。
- 副本集中的 Secondary 节点会发起 Primary 选举。
- 大多数成员(包括仲裁节点)投票选出一个新的 Primary 节点。
- 一旦新的 Primary 选出,客户端连接可以自动切换到新的 Primary 节点。
- 读扩展: 通过配置“读偏好”(Read Preference),客户端可以将读请求路由到 Secondary 节点,从而实现读负载均衡。但需要注意 Secondary 节点可能存在数据滞后。
- 优势:
- 高可用性: 自动故障转移确保数据库服务持续可用。
- 数据冗余: 数据存储在多个节点上,防止单点故障导致数据丢失。
- 读扩展: Secondary 节点可以分担读请求。
8. 分布式 MongoDB 集群是什么?
分布式 MongoDB 集群通常是指结合了分片(Sharding) 和 副本集(Replica Set) 两种机制的复杂部署架构。
- 定义: 一个分布式 MongoDB 集群是一个高可伸缩、高可用、高性能的数据库系统,它能够处理巨大的数据量和高并发负载。它通过将数据分布到多个物理节点(分片)并为每个节点提供冗余(副本集)来实现这些目标。
- 组成部分:
- Shards (分片):
- 每个分片是一个独立的 MongoDB 数据库实例,存储数据集的一个子集。
- 在分布式集群中,每个分片本身通常就是一个副本集。这意味着即使某个分片内的某个节点故障,该分片的数据仍然可用并能继续提供服务。
- Config Servers (配置服务器):
- 存储整个集群的元数据(分片信息、块范围、分片键等)。
- 配置服务器本身也是一个副本集,确保元数据的高可用性和一致性。
- mongos (查询路由器):
- 客户端连接的入口,负责将请求路由到正确的分片,并聚合结果。
mongos
实例通常是无状态的,可以部署多个以提供高可用性和负载均衡。
- Shards (分片):
- 工作流:
- 客户端应用程序连接到一个或多个
mongos
实例。 mongos
实例从 Config Server Replica Set 获取集群的元数据,并缓存这些信息。- 当客户端发送一个查询或写请求时,
mongos
根据分片键和元数据将请求路由到负责该数据的一个或多个 Shard Replica Set。 - Shard Replica Set 的 Primary 节点执行操作,并将变更同步到其 Secondary 节点。
mongos
收集所有分片返回的结果(如果需要),并将其返回给客户端。- 平衡器(Balancer)进程(在 Config Server Primary 上运行)持续监控各分片之间的数据分布,自动迁移数据块以保持平衡。
- 客户端应用程序连接到一个或多个
分布式 MongoDB 集群的优势:
- 无限扩展性: 可以通过添加更多的分片来线性扩展存储容量和读写吞吐量。
- 高可用性: 每个分片和配置服务器都是副本集,提供了自动故障转移和数据冗余,从而提高了整个集群的可用性。
- 负载均衡: 数据均匀分布在多个分片上,且
mongos
可以将请求分散到这些分片,有效分担了负载。 - 数据隔离: 不同的分片可以物理隔离,从而提高安全性和性能。
通过结合这些核心概念,MongoDB 能够构建出功能强大、灵活且高度可伸缩的数据库解决方案。