MongoDB 是一个强大的分布式存储引擎,天然支持高可用、分布式和灵活设计。MongoDB 的一个很重要的设计理念是:服务端只关注底层核心能力的输出,至于怎么用,就尽可能的将工作交个客户端去决策。这也就是 MongoDB 灵活性的保证,但是灵活性带来的代价就是使用成本的提升。与 MySql 相比,想要用好 MongoDB,减少在项目中出问题,用户需要掌握的东西更多。本文致力于全方位的介绍 MongoDB 的理论和应用知识,目标是让大家可以通过阅读这篇文章之后能够掌握 MongoDB 的常用知识,具备在实际项目中高效应用 MongoDB 的能力。
本文既有 MongoDB 基础知识也有相对深入的进阶知识,同时适用于对 MonogDB 感兴趣的初学者或者希望对 MongoDB 有更深入了解的业务开发者。
前言
以下是笔者在学习和使用 MongoDB 过程中总结的 MongoDB 知识图谱。本文将按照一下图谱中依次介绍 MongoDB 的一些核心内容。由于能力和篇幅有限,本文并不会对图谱中全部内容都做深入分析,后续将会针对特定条目做专门的分析。同时,如果图谱和内容中有错误或疏漏的地方,也请大家随意指正,笔者这边会积极修正和完善。
本文按照图谱从以下 3 个方面来介绍 MongoDB 相关知识:
- 基础知识:主要介绍 MongoDB 的重要特性,No Schema、高可用、分布式扩展等特性,以及支撑这些特性的相关设计
- 应用接入:主要介绍 MongoDB 的一些测试数据、接入方式、spring-data-mongo 应用以及使用 Mongo 的一些注意事项。
- 进阶知识:主要介绍 MongoDB 的一些核心功能的设计实现,包括 WiredTiger 存储引擎介绍、Page/Chunk 等数据结构、一致性/高可用保证、索引等相关知识。
(ps:如果大家对某部分知识感兴趣,这里又没有讲解的话,可以自行在网上搜索相关知识,或者连续我,大家共同讨论学习进步)
第一部分:基础知识
MongoDB 是基于文档的 NoSql 存储引擎。MongoDB 的数据库管理由数据库、Collection(集合,类似 MySql 的表)、Document(文档,类似 MySQL 的行)组成,每个 Document 都是一个类 JSON 结构 BSON 结构数据。
MongoDB 的核心特性是:No Schema、高可用、分布式(可平行扩展),另外 MongoDB 自带数据压缩功能,使得同样的数据存储所需的资源更少。本节将会依次介绍这些特性的基本知识,以及 MongoDB 是如何实现这些能力的。
1.1 No Schema
MongoDB 是文档型数据库,其文档组织结构是 BSON(Binary Serialized Document Format) 是类 JSON 的二进制存储格式,数据组织和访问方式完全和 JSON 一样。支持动态的添加字段、支持内嵌对象和数组对象,同时它也对 JSON 做了一些扩充,如支持 Date 和 BinData 数据类型。正是 BSON 这种字段灵活管理能力赋予了 Mongo 的 No Schema 或者 Schema Free 的特性。
No Schema 特性带来的好处包括:
- 强大的表现能力:对象嵌套和数组结构可以让数据库中的对象具备更高的表现能力,能够用更少的数据对象表现复杂的领域模型对象。
- 便于开发和快速迭代:灵活的字段管理,使得项目迭代新增字段非常容易
- 降低运维成本:数据对象结构变更不需要执行 DDL 语句,降低 Online 环境的数据库操作风险,特别是在海量数据分库分表场景。
MongoDB 在提供 No Schema 特性基础上,提供了部分可选的 Schema 特性:Validation。其主要功能有包括: - 规定某个 Document 对象必须包含某些字段
- 规定 Document 某个字段的数据类型 (中type(MongoDB中 开头的都是关键字)
- 规定 Document 某个字段的取值范围:可以是枚举 ,或者正则in,或者正则regex
上面的字段包含内嵌文档的,也就是说,你可以指定 Document 内任意一层 JSON 文件的字段属性。validator 的值有两种,一种是简单的 JSON Object,另一种是通过关键字 $jsonSchema 指定。以下是简单示例,想了解更多请参考官方文档: MongoDB JSON Schema 详解。
方式一:
db.createCollection("saky_test_validation",{validator:
{
$and:[
{name:{$type: "string"}},
{status:{$in:["INIT","DEL"]}}]
}
})
方式二:
db.createCollection("saky_test_validation", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "name", "status", ],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
status: {
enum: [ "INIT", "DEL"],
description: "can only be one of the enum values and is required"
}
} }})
资料领取直通车:大厂面试题锦集+视频教程https://docs.qq.com/doc/DTlhVekRrZUdDUEpy
Linux服务器学习网站:C/C++Linux服务器开发/后台架构师https://ke.qq.com/course/417774?flowToken=1028592
1.2 MongoDB 的高可用
高可用是 MongoDB 最核心的功能之一,相信很多同学也是因为这一特性才想深入了解它的。那么本节就来说下 MongoDB 通过哪些方式来实现它的高可用,然后给予这些特性我们可以实现什么程度的高可用。
相信一旦提到高可用,浮现在大家脑海里会有如下几个问题:
- 是什么:MongoDB 高可用包括些什么功能?它能保证多大程度的高可用?
- 为什么:MongoDB 是怎样做到这些高可用的?
- 怎么用:我们需要做些怎样的配置或者使用才能享受到 MongoDB 的高可用特性?
那么,带着这些问题,我们继续看下去,看完大家应该会对这些问题有所了解了。
1.2.1 MongDB 复制集群
MongoDB 高可用的基础是复制集群,复制集群本质来说就是一份数据存多份,保证一台机器挂掉了数据不会丢失。一个副本集至少有 3 个节点组成:
- 至少一个主节点(Primary):负责整个集群的写操作入口,主节点挂掉之后会自动选出新的主节点。
- 一个或多个从节点(Secondary):一般是 2 个或以上,从主节点同步数据,在主节点挂掉之后选举新节点。
- 零个或 1 个仲裁节点(Arbiter):这个是为了节约资源或者多机房容灾用,只负责主节点选举时投票不存数据,保证能有节点获得多数赞成票。
从上面的节点类型可以看出,一个三节点的复制集群可能是 PSS 或者 PSA 结构。PSA 结构优点是节约成本,但是缺点是 Primary 挂掉之后,一些依赖 majority(多数)特性的写功能出问题,因此一般不建议使用。
复制集群确保数据一致性的核心设计是: - Oplog:oplog 是 MongoDB 的变更记录,类似 MySQL 的 binlog。MongoDB 的写操作都由 Primary 节点负责,Primary 节点会把每一个变更写到内存和 oplog,然后 100ms 一次将 oplog 刷盘。Secondary 节点通过拉取 oplog 信息,回放操作实现数据同步。
- Checkpoint:上面提到了 MongoDB 的写只写了内存和 oplog ,并没有做数据持久化,Checkpoint 就是将内存变更刷新到磁盘持久化的过程。MongoDB 会每 60s 一次将内存中的变更刷盘,并记录当前持久化点(checkpoint),以便数据库在重启后能快速恢复数据。
- 节点选举:MongoDB 的节点选举规则能够保证在 Primary 挂掉之后选取的新节点一定是集群中数据最全的一个,在 3.3.1 节点选举有说明具体实现
从上面 3 点我们可以得出 MongoDB 高可用的如下结论:
- MongoDB宕机重启之后可以通过 checkpoint 快速恢复上一个 60s 之前的数据。
- MongoDB最后一个 checkpoint 到宕机期间的数据可以通过 oplog 回放恢复。
- oplog因为是 100ms 刷盘一次,因此至多会丢失 100ms 的数据(这个可以通过 WriteConcern 的参数控制不丢失,只是性能会受影响,适合可靠性要求非常严格的场景)
- 如果在写数据开启了多数写,那么就算 Primary 宕机了也是至多丢失 100ms 数据(可避免,同上)
1.2.2 读写策略
从上一小节发现,MongoDB 的高可用机制在不同的场景表现是不一样的。实际上,MongoDB 提供了一整套的机制让用户根据自己业务场景选择不同的策略。这里要说的就是 MongoDB 的读写策略,根据用户选取不同的读写策略,你会得到不同程度的数据可靠性和一致性保障。这些对业务开放者非常重要,因为你只有彻底掌握了这些知识,才能根据自己的业务场景选取合适的策略,同时兼顾读写性能和可靠性。
Write Concern —— 写策略
控制服务端一次写操作在什么情况下才返回客户端成功,由两个参数控制:
- w 参数&#x