MongoDB-基础架构
flowchart TD
subgraph 客户端层
A[应用程序] -->|驱动连接| B[mongos路由]
A -->|直连| C[mongod副本集]
end
subgraph 路由层
B -->|元数据查询| D[Config Server]
B -->|请求分发| E[Shard1]
B -->|请求分发| F[Shard2]
end
subgraph 分片集群
E -->|副本集| G[Primary]
E -->|副本集| H[Secondary]
E -->|副本集| I[Arbiter]
F -->|副本集| J[Primary]
F -->|副本集| K[Secondary]
end
subgraph 存储引擎
G -->|WiredTiger| L[内存缓存]
G -->|WiredTiger| M[数据文件.wt]
G -->|WiredTiger| N[索引B-Tree]
end
subgraph 物理存储
M --> O[磁盘]
N --> O
endMongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常流行的 文档类型数据库 。
在高负载的情况下,MongoDB 天然支持水平扩展和高可用,可以很方便地添加更多的节点/实例,以保证服务性能和可用性。在许多场景下,MongoDB 可以用于代替传统的关系型数据库或键/值存储方式,皆在为 Web 应用提供可扩展的高可用高性能数据存储解决方案。
核心优势
灵活的文档模型
MongoDB采用无模式的BSON文档格式,类似于JSON,这意味着它能够存储复杂的数据结构,并且允许文档在结构上彼此不同。这种模型为数据的快速迭代提供了便利,非常适合现代应用程序的快速开发需求。
高性能
MongoDB为读写操作提供了高效的性能。它支持多种索引类型,包括地理空间、文本搜索和复合索引,确保查询速度。同时,它的存储引擎针对性能进行了优化,特别是在处理大数据量和高并发情况下。
易于扩展
MongoDB设计之初就考虑到了可扩展性。它通过分片技术支持水平扩展,允许数据分布在多个服务器上,以实现数据集的增长和分布式查询的处理,满足大规模数据集的应用场景。
多样的数据处理功能
MongoDB提供了强大的聚合框架,支持各种复杂的数据处理操作,比如数据过滤、转换、组合等。此外,它还支持MapReduce操作,供用户进行复杂的数据分析。
成熟的生态和社区支持
MongoDB有着广泛的用户基础和活跃的开发者社区,提供了丰富的学习资源、第三方工具和社区支持。MongoDB Inc.还提供了专业的服务和咨询,帮助企业有效地使用MongoDB。
核心组件
MongoDB 的核心组件包括服务端进程、存储引擎和分布式协调组件:
- mongod
- 主服务进程,负责数据存储、查询处理、索引管理等核心功能。
- 在单节点部署中独立运行
- 在副本集中作为主节点(Primary)或从节点(Secondary)
- 在分片集群中作为分片实例。
- mongos
- 分片集群的路由进程,负责将客户端请求路由到对应分片,并合并结果。不存储数据,仅维护集群元数据。
- 存储引擎
- WiredTiger(默认引擎):支持文档级并发控制、压缩、多文档事务(4.0+版本)。
- MMAPv1(已淘汰):基于内存映射文件,表级锁性能较低。
技术架构
数据存储层
MongoDB使用内存映射文件存储引擎(如WiredTiger)将数据持久化到磁盘。存储引擎负责数据的读写、压缩、加密等操作。MongoDB将数据划分为多个集合(collection),每个集合包含多个文档(document)。文档是MongoDB的基本数据单位,以BSON格式存储。
数据模型层
MongoDB的数据模型基于文档,支持嵌套文档和数组。这使得MongoDB能够存储复杂的数据结构,如树形结构、图形数据等。MongoDB还提供了丰富的数据类型,如字符串、整数、浮点数、日期、二进制数据等。
查询语言层
MongoDB使用基于文档的查询语言(MongoDB Query Language,MQL),支持丰富的查询操作符和聚合管道。MQL允许用户根据文档的结构和内容进行查询,实现灵活的数据检索和分析。
索引层
MongoDB支持多种类型的索引,如单字段索引、复合索引、地理空间索引等。索引可以提高查询性能,加快数据的检索速度。MongoDB还支持索引交集和索引覆盖扫描等优化技术,进一步提高查询效率。
复制和分片层
MongoDB支持主从复制和分片集群,确保数据的高可用性和可扩展性。主从复制可以实现数据的备份和故障恢复;分片集群可以将数据分布在多个节点上,实现水平扩展和负载均衡。
事务层
MongoDB从4.0版本开始支持多文档事务,确保数据的一致性和完整性。事务是一系列操作的原子单位,要么全部成功,要么全部失败。MongoDB的事务支持隔离级别为“可重复读”(Read Committed),满足大多数应用程序的需求。
安全性和认证层
MongoDB提供了一系列安全特性,如身份验证、授权、加密等。身份验证可以确保只有授权的用户才能访问数据库;授权可以控制用户对数据库的访问权限;加密可以保护数据在传输和存储过程中的安全。
客户端驱动层
MongoDB提供了多种编程语言的客户端驱动,如Java、Python、Node.js等。客户端驱动负责与MongoDB服务器进行通信,实现数据的增删改查等操作。MongoDB的客户端驱动具有良好的兼容性和性能,方便开发者在各种环境中使用MongoDB。
数据模型
MongoDB 采用文档导向的存储模型:
- 文档(Document):基本数据单元,类似 JSON 对象(如
{ "name": "Alice", "age": 30 })。 - 集合(Collection):文档的容器,无固定模式,支持动态字段增减。
- 数据库(Database):逻辑分组,包含多个集合(如
admin、local、config等内置库)。
工作原理
MongoDB是一种基于文档的NoSQL数据库,通过其灵活的文档模型、强大的索引和查询系统、分片、复制集合等一系列机制,提供了一个高性能、易于扩展、支持高并发的数据库解决方案,适用于各种现代应用程序的数据存储和处理需求。
存储机制
MongoDB内部使用BSON(Binary JSON)格式来存储数据,这是一种类JSON的二进制形式,允许存储更丰富的数据类型。每个BSON文档对应于关系数据库中的一行数据,并且每个文档可以拥有不同的字段。这些文档被组织在集合(collections)中,类似于关系数据库的表。集合内部不强制要求一个统一的模式,这就赋予了MongoDB很高的灵活性,可以灵活地适应应用程序数据要求的变化。
查询处理
MongoDB使用动态查询语言,用户可通过各种操作符来构建复杂的查询。MongoDB的查询引擎会将这些查询转换为内部操作,并使用优化过的策略来检索数据。为了提高查询效率,MongoDB支持索引,包括单字段索引、复合索引、多键索引、地理空间索引等,这些索引有助于快速查找数据。
数据分片
分片是MongoDB处理大数据集的关键机制,可以将数据跨多个服务器分布存储。通过对数据进行水平分割,MongoDB可以支持集群的可扩展性,使得数据库能够处理更大规模的数据。每个分片负责集群中一部分数据,并且可以在多个副本集之间复制,以确保高可用性和数据冗余。
复制和高可用性
MongoDB通过副本集来实现数据的冗余和高可用性。一个副本集由多个MongoDB实例组成,其中一个实例作为主节点负责处理客户端请求,其他实例作为从节点可以在主节点出现故障时接管服务。主从之间的数据同步是自动的,这保证了数据的一致性。
MongoDB 存储引擎
MongoDB 支持哪些存储引擎?
存储引擎(Storage Engine)是数据库的核心组件,负责管理数据在内存和磁盘中的存储方式。
与 MySQL 一样,MongoDB 采用的也是 插件式的存储引擎架构 ,支持不同类型的存储引擎,不同的存储引擎解决不同场景的问题。在创建数据库或集合时,可以指定存储引擎。
插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如MySQL既可以支持B-Tree结构的InnoDB存储引擎,还可以支持LSM结构的RocksDB存储引擎。
在存储引擎刚出来的时候,默认是使用 MMAPV1 存储引擎,MongoDB4.x 版本不再支持 MMAPv1 存储引擎。
现在主要有下面这两种存储引擎:
- WiredTiger 存储引擎 :自 MongoDB 3.2 以后,默认的存储引擎为 WiredTiger 存储引擎 。非常适合大多数工作负载,建议用于新部署。WiredTiger 提供文档级并发模型、检查点和数据压缩(后文会介绍到)等功能。
- In-Memory 存储引擎 :In-Memory 存储引擎在 MongoDB Enterprise 中可用。它不是将文档存储在磁盘上,而是将它们保留在内存中以获得更可预测的数据延迟。
此外,MongoDB 3.0 提供了 可插拔的存储引擎 API ,允许第三方为 MongoDB 开发存储引擎,这点和 MySQL 也比较类似。
WiredTiger 基于 LSM Tree 还是 B+ Tree?
目前绝大部分流行的数据库存储引擎都是基于 B/B+ Tree 或者 LSM(Log Structured Merge) Tree 来实现的。对于 NoSQL 数据库来说,绝大部分(比如 HBase、Cassandra、RocksDB)都是基于 LSM 树,MongoDB 不太一样。
此外,WiredTiger 还支持 LSM(Log Structured Merge) 树作为存储结构,MongoDB 在使用WiredTiger 作为存储引擎时,默认使用的是 B+ 树。
使用 B+ 树时,WiredTiger 以 page 为基本单位往磁盘读写数据。B+ 树的每个节点为一个 page,共有三种类型的 page:
- root page(根节点) :B+ 树的根节点。
- internal page(内部节点) :不实际存储数据的中间索引节点。
- leaf page(叶子节点):真正存储数据的叶子节点,包含一个页头(page header)、块头(block header)和真正的数据(key/value),其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的checksum、块在磁盘上的寻址位置等信息。
MongoDB 聚合
MongoDB 聚合有什么用?
实际项目中,我们经常需要将多个文档甚至是多个集合汇总到一起计算分析(比如求和、取最大值)并返回计算后的结果,这个过程被称为 聚合操作 。
根据官方文档介绍,我们可以使用聚合操作来:
- 将来自多个文档的值组合在一起。
- 对集合中的数据进行的一系列运算。
- 分析数据随时间的变化。
MongoDB 提供了哪几种执行聚合的方法?
MongoDB 提供了两种执行聚合的方法:
- 聚合管道(Aggregation Pipeline) :执行聚合操作的首选方法。
- 单一目的聚合方法(Single purpose aggregation methods) :也就是单一作用的聚合函数比如
count()、distinct()、estimatedDocumentCount()。
绝大部分文章中还提到了 map-reduce 这种聚合方法。不过,从 MongoDB 5.0 开始,map-reduce 已经不被官方推荐使用了,替代方案是 聚合管道。聚合管道提供比 map-reduce 更好的性能和可用性。
MongoDB 聚合管道由多个阶段组成,每个阶段在文档通过管道时转换文档。每个阶段接收前一个阶段的输出,进一步处理数据,并将其作为输入数据发送到下一个阶段。
每个管道的工作流程是:
- 接受一系列原始数据文档
- 对这些文档进行一系列运算
- 结果文档输出给下一个阶段
MongoDB 事务
NoSQL 数据库通常不支持事务,为了可扩展和高性能进行了权衡。不过,也有例外,MongoDB 就支持事务。
与关系型数据库一样,MongoDB 事务同样具有 ACID 特性:
- 原子性(
Atomicity) :事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 一致性(
Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; - 隔离性(
Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。WiredTiger 存储引擎支持读未提交( read-uncommitted )、读已提交( read-committed )和快照( snapshot )隔离,MongoDB 启动时默认选快照隔离。在不同隔离级别下,一个事务的生命周期内,可能出现脏读、不可重复读、幻读等现象。 - 持久性(
Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
MongoDB 单文档原生支持原子性,也具备事务的特性。当谈论 MongoDB 事务的时候,通常指的是 多文档 。MongoDB 4.0 加入了对多文档 ACID 事务的支持,但只支持复制集部署模式下的 ACID 事务,也就是说事务的作用域限制为一个副本集内。MongoDB 4.2 引入了 分布式事务 ,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
根据官方文档介绍:
从 MongoDB 4.2 开始,分布式事务和多文档事务在 MongoDB 中是一个意思。分布式事务是指分片集群和副本集上的多文档事务。从 MongoDB 4.2 开始,多文档事务(无论是在分片集群还是副本集上)也称为分布式事务。
在大多数情况下,多文档事务比单文档写入会产生更大的性能成本。对于大部分场景来说, 非规范化数据模型(嵌入式文档和数组) 依然是最佳选择。也就是说,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
注意 :
- 从MongoDB 4.2开始,多文档事务支持副本集和分片集群,其中:主节点使用WiredTiger存储引擎,同时从节点使用WiredTiger存储引擎或In-Memory存储引擎。在MongoDB 4.0中,只有使用WiredTiger存储引擎的副本集支持事务。
- 在MongoDB 4.2及更早版本中,你无法在事务中创建集合。从 MongoDB 4.4 开始,您可以在事务中创建集合和索引。有关详细信息,请参阅 在事务中创建集合和索引。
MongoDB 索引
MongoDB 索引有什么用?
和关系型数据库类似,MongoDB 中也有索引。索引的目的主要是用来提高查询效率,如果没有索引的话,MongoDB 必须执行 集合扫描 ,即扫描集合中的每个文档,以选择与查询语句匹配的文档。如果查询存在合适的索引,MongoDB 可以使用该索引来限制它必须检查的文档数量。并且,MongoDB 可以使用索引中的排序返回排序后的结果。
虽然索引可以显著缩短查询时间,但是使用索引、维护索引是有代价的。在执行写入操作时,除了要更新文档之外,还必须更新索引,这必然会影响写入的性能。因此,当有大量写操作而读操作少时,或者不考虑读操作的性能时,都不推荐建立索引。
MongoDB 支持哪些类型的索引?
MongoDB 支持多种类型的索引,包括单字段索引、复合索引、多键索引、哈希索引、文本索引、 地理位置索引等,每种类型的索引有不同的使用场合。
- 单字段索引: 建立在单个字段上的索引,索引创建的排序顺序无所谓,MongoDB 可以头/尾开始遍历。
- 复合索引: 建立在多个字段上的索引,也可以称之为组合索引、联合索引。
- 多键索引 :MongoDB 的一个字段可能是数组,在对这种字段创建索引时,就是多键索引。MongoDB 会为数组的每个值创建索引。就是说你可以按照数组里面的值做条件来查询,这个时候依然会走索引。
- 哈希索引 :按数据的哈希值索引,用在哈希分片集群上。
- 文本索引: 支持对字符串内容的文本搜索查询。文本索引可以包含任何值为字符串或字符串元素数组的字段。一个集合只能有一个文本搜索索引,但该索引可以覆盖多个字段。MongoDB 虽然支持全文索引,但是性能低下,暂时不建议使用。
- 地理位置索引: 基于经纬度的索引,适合 2D 和 3D 的位置查询。
- 唯一索引 :确保索引字段不会存储重复值。如果集合已经存在了违反索引的唯一约束的文档,则后台创建唯一索引会失败。
- TTL 索引 :TTL 索引提供了一个过期机制,允许为每一个文档设置一个过期时间,当一个文档达到预设的过期时间之后就会被删除。
复合索引中字段的顺序有影响吗?
复合索引中字段的顺序非常重要,例如下图中的复合索引由{userid:1, score:-1}组成,则该复合索引首先按照userid升序排序;然后再每个userid的值内,再按照score降序排序。
在复合索引中,按照何种方式排序,决定了该索引在查询中是否能被应用到。
走复合索引的排序:
db.s2.find().sort({"userid": 1, "score": -1})
db.s2.find().sort({"userid": -1, "score": 1})不走复合索引的排序:
db.s2.find().sort({"userid": 1, "score": 1})
db.s2.find().sort({"userid": -1, "score": -1})
db.s2.find().sort({"score": 1, "userid": -1})
db.s2.find().sort({"score": 1, "userid": 1})
db.s2.find().sort({"score": -1, "userid": -1})
db.s2.find().sort({"score": -1, "userid": 1})我们可以通过 explain 进行分析:
db.s2.find().sort({"score": -1, "userid": 1}).explain()复合索引遵循左前缀原则吗?
MongoDB 的复合索引遵循左前缀原则 :拥有多个键的索引,可以同时得到所有这些键的前缀组成的索引,但不包括除左前缀之外的其他子集。比如说,有一个类似 {a: 1, b: 1, c: 1, ..., z: 1} 这样的索引,那么实际上也等于有了 {a: 1}、{a: 1, b: 1}、{a: 1, b: 1, c: 1} 等一系列索引,但是不会有 {b: 1} 这样的非左前缀的索引。
什么是 TTL 索引?
TTL 索引提供了一个过期机制,允许为每一个文档设置一个过期时间 expireAfterSeconds ,当一个文档达到预设的过期时间之后就会被删除。TTL 索引除了有 expireAfterSeconds 属性外,和普通索引一样。
数据过期对于某些类型的信息很有用,比如机器生成的事件数据、日志和会话信息,这些信息只需要在数据库中保存有限的时间。
TTL 索引运行原理
- MongoDB 会开启一个后台线程读取该 TTL 索引的值来判断文档是否过期,但不会保证已过期的数据会立马被删除,因后台线程每 60 秒触发一次删除任务,且如果删除的数据量较大,会存在上一次的删除未完成,而下一次的任务已经开启的情况,导致过期的数据也会出现超过了数据保留时间 60 秒以上的现象。
- 对于副本集而言,TTL 索引的后台进程只会在 Primary 节点开启,在从节点会始终处于空闲状态,从节点的数据删除是由主库删除后产生的 oplog 来做同步。
TTL 索引限制 :
- TTL 索引是单字段索引。复合索引不支持 TTL
_id字段不支持 TTL 索引。- 无法在上限集合(Capped Collection)上创建 TTL 索引,因为 MongoDB 无法从上限集合中删除文档。
- 如果某个字段已经存在非 TTL 索引,那么在该字段上无法再创建 TTL 索引。
什么是覆盖索引查询?
根据官方文档介绍,覆盖查询是以下的查询:
- 所有的查询字段是索引的一部分。
- 结果中返回的所有字段都在同一索引中。
- 查询中没有字段等于
null。
由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。因为索引存在于内存中,从索引中获取数据比通过扫描文档读取数据要快得多。
举个例子:我们有如下 users 集合:
{
"_id": ObjectId("53402597d852426020000002"),
"contact": "987654321",
"dob": "01-01-1991",
"gender": "M",
"name": "Tom Benzamin",
"user_name": "tombenzamin"
}我们在 users 集合中创建联合索引,字段为 gender 和 user_name :
db.users.ensureIndex({gender:1,user_name:1})现在,该索引会覆盖以下查询:
db.users.find({gender:"M"},{user_name:1,_id:0})为了让指定的索引覆盖查询,必须显式地指定 _id: 0 来从结果中排除 _id 字段,因为索引不包括 _id 字段。
集群架构模式
MongoDB的三种主要集群架构模式分别是主从复制(Master-Slave)、副本集(Replica Set)和分片(Sharding)。
主从复制(Master-Slave)
这是一种简单的复制模式,其中一台服务器被配置为主服务器(Master),负责处理所有的写操作和部分读操作,而其他服务器则作为从服务器(Slave),主要处理读操作以及作为主服务器的备份。然而,主从复制模式存在一些缺点,例如,主节点故障时,系统无法自动切换,需要手动干预;同时,主从复制模式下数据一致性的保障也相对较弱。因此,MongoDB官方已经不建议在新的生产环境中使用这种模式。
副本集(Replica Set)
副本集是MongoDB推荐的生产环境部署模式。在副本集中,每个节点都可以担任主节点或从节点的角色,通过异步复制数据到多个服务器上,保证了数据的高可用性和冗余性。
实现 failover :当主节点出现故障时,副本集可以自动进行故障切换,选择一个从节点成为新的主节点,从而保证了服务的连续性。此外,副本集还提供了数据冗余,增强了数据的容错能力。
实现读写分离 :我们可以设置从节点上可以读取数据,主节点负责写入数据,这样的话就实现了读写分离,减轻了主节点读写压力过大的问题。MongoDB 4.0 之前版本如果主库压力不大,不建议读写分离,因为写会阻塞读,除非业务对响应时间不是非常关注以及读取历史数据接受一定时间延迟。
通常来说,一个复制集群包含 1 个主节点(Primary),多个从节点(Secondary)以及零个或 1 个仲裁节点(Arbiter)。
- 主节点 :整个集群的写操作入口,接收所有的写操作,并将集合所有的变化记录到操作日志中,即 oplog。主节点挂掉之后会自动选出新的主节点。
- 从节点 :从主节点同步数据,在主节点挂掉之后选举新节点。不过,从节点可以配置成 0 优先级,阻止它在选举中成为主节点。
- 仲裁节点 :这个是为了节约资源或者多机房容灾用,只负责主节点选举时投票不存数据,保证能有节点获得多数赞成票。
flowchart TD
subgraph 客户端层
A[Client Application] --> B[Driver]
end
subgraph 主节点
C[Primary]
end
subgraph 从节点
D[Secondary]
E[Secondary]
end
B -->|Writes| C
B -->|Reads| C
C -->|Replication| D
C -->|Replication| E
style A fill:#333333,color:white
style B fill:#333333,color:white
style C fill:#0066cc,color:white
style D fill:#009933,color:white
style E fill:#009933,color:white主节点与备节点之间是通过 oplog(操作日志) 来同步数据的。oplog 是 local 库下的一个特殊的 上限集合(Capped Collection) ,用来保存写操作所产生的增量日志,类似于 MySQL 中 的 Binlog。
上限集合类似于定长的循环队列,数据顺序追加到集合的尾部,当集合空间达到上限时,它会覆盖集合中最旧的文档。上限集合的数据将会被顺序写入到磁盘的固定空间内,所以,I/O 速度非常快,如果不建立索引,性能更好。
当主节点上的一个写操作完成后,会向 oplog 集合写入一条对应的日志,而从节点则通过这个 oplog 不断拉取到新的日志,在本地进行回放以达到数据同步的目的。
副本集最多有一个主节点。如果当前主节点不可用,一个选举会抉择出新的主节点。MongoDB 的节点选举规则能够保证在 Primary 挂掉之后选取的新节点一定是集群中数据最全的一个。
分片(Sharding)
分片是MongoDB处理大规模数据的核心技术。通过将数据分散存储到多个服务器上,分片可以显著提高系统的整体性能和可扩展性。每个分片都是一个独立的数据库,可以独立地进行数据复制和故障恢复。在实际生产环境中,通常将副本集和分片两种技术结合使用,以实现既高性能又高可用性的数据存储解决方。
分片集群角色
- Shard角色(或称为分片服务器):
这是MongoDB分片集群中的数据节点,用于存储实际的数据块。在实际生产环境中,一个Shard角色可以由几台机器组成一个副本集(Replica Set)来承担,以防止主机单点故障,保证数据的高可用性和完整性。Shard角色可以是一个副本集,也可以是单独的一台服务器。
- Config Server角色(或称为配置服务器):
这类角色主要用来保存MongoDB分片集群的元数据信息,包括各个分片包含了哪些数据的信息,以及数据块的分布信息等。Config Server角色通常由一个独立的mongod进程来运行,并且为了保证其高可用性,通常会将其运行为一个副本集。它不需要太多的存储空间,因为保存的只是数据的分布表。
Router角色(或称为路由服务器、mongos):
这是MongoDB分片集群中的前端路由,客户端由此接入,让整个集群看上去像单一数据库。Router角色主要用来接收客户端的读写请求,并将请求路由到相应的分片上进行处理。为了使得Router角色的高可用,通常会用多个节点来组成Router高可用集群。Router角色通常由mongos实例来运行。
以上三种角色共同协作,实现了MongoDB的分片集群功能,使得MongoDB能够支持大规模的数据存储和高并发的读写操作。
数据读写流程
- 客户端发送请求:客户端通过MongoDB的驱动程序连接到Router角色(mongos实例)。客户端发送读写请求到Router,请求中包含了要操作的数据库、集合以及具体的CRUD(增删改查)操作。
- Router路由请求:Router接收到客户端的请求后,会根据请求中的元数据信息(如数据库名、集合名和查询条件等),查询Config Server来获取数据的分片信息。Config Server返回相关的分片信息给Router,告诉它应该将数据路由到哪个Shard上进行处理。
- Router转发请求:Router根据从Config Server获取的分片信息,将客户端的请求转发到相应的Shard上。如果请求涉及多个Shard上的数据(如跨分片的查询),Router可能会将请求拆分成多个子请求,并分别发送到相关的Shard上进行处理。
- Shard处理请求:Shard接收到Router转发的请求后,会在本地执行相应的CRUD操作。如果是写操作(如插入、更新、删除),Shard会在本地进行数据变更,并将变更结果返回给Router;如果是读操作(如查询),Shard会查询本地存储的数据,并将查询结果返回给Router。
- Router汇总结果:如果请求涉及多个Shard上的数据,Router会等待所有Shard返回结果后,对结果进行汇总和排序等操作(如果需要的话),然后将最终的结果返回给客户端。
- 客户端接收结果:客户端通过MongoDB的驱动程序接收到Router返回的结果,完成一次数据读写操作。
什么是分片键?
分片键(Shard Key) 是数据分区的前提, 从而实现数据分发到不同服务器上,减轻服务器的负担。也就是说,分片键决定了集合内的文档如何在集群的多个分片间的分布状况。
分片键就是文档里面的一个字段,但是这个字段不是普通的字段,有一定的要求:
- 它必须在所有文档中都出现。
- 它必须是集合的一个索引,可以是单索引或复合索引的前缀索引,不能是多索引、文本索引或地理空间位置索引。
- MongoDB 4.2 之前的版本,文档的分片键字段值不可变。MongoDB 4.2 版本开始,除非分片键字段是不可变的
_id字段,否则您可以更新文档的分片键值。MongoDB 5.0 版本开始,实现了实时重新分片(live resharding),可以实现分片键的完全重新选择。 - 它的大小不能超过 512 字节。
如何选择分片键?
选择合适的片键对 sharding 效率影响很大,主要基于如下四个因素(摘自分片集群使用注意事项 - - 腾讯云文档):
- 取值基数 取值基数建议尽可能大,如果用小基数的片键,因为备选值有限,那么块的总数量就有限,随着数据增多,块的大小会越来越大,导致水平扩展时移动块会非常困难。例如:选择年龄做一个基数,范围最多只有100个,随着数据量增多,同一个值分布过多时,导致 chunck 的增长超出 chuncksize 的范围,引起 jumbo chunk,从而无法迁移,导致数据分布不均匀,性能瓶颈。
- 取值分布 取值分布建议尽量均匀,分布不均匀的片键会造成某些块的数据量非常大,同样有上面数据分布不均匀,性能瓶颈的问题。
- 查询带分片 查询时建议带上分片,使用分片键进行条件查询时,mongos 可以直接定位到具体分片,否则 mongos 需要将查询分发到所有分片,再等待响应返回。
- 避免单调递增或递减 单调递增的 sharding key,数据文件挪动小,但写入会集中,导致最后一篇的数据量持续增大,不断发生迁移,递减同理。
综上,在选择片键时要考虑以上4个条件,尽可能满足更多的条件,才能降低 MoveChuncks 对性能的影响,从而获得最优的性能体验。
分片策略有哪些?
MongoDB 支持两种分片算法来满足不同的查询需求(摘自MongoDB 分片集群介绍 - 阿里云文档):
基于范围的分片
MongoDB 按照分片键(Shard Key)的值的范围将数据拆分为不同的块(Chunk),每个块包含了一段范围内的数据。当分片键的基数大、频率低且值非单调变更时,范围分片更高效。
- 优点:Mongos 可以快速定位请求需要的数据,并将请求转发到相应的 Shard 节点中。
- 缺点:可能导致数据在 Shard 节点上分布不均衡,容易造成读写热点,且不具备写分散性。
- 适用场景:分片键的值不是单调递增或单调递减、分片键的值基数大且重复的频率低、需要范围查询等业务场景。
基于 Hash 值的分片
MongoDB 计算单个字段的哈希值作为索引值,并以哈希值的范围将数据拆分为不同的块(Chunk)。
- 优点:可以将数据更加均衡地分布在各 Shard 节点中,具备写分散性。
- 缺点:不适合进行范围查询,进行范围查询时,需要将读请求分发到所有的 Shard 节点。
- 适用场景:分片键的值存在单调递增或递减、片键的值基数大且重复的频率低、需要写入的数据随机分发、数据读取随机性较大等业务场景。
除了上述两种分片策略,您还可以配置 复合片键 ,例如由一个低基数的键和一个单调递增的键组成。
分片数据如何存储?
Chunk(块) 是 MongoDB 分片集群的一个核心概念,其本质上就是由一组 Document 组成的逻辑数据单元。每个 Chunk 包含一定范围片键的数据,互不相交且并集为全部数据,即离散数学中划分的概念。
分片集群不会记录每条数据在哪个分片上,而是记录 Chunk 在哪个分片上一级这个 Chunk 包含哪些数据。
默认情况下,一个 Chunk 的最大值默认为 64MB(可调整,取值范围为 1~1024 MB。如无特殊需求,建议保持默认值),进行数据插入、更新、删除时,如果此时 Mongos 感知到了目标 Chunk 的大小或者其中的数据量超过上限,则会触发 Chunk 分裂。
%% 原始来源:知乎@随风
flowchart TD
A[ShardA<br/>64.2 MB] --> B{Split}
B --> C[ShardA<br/>32.1 MB]
B --> D[ShardX<br/>32.1 MB]数据的增长会让 Chunk 分裂得越来越多。这个时候,各个分片上的 Chunk 数量可能会不平衡。Mongos 中的 均衡器(Balancer) 组件就会执行自动平衡,尝试使各个 Shard 上 Chunk 的数量保持均衡,这个过程就是 再平衡(Rebalance)。默认情况下,数据库和集合的 Rebalance 是开启的。
如下图所示,随着数据插入,导致 Chunk 分裂,让 AB 两个分片有 3 个 Chunk,C 分片只有一个,这个时候就会把 B 分配的迁移一个到 C 分片实现集群数据均衡。
Balancer 是 MongoDB 的一个运行在 Config Server 的 Primary 节点上(自 MongoDB 3.4 版本起)的后台进程,它监控每个分片上 Chunk 数量,并在某个分片上 Chunk 数量达到阈值进行迁移。
graph LR
subgraph Shard A
A1[Chunk]
A2[Chunk]
A3[Chunk]
end
subgraph Shard B
B1[Chunk]
B2[Chunk]
B3[Chunk]
end
subgraph Shard C
C1[Chunk]
C2[Chunk]
C3[Chunk]
end
B3 -->|Migrate| C3Chunk 只会分裂,不会合并,即使 chunkSize 的值变大。
