MongoDB 的分片(Sharding)是其应对海量数据存储和高并发读写请求的核心水平扩展方案。其基本原理是将大数据集分散存储到多个独立的服务器(或服务器集群,即副本集)上,每个服务器只存储数据的一部分。
分片的核心组件
MongoDB 的分片集群由以下三个主要组件构成:
-
Shard (分片):
- 定义: 每个分片是一个独立的 MongoDB 副本集(Replica Set)。它存储了数据集的一个子集(即数据块)。
- 作用: 存储实际的数据,并处理其所负责数据块的读写请求。副本集的存在是为了提供高可用性和数据冗余。
- 数量: 一个分片集群至少需要一个分片,但为了实现分片扩容和负载均衡,通常会有多个分片。
-
Mongos (查询路由器):
- 定义: Mongos 是一个路由服务,应用程序(客户端)连接到 Mongos,而不是直接连接到任何单个分片。
- 作用:
- 路由请求: 接收来自客户端的查询和写入请求。
- 解析请求: 根据分片键(Shard Key)和目录信息(Config Servers 提供)将请求路由到正确的(一个或多个)分片上。
- 结果合并: 如果一个查询需要从多个分片获取数据,Mongos 会并行地向这些分片发送请求,然后收集结果并合并、排序,最后返回给客户端。
- 抽象: 对于客户端来说,Mongos 使整个分片集群看起来就像一个单一的 MongoDB 实例。
- 数量: 通常部署多个 Mongos 实例以提供高可用性和负载均衡。它们本身是无状态的,可以很容易地扩展。
-
Config Servers (配置服务器):
- 定义: 配置服务器存储整个分片集群的元数据(metadata),包括分片信息、数据块(chunk)范围、分片键定义以及数据在分片间的分布情况等。
- 作用:
- 存储元数据: 维护集群的所有配置信息。
- 路由信息: Mongos 启动时会连接 Config Servers 获取路由信息,并在数据分布发生变化时(如数据块迁移)更新其缓存。
- 架构: 从 MongoDB 3.4 版本开始,Config Servers 本身也必须是一个副本集(至少 3 个节点),以确保高可用性和数据一致性。在此之前,它可以使用三台独立的 mongod 实例。
- 重要性: 配置服务器的健康和可用性对于整个分片集群的正常运行至关重要。
分片的工作原理概览
-
分片键 (Shard Key) 的选择:
- 分片的基础是为集合选择一个或多个字段作为“分片键”。MongoDB 使用这个分片键的值来决定一条文档应该存储在哪个分片上。
- 分片键的选择至关重要,它直接影响查询效率、数据分布均匀性和集群的扩展能力。好的分片键应该具备:
- 基数高: 有足够多的不重复值。
- 变动性低: 不经常更新,因为更新分片键会导致文档在分片之间移动。
- 均匀分布: 能够使数据均匀分布在各个分片上,避免“热点”分片。
-
数据块 (Chunk) 和范围:
- 当一个集合被分片后,MongoDB 会根据分片键的范围将其划分为多个“数据块”(chunk)。每个数据块表示分片键值范围的一部分,并且默认大小为 64MB(可配置)。
- Config Servers 存储了这些数据块以及它们当前所在的分片信息。
-
数据路由:
- 客户端应用程序连接到 Mongos。
- 当客户端发出一个读或写请求时,Mongos 会从请求中提取分片键的值。
- Mongos 查询 Config Servers 获取的元数据,找到包含该分片键值的数据块所属的分片。
- Mongos 将请求直接路由到对应的分片。
- 如果查询没有包含分片键,或者包含的分片键无法确定单一分片(例如范围查询),Mongos 可能会将查询广播到所有相关的分片,然后合并结果。这种称为“scatter-gather”的查询效率较低。
-
数据平衡 (Balancer):
- MongoDB 会自动监控各分片之间的数据量分布。
- 如果某个分片的数据块数量过多,或者某个分片的数据量明显高于其他分片(出现“不平衡”),后台的平衡器(Balancer)进程(通常运行在 Config Server Replica Set 的 Primary 节点上)会自动启动。
- 平衡器会将一些数据块从负载较重的分片迁移到负载较轻的分片,以保持数据分布的均匀性。这个过程对应用程序是透明的。数据块迁移是基于分片键的范围进行的。
分片键的类型
MongoDB 支持两种主要的分片键类型:
-
范围分片 (Range Sharding):
- 根据分片键值的范围将数据划分为数据块。
- 例如,如果使用用户 ID (UUID) 作为分片键,数据块可能是
UUID_min到UUID_max之间。 - 适用于范围查询效率高,但可能导致数据分布不均(如果分片键值集中在某个范围),从而产生热点。
- 需要仔细选择分片键以确保均匀分布。
-
哈希分片 (Hashed Sharding):
- 对分片键的值进行哈希计算,然后根据哈希值的大小范围将数据划分为数据块。
- 哈希分片旨在实现数据在各分片上的随机和均匀分布,有效避免热点。
- 适用于大量的随机插入和更新,以及点查询(基于分片键的精确查询)。
- 不适用于范围查询,因为哈希值会打乱原始顺序,范围查询可能需要扫描所有分片(即 scatter-gather)。
为什么需要分片?
- 水平扩展存储: 当单个服务器的磁盘空间不足时,可以通过添加更多的分片来无限扩展存储容量。
- 水平扩展读写吞吐量: 将请求分散到多个服务器上,可以处理更高的并发读写请求量。
- 避免热点: 通过合理的分片键和数据平衡,可以将工作负载均匀分布在各个分片上,避免某个分片成为性能瓶颈。
- 提高可用性: 虽然分片集群本身的设置比副本集更复杂,但每个分片都是一个副本集,并且整个集群通过多个 Mongos 路由和 Config Servers 提供高度可用性。
分片的限制和注意事项
- 复杂性: 分片集群的部署和管理比单一副本集要复杂得多。
- 分片键选择: 选择一个好的分片键是至关重要的,一旦确定,更改起来会很困难。糟糕的分片键可能导致性能问题或不均匀的数据分布。
- 分片键无法更新: 一旦定义了分片键,就不能在不重建集合的情况下更改它。
- 性能开销: Mongos 路由和配置服务器的额外层级会带来轻微的性能开销。跨分片的查询(scatter-gather)效率会降低。
- 唯一性索引: 对于分片集合,除了
_id索引,任何自定义的唯一性索引都必须包含分片键作为其前缀(或分片键本身)。
分片是 MongoDB 在处理大数据量和高并发场景下的强大武器,但需要在设计初期仔细规划,特别是分片键的选择。