一、分片集群的基本架构
为什么要使用分片集群?
副本集遇到的问题:
副本集(ReplicaSet) 帮助我们解决读请求扩展、高可用等问题。随着业务场景进一 步增长,可能会出现以下问题:
- 存储容量超出单机磁盘容量
- 活跃数据集超出单机内存容量:很多读请求需要从磁盘读取
- 写入量超出单机 IOPS 上限
垂直扩容(Scale Up) VS ⽔平扩容(Scale Out):
- 垂直扩容 : 用更好的服务器,提高 CPU 处理核数、内存数、带宽等
- 水平扩容 : 将任务分配到多台计算机上
什么是 MongoDB 分⽚集群:
- MongoDB 分片集群(Sharded Cluster)是对数据进行水平扩展的一种方
- MongoDB 使用 分片集群 来支持大数据集和高吞吐量的业务场景。
分⽚集群的基本架构
- Mongos
- 分片集群的访问入口
- 对请求进行路由、分发、合并
- 部署多个 Mongos 来保证高可用
- ConfigServer
- 存储元信息和集群配置
- 部署为副本集来保证高可用
- Shard
- 存储用户数据,不同 Shard 保存不同用户数据
- 部署为副本集来保证高可用
如何链接分片集群
有了一个分片集群以后,Drivers 需要通过连接 Mongos 来达到和整个集群交互的目 的,而 Mongos 则会根据客户端的请求来向后端不同的 Shard 进行请求的发起。 比如对 集合一进行读写,Mongos 会和 Shard A 和 Shard B 进行请求交互,如果读写集合二, 那么 Mongos 指挥只会和 Shard A 进行数据交互。
如下图所示:在阿里云 Mongos 上申请的一个分片集群,列举了每个 Mongos 的链接 地址,并且拼接好了 ConnectionStringURI,如果使用单个 Mongos 进行链接,可能会 有单点的风险,所以推荐使用 ConnectionStringURI 来进行访问。
ConnectionStringURI 各个组成部分:
mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:por tN]]][/[database][?options]]
- Mongodb://:前缀,代表这是一个 Connection String URI 连接地址。
- Username:password@:连接 MongoDB 实例的用户名和密码,使用英文冒号(:) 分隔。
- HostX:portX:实例的连接地址和端口号。
- /Database:鉴权数据库名,即数据库账号所属的数据库。
- ?Options:指定额外的连接选项。
举个例子:
“Example : mongodb://user:password@mongos1:3717,mongos2:3717/ad min”
用户名为 User,密码为 Password,然后来连接 Mongos1 和 Mongos2,它们的端 口都是 3717,全数据库是 admin,这样的一个 ConnectionStringURI。
Database 的主分⽚(Primary Shard)
Primary Shard 的定义:
默认情况下,每个 Database 的集合都是未分片的,存储在一个固定的 Shard 上, 称为 Primary Shard。
Primary Shard 的选择:
当创建一个新的 Database 时,系统会根据各个 Shard 目前存储的数据量,选择一 个数据量最小的 Shard 作为新 Database 的 Primary Shard。
如何将集合进行分片
MongoDB 将数据进行分片支持集合级别,已经被分片的集合被切分成多份保 存在 Shard 上。
sh.enableSharding("")
- // eg: "record" Example : sh.enableSharding("records") sh.shardCollection(".", { : , ... } )
- : 分片键字段的名字
- : {1 | -1 |"hashed"} 。1 | -1 : 基于范围分片键,"hashed" : 哈希
分片键
举个例子:
“Example : sh.shardCollection("records.people", { zipcode: 1 } )”对 recor ds.people 集合进行分片,这是一个基于 records 范围分片。
二、Shard Key(分片键)
范围分⽚ VS 哈希分⽚
- 范围分⽚:根据 ShardKey 的值进⾏数据分⽚。
优点:很好的满足范围查询的需求;
缺点:分片键单调写入,无法扩充写能力;
范围分⽚⽀持多个字段的范围分⽚:{x : 1} {x : 1 , y : 1}
如上图所示:是一个基于 x 的范围分配,数据被分为了 4 部分,切割点分别是 x:-75 ; x:25 ; x:175 值相近的数据是相邻的,这种情况下,可以很好的满足范围查询的需求。但 是如果是基于分片键的单调写入,由于数据都会由于所有的写入都会被最后一个 Chunk 来 承载,所以这样就无法很好的扩充写能力。
- 哈希分⽚:根据 ShardKey 计算 哈希值,基于哈希值进⾏数据分⽚。
优点:分片单调写入,充分的扩展写能力;
缺点:不能高效的进行范围查询。
如上图所示: x:25 x:26 x:27,经过哈希计算后数据被打散不同的 Chunk 上,基 于哈希分片可以单调,对于分片键单调写入的场景,可以充分的扩展写能力,但是却不能高 效的进行范围查询。
哈希分⽚仅⽀持单个字段的哈希分⽚:
{ x : "hashed" } {x : 1 , y : "hashed"} // 4.4 new
4.4 以后的版本,可以将单个字段的哈希分片和一个到多个的范围分片键字段来进行组 合,比如下指定 x:1,y 是哈希的方式。
如何选择合理的分⽚键
- Cardinality(基数):越⼤越好
- 以性别作为分片键 :数据最多被拆分为 2 份
- 以⽉份作为分片键 :数据最多被拆分为 12 份
- Frequency(频率,⽂档中出现某个值的频率):越低越好 记录全国人口的集合,以当前所在城市作为分片键:大多数数据集中在一线城市所在的 Chunk。
- Monotonically Changing(单调变化):使⽤哈希分⽚
记录日志集合,使用⽇志⽣成时间作为分片键:
如果使用范围分片,数据写入只会在最后一个 Shard 上完成。
分片键(ShardKey)的约束
ShardKey 必须是一个索引。非空集合须在 ShardCollection 前创建索引;空集合 ShardCollection 自动创建索引
4.4 版本之前:
- ShardKey 大小不能超过 512 Bytes;
- 仅支持单字段的哈希分片键;
- Document 中必须包含 ShardKey;
- ShardKey 包含的 Field 不可以修改。
4.4 版本之后:
- ShardKey 大小无限制;
- 支持复合哈希分片键;
- Document 中可以不包含 ShardKey,插入时被当做 Null 处理;
- 为 ShardKey 添加后缀 refineCollectionShardKey 命令,可以修改 ShardKey 包含 的 Field;
而在 4.2 版本之前,ShardKey 对应的值不可以修改;4.2 版本之后,如果 ShardKey 为非_ID 字段,那么可以修改 ShardKey 对应的值。
FefineCollectionShardKey
4.4 版本新增命令,通过分片键增加后缀字段的方式来修改分片键:
db.adminCommand( { refineCollectionShardKey: "<database>.<collection>", key: { <existing key specification>, <suffix1>: <1|"hashed">, ... } } )
这个例子中:
- : 当前的分片键,即新的分片键必须以当前分片键为 前缀;
- : 新增的分片键字段;
- <1|"hashed"> : <1> -- 范围分片键 ;<"hashed"> -- 哈希分片键。
FefineCollectionShardKey 的使用说明:
- 新的 ShardKey 对应的索引在FefineCollectionShardKey执⾏前须已经创建完成;
- FefineCollectionShardKey 只会修改 Config 节点上的元数据,不会有任何数据迁 移,数据的打散随后续正常分裂&迁移⽽完成;
- 4.4 版本中⽀持了 ShardKey 缺失的情况(当做 Null 处理),为了应对并不是所有 ⽂档都存在新的 ShardKey 的所有字段;
- 4.4 版本中⽀持复合哈希分⽚键,⽽在之前的版本中只能⽀持单字段的哈希分⽚键。
特定⽬标的操作(Targeted Operations)vs⼴播的操作(Broadcast Operations)
Mongos 是如何基于请求当中的分片键信息来做请求转发,有两种转发行为,一种叫 做特定目标的操作,一种叫做广播操作。
- 特定目标的操作(Targeted Operations):根据分⽚键计算出⽬标 Shard(s),发 起请求并返回结果。
- 包含分片键的查询操作、更新、删除操作、插入操作
如上图所示:以 a 为 Shard Key 如果请求当中带了 a 字段,那么 Mongos 就可以识 别出来它的目标 Shard,如果是 Shard B,就可以直接跟 Shard B 进行交互,获取结果 并返回给客户端。
- ⼴播的操作(Broadcast Operations):将请求发送给所有 Shard,合并查询结果 并返回给客户端。
- 不包含分片键的查询操作、_ID 字段的更新、删除操作
如图所示:
三、Chunk & Balancer
什么是 Chunk?
- MongoDB 基于 ShardKey 将 Collection 拆分成多个 数据子集,每个子集称为一 个 Chunk;
- shardedCollection 的数据按照 ShardKey 划分为 MinKey ~ MaxKey 的间;
- 每个 Chunk 有自己负责的一个区间(前闭后开);
- 存储 ShardedCollection 的 Shard 上有该 Collection 的一个或多个 Chunk ;
如上图所示:分片的集合是基于 x 的范围分片,数据被分成了 4 个 Chunk, Chunk 1 : [minKey, -75) ; Chunk2 : [-75, 25) ; Chunk3 : [25, 175) ; Chunk4 : [175, maxKey)是个前闭后开的区间。ShardA 是持有 Chunk1 和 Chunk2,而 ShardB 和 ShardC 则分别持有 Chunk3 和 Chunk4。
Chunk 分裂(Chunk Splits)
- Chunk 分裂的定义
伴随着数据的写入,当 Chunk 增长到指定大小(默认为 64MB)时,MongoDB 会 对 Chunk 进行分裂,称为 Chunk Split。 - Chunk 分裂的⽅式
⼿动触发:
- sh.splitAt(namespace, query)
- sh.splitFind(namespace, query)
⾃动触发:只有 插⼊和更新 操作才会触发⾃动 Chunk Split。当 Chunk Size 被 调⼩时,不会⽴即发⽣Chunk Split。
- JumboChunk 一个最小的 Chunk 可以只包含一个唯一的 ShardKey,这样的 Chunk 不可以再进 行分裂,称为 JumboChunk。
如下图所示:
Chunk 分裂管理
Chunk 分裂管理包括:⼿动进⾏Chunk 分裂与调整 ChunkSize。
- ⼿动进⾏Chunk 分裂
场景举例:业务需要向集合中插⼊⼤量的数据,⽽这些数据只分布在较少的 Chunk 中。
直接插⼊⽆法利⽤多 Shard 并发写⼊,并且插⼊后触发 Chunk 分裂,进⽽触发 Chunk 迁移,产⽣很多⽆效 IO。
sh.splitAt(namespace, query) : 指定 Chunk 分裂点,
例如:x: [0, 100) , sh.splitAt(ns, {x: 70}) 分裂后 x: [0, 70) , [70, 100)
sh.splitFind(namespace, query) : 从中间分裂⽬标 Chunk,
例如:x: [0, 100) , sh.splitFind(ns, {x: 70})分裂后 x: [0, 50) , [50, 100) - 调整 ChunkSize
例如: use config; db.settings.save( { _id:"chunksize", value: } );
这里调整 ChunkSize 的方式,是对 config 库的 settings 集合,增加一条文档,这个 文档的 ID 是 ChunkSize。
说明:
- 只有在 插⼊和更新 操作才会触发对应Chunk分裂 -- 调ChunkSize会⽴即触 发所有 Chunk 分裂为新的⼤⼩;
- ChunkSize 取值范围 : 1 ~ 1024 MB;
- 调⼩ ChunkSize 可以让 Chunk 更均衡的分布,但是 Chunk 迁移次数会增加;
- 调⼤ ChunkSize 会减少 Chunk 迁移,但会导致 Chunk 分布不均。
Chunk 迁移(Chunk Migration)
- Chunk 迁移的定义:
为了保证数据负载均衡,MongoDB 支持 Chunk 在 Shard 间迁移,称为 Chunk Migration。 - Chunk 迁移的⽅式:
- 自动触发:当 Chunk 在 Shard 之间分布不均时,Balancer 进程会自动触发 Chunk 迁移;
- 手动触发:sh.moveChunk(namespace, query, destination)
- Example : sh.moveChunk("records.people", { zipcode: "53187" }, "sha rd0019")。
- Chunk 迁移的影响:
- 影响 Shard 使用磁盘的大小;
- 增加 网络带宽 及 系统负载,这些会对系统性能造成影响。
- Chunk 迁移的约束:
- 每个 Shard 同一时间只能有一个 Chunk 在进行迁移;
- 不会迁移 Chunk中文档数量是平均Chunk文档数1.3倍的Chunk // 4.4 提供 选项支持。
Balancer
- Balancer 是 MongoDB 的一个后台进程,用保证集合的 Chunk 在各个 Shard 上是 均衡的。
- Balancer 运行在 ConfigServer 的 Primary 节点。 默认为 开启状态。
- 当分片集群中发生 Chunk 不均衡的情况时,Balancer 将触发 Chunk 从 Chunk 数 量最多的 Shard 向 Chunk 数量最少的 Shard 上迁移。
如图所示:Chunk 的数量小于 20,迁移阈值是 2,随着 Chunk 数量增大,迁移阈值 分别增长为 4 和 8。
AutoSplit & Balancer 管理命令
- 开启 Chunk⾃动分裂: sh.enableAutoSplit()。
- 关闭 Chunk⾃动分裂: sh.disableAutoSplit()。
- 查看 Balancer 是否开启: sh.getBalancerState()。
- 查看 Balancer 是否正在运⾏: sh.isBalancerRunning()。
- 开启 Balancer: sh.startBalancer() / sh.setBalancerState(true)
- 4.2 版本开始,会同时开启 AutoSplit。
- 关闭 Balancer: sh.stopBalancer() / / sh.setBalancerState(false)
- 4.2 版本开始,会同时关闭 AutoSplit。
- 开启某个集合⾃动迁移: sh.enableBalancing(namespace)。
- 关闭某个集合⾃动迁移: sh.disableBalancing(namespace)。
- 修改 Balancer Window:
use config; db.settings.update( { _id: "balancer" }, { $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } } }, { upsert: true } ); 。
JumboChunk
JumboChunk 的定义:一个最小的 Chunk 可以只包含一个唯一的 ShardKey,这 样的 Chunk 不可以再进行分裂。
JumboChunk 的产生:ShardKey 选择不合理才会产生 JumboChunk。如果这些 JumboChunk 是高频访问的,就会引起单 Shard 性能瓶颈。另外 Chunk 无法迁移,如 果再进行迁移,会引起 Shard 间数据不均。
随着 MongoDB 版本迭代,这些问题也在逐步的被解决,比如 4.4 版本当中为我们提 供了 RefineCollectionShardKey 的命令,重新设置 ShardKey 同时 4.4 当中也给 Balancer 提供了一些设置,给 MoveChunk 提供了一些 Option,来支持 Chunk 迁移。
在 4.2 和 4.0 的较新的小版本当中,也提供了命令来清理集群中的 JumboChunk 标
识。
四、集群管理
命令回顾
- Balancer
- sh.setBalancerState(state)
- true : sh.startBalancer()
- false : sh.stopBalancer()
- sh.getBalancerState()
- sh.isBalancerRunning() sh.disableBalancing(namespace) sh.enableBalancing(namespace)
- Chunk
- sh.disableAutoSplit()
- sh.enableAutoSplit()
- sh.moveChunk( … )
- sh.splitAt( … )
- sh.splitFind( … )
- Sharding
- sh.shardCollection()
- sh.enableSharding()
集群状态查看 - sh.status()
如图所示:
Sharding Version:分片集群的版本信息。
Shards:分片集群中目前有 2 个 Shard,每个 Shard 的名称、链接信息以及当前状
态。
Most Recently Active Mongoses:目前分片集群中有2个4.2.1版本的Mongos。
Autosplit&balancer
- 当前开启 Auto-Split
- Balancer 状态为开启
- Balancer 目前没有正在运行
- 过去一段时间 Balancer 执行的成功、失败信息。
Records 库
- Primary Shard : xxxx746b04
- 开启 Sharding(EnableSharding)
- 相关版本信息。
Records.people 集合
- Shard Key 为 { "Zipcode" : "Hashed" },无须 Unique 约束
- Balancer 可以针对该 集合 进行 Balance
- 集合有 4 个 Chunk,平均分布在 2 个 Shard 上
- 各 Chunk 所负责的范围以及所属的 Shard。
LogicalSession
3.6 版本开始,MongoDB driver 将所有的操作与 LogicalSession 关联
3.4 版本及以前,如图所示:
3.4 版本及以后,如图所示:
- LogicalSession ID
{ // 唯一标识。可以由客户端生成,也可以由服务端生成 分片集群使用及原理介绍 < 62 "id" : UUID("32415755-a156-4d1c-9b14-3c372a15abaf"), // 目前登录用户标识 "uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSu FU=") }
- ⾃动清理机制
- 持久存储: Config.System.Sessions。TTL 索引:默认 30 分钟
- 默认每 5 分钟一次同步,关闭已被清理的 Session,同时关闭 Session 上的 Cursor
- 使⽤⽅式
- use config; db.system.sessions.aggregate( [ { $listSessions: { allUse rs: true } } ] )
- db.runCommand( { killSessions: [ { id : }, ... ] } )
- startSession / refreshSessions / endSessions ...
快速掌握MongoDB核心技术干货目录
电子书下载:《玩转MongoDB从入门到实战》 | https://developer.aliyun.com/article/780915 |
走进 MongoDB | https://developer.aliyun.com/article/781079 |
MongoDB聚合框架 | https://developer.aliyun.com/article/781095 |
复制集使用及原理介绍 | https://developer.aliyun.com/article/781137 |
分片集群使用及原理介绍 | https://developer.aliyun.com/article/781104 |
ChangeStreams 使用及原理 | https://developer.aliyun.com/article/781107 |
事务功能使用及原理介绍 | https://developer.aliyun.com/article/781111 |
MongoDB最佳实践一 | https://developer.aliyun.com/article/781139 |
MongoDB最佳实践二 | https://developer.aliyun.com/article/781141 |