MongoDB分片简介
1、MongoDB分片
ps:
- 由于最近遇到了一些问题,使我不得不对mongodb的分片机制做一个总结。后续将会写一篇mongodb 聚合查询优化的文章。
- 本文是对mongodb分片知识点的梳理以及一些个人经验之谈,其中分片理论知识大部分来源于官方资料:www.mongodb.com/docs/v4.2/c…
1.1、分片概念简介
分片(Sharding): 是指将数据拆分,分散在不同机器上的过程,使用分片可以将 读和写 操作分散到不同的机器上(前提是分片键选择合适),从而有效减少单台机器的CPU和存储的压力,提高QPS,使整个集群能够处理更大的负载。
自动水平扩展: MongoDB提供了一种称为Auto-Sharding
的机制,该机制可以自动实现系统的水平扩展。虽然分片的概念源于关系型数据库的分区,但还是有一些差别。最大的差别是MongoDB自动完成所有的工作,不需要人工介入,并且当各个分片中的数据分布不平衡时,自动保持数据平衡。
1.2、分片集群组成
下边我们看下mongoddb分片部署时,都由哪几部分组成:
1. shard: 称为片,每个shard 可以是一台服务器上的一个Mongod实例,但是为了提高系统的可靠性实现自动故障恢复,一个Shard应该是一个复制集(官方原话:从MongoDB 3.6版本开始,每个分片必须部署为副本集架构。)。
2. mongos(路由/代理): 充当路由
或者说代理人
的角色,为 应用程序和分片集群间的通信 提供一个通道,可以理解成:应用程序和mongodb库之间的代理人
,通过他,应用程序可以不关心分片集群的具体底层分片逻辑,也就是说在应用程序进行 读/写
数据时候,直接面向mongos操作。而具体是往哪个片读/写 则由分片键决定。
3. Config Servers(配置服务器): 存储了分片集群的元数据和配置信息。从MongoDB 3.4版本开始,config servers必须部署为副本集架构 (CSRS)
4. chunk(块): 这个是我自己加的,一个shard实例/副本集 在存储数据时,整个存储空间会被分割成一个一个的chunk数据块(根据分片键进行分割),下边我们紧接着会说到。
ok分片集群基本上就这些组件,下边我们看下chunk相关和分片的两种方式。
1.3、chunk相关
上边的分片集群组成中,我们也说了 某个shard实例/副本集 的存储结构是由一个一个chunk数据块 组成的。
- 什么是chunk(块)?
- 一个分片后的集合会包含多个 chunk,每个 chunk 位于哪个分片(Shard) 则记录在 Config Server(配置服务器)上(注意:
chunk的切分是根据分片键来的
), Mongos 在操作分片集合时,会自动根据分片键 直接 找到对应的 chunk,并向该 chunk 所在的shard实例发起操作请求。如上图,假设某集合 的分片键是
x
,(这里以范围分片为例(下边我们会讲到)
),写入该集合时会根据x
所在的范围,被分散到chunk1 ~ chunk4
的某一个chunk当中。每个chunk只包含特定范围
的数据(比如chunk2
就只包含x
的取值在[-75, 25) 范围内
的文档)。
- 一个分片后的集合会包含多个 chunk,每个 chunk 位于哪个分片(Shard) 则记录在 Config Server(配置服务器)上(注意:
- chunk分裂:
- 默认情况下,一个chunk的最大值默认为64MB(可调整),进行数据插入、更新、删除时,如果此时mongos感知到了目标chunk的大小超过上限,则会触发chunk分裂。
- chunk迁移:
- MongoDB默认情况下会开启一个balancer模块用于定期检测各个shard上的chunk数量分布,当检测到各个shard上的chunk数量存在分布不均匀的情况时,就会触发chunk迁移。如下图,
三个shard的chunk数量分别为3、3、1,此时balancer认为chunk数量分布不均,于是会将shard B上的chunk迁移一个到shard C上,这样三个shard的chunk数量分布最终就会变为3、2、2,分布更为均匀了。
- MongoDB默认情况下会开启一个balancer模块用于定期检测各个shard上的chunk数量分布,当检测到各个shard上的chunk数量存在分布不均匀的情况时,就会触发chunk迁移。如下图,
mongodb有两种分片策略,分片策略是根据分片键的选择来定的,下边我们介绍两种分片策略:
1.4、分片键介绍
mongodb使用片键在各分片之间进行分发,分片键由集合中的非数组结构的一个或多个字段组成,不能超过512个字节。对集合进行分片时需要先选择一个分片键,如果是一个非空集合,分片键字段必须具有索引,对于空集合,会自动对分片键加上索引。不能在分片后修改分片键(v4.2后可以更改分片键),分片键的选择会影响集群的性能和效率。如果查询不包含分片键,则mongos将执行广播操作,查询分片集群中的所有分片,所以分片键的选择非常重要。即分片键会确定数据在分片中的分布。所以,分片键可以说是一个双刃剑,用好了性能提升,用不好性能反而下降。
1.5、范围分片
对某集合进行范围分片:
sh.shardCollection("<database>.<collection>", { <shard key field> } )
对于基于范围的分片来说:
MongoDB按照片键的范围把数据分成不同部分。假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点,MongoDB把这条直线划分为更短的不重叠的片段,并称之为 数据块,每个数据块包含了片键在一定范围内的数据。 在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中,如下图示意:
由于X的值始终在增加,因此,包含maxKey(上限) 的数据块将接收大多数传入的写操作。 这将插入操作限制在某一个分片上,从而降低了写的性能
1.6、哈希分片
给某集合中的主键id添加hash索引:
db.collection.createIndex( { _id: "hashed" } )
对某集合进行hash分片:
sh.shardCollection( "database.collection", { <field> : "hashed" } )
基于哈希的分片: 在使用基于哈希分片的系统中,拥有”相近”片键的文档 很可能不会 存储在同一个数据块中,因此数据的分离性更好一些。值的注意的是: 在进行分片前,你选择的分片键,必须具有hash索引。 hash分片示意图如下:
可以看到该机制通过hash的散列特性,将读(精确查询时)/写操作分散到不同的分片上去。(但是要注意,分片键字段一定要有基数大的特点,否则hash冲突会陡增,分片的优点难以发挥)
1.7、哈希分片 vs 范围分片
给定一个使用单调递增(实际业务中,比如创建时间就属于单调递增的字段,或者集合主键 ObjectId 也是) 值 X 作为分片键的集合,使用范围分片会导致数据分布情况如下图所示:
如果使用hash分片,则数据的分布示意图如下:
通过以上描述我想范围分片和hash分片已经很清晰了,我们下边小结一下:
-
范围分片:
写入性能差,根据分片键范围查询性能好
,因为写都集中到某个分片上了,但是如果使用分片键字段进行范围查询的话,将通过分片键直接定位某几个chunk或者某一个chunk,也就是检索的数据少了,自然也就高效。 -
hash分片:
写入性能好,精确查询性能好,范围查询性能差
,因为每次写入时候,都通过hash方法将数据散列到某一个分片中。对于单个分片来说降低了写次数。但是这样的存储会导致在进行范围查询时,需要查询所有的分片,从而加载全部数据,降低了范围查询的性能,但是如果你使用精确查询(根据分片键),那么还是可以发挥hash的特性,降低筛选的数据量,提高性能。
2、分片时候的一些个人经验之谈:
- 如果使用hash分片,则在进行分片前,你选择的分片键,必须具有hash索引,如果没有需要创建。
- 不管是hash分片还是范围分片,分片键一定要有较大的基数,否则无法充分发挥分片的优势
- 从 MongoDB 4.2 开始,允许更新文档的分片键值,除非分片键字段是不可变的 _id 字段。在 MongoDB 4.2 之前,文档的分片键字段值是不可变的!
- 开启分片后,要注意观察mongodb的 upsert操作,该操作要求where 条件必须是分片键。
- 在进行分片时,集合中不能有除分片键之外的唯一索引
- 分片键一定要根据你的场景来慎重选择,最好在线下做好充分的测试
- 在开发时,最好是提前进行分片,避免上线后,对已存在数据的集合进行分片。
- 其他一些个人的经验之谈,见我的另外一篇文章:MongoDB踩坑记
- todo (以后遇见了再不断补充)~~~
其他一些使用mongodb时候的注意事项,欢迎补充,指正!
转载自:https://juejin.cn/post/7212937418960715833