MongoDB–片键的选择策略和原则
文章目录
一:片键的选择原则
片键的选择主要有两个原则,一个是片键基数(片键的范围),一个是片键的分布情况。
1. 片键的基数(可能取值范围)
chunk块中是连续的的片键范围的数据,当文档中片键字段属于这个范围的时候,文档就属于这个chunk。片键的基数就是片键可能的取值情况。
1.例如片键选择的字段是sex性别,只有男和女,那么无论怎么分只会有两个chunk,在医院做护士信息统计,可能绝大部分的都是女,这样会导致片键是女的chunk块压力过大。
2.但是如果片键选择的字段是name,一般重名的可能性不大,那么有多少个name就会存在多少个chunk(但是这样会造成config中的元数据过多);
3.如果片键选择的是age,一般来说片键的范围也就是age的范围就是[0,100],最多只能拆分出101个chunk,可能某些age的人数比较多,某些age的人十分少甚至没有,这样会造成每个chunk的读写压力是不均匀的。
由于对于balancer来说,迁移数据的单位是chunk,所以balancer会认为系统中每个shard上的chunk是均匀的,负载是均匀的;而由于相同的片键值的文档一定会处于同一个块中,所以一个chunk中相同片键值的文档过多的时候也是无法拆分的。
2. 片键在可能取值范围内的分布情况
由于相同的片键值的文档一定会处于同一个块中,所以一个chunk中相同片键值的文档过多的时候也是无法拆分的
对于学校而言,人数最多的是7~20岁的学生,可见7,8,9……19,20这几个chunk会非常繁忙,数据量会非常大,导致拥有这些chunk的shard也会非常繁忙并且面临存储压力。如果不巧这些chunk中的大部分正好都分布在同一个片上,那么这个片可能就忙不过来了
这就体现出了片键取值分布情况的重要性:片键取值分布直接影响到每个片的压力情况,应该尽可能选择取值分布均匀的字段做片键。
3. 结论
- 片键取值分布直接影响到每个片的压力情况,应该尽可能选择取值分布均匀的字段做片键
- 片键选取的字段,可能取值要尽可能多,也就是基数一定要大
二:片键的策略
1. 散列片键
散列分片可以使其他任何键随机分发,因此如果在大量查询中使用升序键又希望写入数据随机分发的话,就可以使用散列分片。
创建散列分片,首先要创建散列索引
>db.user.ensureIndex({"username":"hashed"})
然后对集合分片
>db.shardCollection("app.user",{"username":"hashed"})
注意:片键值相同的文档还是会分发到相同的块中,但是可以随机分发,避免了maxKey和minKey的块中数据过多一直分裂,块一直迁移。
2. GridFS的散列片键
files集合:
大多数情况下不需要对
files
集合进行分片,这个集合通常很小,只包含了一些元信息.集合中也没有合适的片键可以将数据均衡地分布在集群中.如果你 必须 对files
进行分片,可以使用_id
字段与应用相关的字段做复合片键.不将
files
分片意味着所有文件的元信息都存储在一个分片上,在生产环境中, 必须 在存储了files
的分片上使用复制集.
chunks集合:
db.fs.chunks.createIndex( { files_id : "hashed" , n : 1 } )
db.runCommand( { shardCollection : "test.fs.chunks" , key : { files_id : "hashed" , n : 1 } } )
或者
db.fs.chunks.createIndex( { files_id : "hashed"})
db.runCommand( { shardCollection : "test.fs.chunks" , key : { files_id : 1 } } )
重点:
在GridFS存储中,对
chunks
集合进行分片时, 只有 两个片键可以选择,{ files_id : 1 , n : 1 }
与{ files_id : 1 }
.默认的
files_id
是 ObjectId,ObjectId是递增的,因此所有新写入的数据都会存储到一个单独的分片中,如果这个分片的写负载太大,考虑换一个片键或者在files
集合中使用不同的_id
值.
3. 流水策略
强制将所有的新数据插入到SSD,然后让均衡器将旧的块移动到其他分片上,从而让更好的服务器处理负载
//1.在性能比较好的服务器上(例如SSD)的分片上,指定一个标签
sh.addShardTag("shard1","ssd")
//2.指定升序键的当前值一直到真无穷范围的块指定分布在性能好的服务器上,所有的插入请求都会被路由到这个块上
sh.addTagRange("dbName.collName",{"_id":ObjectId()},{"_id":MaxKey},"ssd")
//3.可以创建一个定时任务每天更新一次标签范围
>use config
>var tag=db.tags.finds.findOne({"ns":"dbName.collName","max":{"shardKey":MaxKey}})
>tag.min.shardKey=Object()
>db.tags.save(tag)
4. 多热点
如何将写请求分布到集群中的时候,分片才是最高效的。多热点考虑的就是如何将写请求分布到集群中。
三:手动迁移chunk
如果不希望数据被自动分发,可以关闭均衡器,使用moveChunk命令手动进行数据的分发。
//关闭均衡器,如果不存在会创建一个
>db.settings.update({"_id":"balancer"},{"enabled":false},true)
//可以在config.chunks找出每个块的分发位置
>db.chunks.find()
//迁移,指定下边界值和目标分片
>sh.moveChunk("test.manual.stuff",{user_id:NumberLong("-156468851555")},"shard1")
或者:
db.runCommand( { moveChunk : "myapp.users" ,
find : {username : "smith"} ,
to : "mongodb-shard3.example.net" } )
注释:
moveChunk:一个集合的名字要加上数据库的名称:比如test.yql
find:一个查询语句,指定集合中的符合查询的数据或者chunk,系统自动查出from 的shard
to: 指向chunk的目的shard
只要目的shard和源sharad同意指定的chunk由目的shard接管,命令就返回。迁移chunk是一个比较复杂的过程,它包括两个内部通信协议:
1 复制数据,包括在复制过程中的变化的数据
2 确保所有参与迁移的组成部分:目的shard ,源shard ,config server都确定迁移已经完成!
注意:
片键不可以是数组,在拥有数组的键上执行sh.shardCollection(),则命令不会生效,向片键插入数组值也是不被允许的;
不能在地理空间索引上进行分片