MongoDB 分片
MongoDB分片概念
分片是一种用于在 多台计算机之间分配数据的方法。 MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。
当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量。这时,我们就可以通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据。
解决系统增长的方法有两种: 垂直缩放和水平缩放。MongoDB 通过分片支持水平扩展。
为何使用分片?
分片目的
高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。为了解决这些问题,有两个基本的方法: 垂直扩展
和水平扩展
。
垂直扩展:增加更多的CPU和存储资源来扩展容量。
水平扩展:将数据集分布在多个服务器上。水平扩展即分片。
分片设计思想
分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此,通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,应用只需要访问存储这条数据的分片。
使用分片减少了每个分片存储的数据。
例如,如果数据库1tb的数据集,并有4个分片,然后每个分片可能仅持有256 GB的数据。如果有40个分片,那么每个切分可能只有25GB的数据。
分片机制提供了如下三种优势
对集群进行抽象,让集群“不可见”
MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。
保证集群总是可读写
MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。
使集群易于扩展
当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。
分片集群架构
Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会议分散存储到各个shard。Mongos启动后,会从配置服务器加载元数据,开始提供服务,将用户的请求正确路由到对应的碎片。
分片特点
复制所有的写入操作到主节点
延迟的敏感数据会在主节点查询
单个副本集限制在12个节点
当请求量巨大时出现内存不足,想将大量数据放在内存中提高性能
本地磁盘不足
垂直扩展价格昂贵
服务器出现写瓶颈的时候
分片就是把mongo单个表的数据分到多个chunk上,从而提升mongo的读写能力。
集群中数据分布
Chunk是什么
在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。chunk的产生,会有以下两个用途:
Splitting:当一个chunk的大小超过配置中的chunk size时,MongoDB的后台进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况
Balancing:在MongoDB中,balancer是一个后台进程,负责chunk的迁移,从而均衡各个shard server的负载,系统初始1个chunk,chunk size默认值64M,生产库上选择适合业务的chunk size是最好的。ongoDB会自动拆分和迁移chunks。
分片集群的数据分布(shard节点)
(1)使用chunk来存储数据
(2)进群搭建完成之后,默认开启一个chunk,大小是64M,
(3)存储需求超过64M,chunk会进行分裂,如果单位时间存储需求很大,设置更大的chunk
(4)chunk会被自动均衡迁移。
chunksize的选择
适合业务的chunksize是最好的。
chunk的分裂和迁移非常消耗IO资源;chunk分裂的时机:在插入和更新,读数据不会分裂。
chunksize的选择
小的chunksize:数据均衡是迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。
大的chunksize:数据分裂少。数据块移动集中消耗IO资源。通常100-200M
chunk分裂及迁移
随着数据的增长,其中的数据大小超过了配置的chunk size,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。
这时候,各个shard 上的chunk数量就会不平衡。这时候,mongos中的一个组件balancer 就会执行自动平衡。把chunk从chunk数量最多的shard节点挪动到数量最少的节点。
chunkSize对分裂及迁移的影响
MongoDB 默认的 chunkSize 为64MB,如无特殊需求,建议保持默认值;chunkSize 会直接影响到 chunk 分裂、迁移的行为。
chunkSize 越小,chunk 分裂及迁移越多,数据分布越均衡;反之,chunkSize 越大,chunk 分裂及迁移会更少,但可能导致数据分布不均。
chunkSize 太小,容易出现 jumbo chunk(即shardKey 的某个取值出现频率很高,这些文档只能放到一个 chunk 里,无法再分裂)而无法迁移;chunkSize 越大,则可能出现 chunk 内文档数太多(chunk 内文档数不能超过 250000 )而无法迁移。
chunk 自动分裂只会在数据写入时触发,所以如果将 chunkSize 改小,系统需要一定的时间来将 chunk 分裂到指定的大小。
chunk 只会分裂,不会合并,所以即使将 chunkSize 改大,现有的 chunk 数量不会减少,但 chunk 大小会随着写入不断增长,直到达到目标大小。
为何分到多个chunk上可以提升读写能力?
多个chunk分布在多个机器上,可以充分利用多台机器的CPU和磁盘IO;
分到多个chunk上的时候由于分片键的存在,如果按照分片键进行查询,则直接定位到某个chunk,只需要在这一个chunk上进行查询,这样明显会很快。
数据区分
分片键shard key
MongoDB中数据的分片是、以集合为基本单位的,集合中的数据通过片键(Shard key)被分成多部分。其实片键就是在集合中选一个键,用该键的值作为数据拆分的依据。
所以一个好的片键对分片至关重要。片键必须是一个索引,通过sh.shardCollection加会自动创建索引(前提是此集合不存在的情况下)。一个自增的片键对写入和数据均匀分布就不是很好,因为自增的片键总会在一个分片上写入,后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。
随机片键对数据的均匀分布效果很好。注意尽量避免在多个分片上进行查询。在所有分片上查询,mongos会对结果进行归并排序。
对集合进行分片时,你需要选择一个片键,片键是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中。
为了按照片键划分数据块,MongoDB使用基于范围的分片方式或者 基于哈希的分片方式。
::: tip 分片键的限制
分片键是不可变;
分片键必须有索引;
分片键大小限制在512bytes;
分片键用于路由查询;
MongoDB不接受已进行collection级分片的collection插入无分片键的文档(也不支持空值插入) :::
以范围为基础的分片Sharded Cluster
Sharded Cluster支持将单个集合的数据分散存储在多shard上,用户可以指定根据集合内文档的某个字段即shard key来进行范围分片(range sharding)。
对于基于范围的分片,MongoDB按照片键的范围把数据分成不同部分。
假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点。MongoDB把这条直线划分为更短的不重叠的片段,并称之为数据块,每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中。
基于哈希的分片
分片过程中利用哈希索引作为分片的单个键,且哈希分片的片键只能使用一个字段,而基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。
对于基于哈希的分片,MongoDB计算一个字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有”相近”片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。
Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。
分片键选择建议
1、递增的sharding key
数据文件挪动小。(优势)
因为数据文件递增,所以会把insert的写IO永久放在最后一片上,造成最后一片的写热点。同时,随着最后一片的数据量增大,将不断的发生迁移至之前的片上。
2、随机的sharding key
数据分布均匀,insert的写IO均匀分布在多个片上。(优势)
大量的随机IO,磁盘不堪重荷。
3、混合型key
大方向随机递增,小范围随机分布。
为了防止出现大量的chunk均衡迁移,可能造成的IO压力。我们需要设置合理分片使用策略(片键的选择、分片算法(range、hash))
分片后对查询、写入影响
所有的请求都有mongos来路由、分发、合并,这些动作对客户端driver透明。Mongos会根据请求类型及shard key将请求路由到对应的shard,因此不同的操作请求存在不同的限制。
查询请求
查询请求中不包含shard key,则必须将查询分发到所有的shard,然后合并查询结果返回给客户端;
查询请求包含shard key,则直接根据shard key计算出需要查询的chunk,向对应的shard发送查询请求。
插入请求
写操作必须包含shard key,mongos根据shard key算出文档应该存储到哪个chunk,然后将写请求发送到chunk所在的shard;
更新/删除请求
更新、删除请求的查询条件必须包含shard key或者_id,若果是包含shard key,则直接路由到指定的chunk,如果只包含_id,则需将请求发送到所有的shard
部署分片集群
# 10个实例:27017-27026
# configserver:
3台构成的复制集(1主两从,不支持arbiter)27018-27020(复制集名字configsvr)
# shard节点:
sh1:27021-23 (1主两从,其中一个节点为arbiter,复制集名字sh1)
sh2:27024-26 (1主两从,其中一个节点为arbiter,复制集名字sh2)
shard复制集配置
# 1.目录创建:
mkdir -p /mongodb/27021/{conf,log,data}
mkdir -p /mongodb/27022/{conf,log,data}
mkdir -p /mongodb/27023/{conf,log,data}
mkdir -p /mongodb/27024/{conf,log,data}
mkdir -p /mongodb/27025/{conf,log,data}
mkdir -p /mongodb/27026/{conf,log,data}
# 2.修改配置文件:
# sh1:
cat >> /mongodb/27021/conf/mongodb.conf <<EOF
systemLog:
destination: file
path: /mongodb/27021/log/mongodb.log
logAppend: true
storage:
journal:
enabled: true
dbPath: /mongodb/27021/data
directoryPerDB: true
#engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 1
directoryForIndexes: true
collectionConfig:
blockCompressor: zlib
indexConfig:
prefixCompression: true
net:
port: 27021
replication:
oplogSizeMB: 2048
replSetName: sh1
sharding:
clusterRole: shardsvr
processManagement:
fork: true
EOF
cp /mongodb/27021/conf/mongodb.conf /mongodb/27022/conf/
cp /mongodb/27021/conf/mongodb.conf /mongodb/27023/conf/
sed 's#27021#27022#g' /mongodb/27022/conf/mongodb.conf -i
sed 's#27021#27023#g' /mongodb/27023/conf/mongodb.conf -i
# sh2:
cat >> /mongodb/27024/conf/mongodb.conf << EOF
systemLog:
destination: file
path: /mongodb/27024/log/mongodb.log
logAppend: true
storage:
journal:
enabled: true
dbPath: /mongodb/27024/data
directoryPerDB: true
wiredTiger:
engineConfig:
cacheSizeGB: 1
directoryForIndexes: true
collectionConfig:
blockCompressor: zlib
indexConfig:
prefixCompression: true
net:
port: 27024
replication:
oplogSizeMB: 2048
replSetName: sh2
sharding:
clusterRole: shardsvr
processManagement:
fork: true
EOF
cp /mongodb/27024/conf/mongodb.conf /mongodb/27025/conf/
cp /mongodb/27024/conf/mongodb.conf /mongodb/27026/conf/
sed 's#27024#27025#g' /mongodb/27025/conf/mongodb.conf -i
sed 's#27024#27026#g' /mongodb/27026/conf/mongodb.conf -i
# 3启动所有节点,并搭建复制集:
mongod -f /mongodb/27021/conf/mongodb.conf
mongod -f /mongodb/27022/conf/mongodb.conf
mongod -f /mongodb/27023/conf/mongodb.conf
mongod -f /mongodb/27024/conf/mongodb.conf
mongod -f /mongodb/27025/conf/mongodb.conf
mongod -f /mongodb/27026/conf/mongodb.conf
# 搭建sh1复制集
mongo --port 27021
use admin
config = {_id: 'sh1', members: [
{_id: 0, host: '10.0.0.200:27021'},
{_id: 1, host: '10.0.0.200:27022'},
{_id: 2, host: '10.0.0.200:27023',"arbiterOnly":true}]
}
rs.initiate(config)
# 搭建sh2复制集
mongo --port 27024
use admin
config = {_id: 'sh2', members: [
{_id: 0, host: '10.0.0.200:27024'},
{_id: 1, host: '10.0.0.200:27025'},
{_id: 2, host: '10.0.0.200:27026',"arbiterOnly":true}]
}
rs.initiate(config)
config节点配置
# 1目录创建:
mkdir -p /mongodb/27018/conf /mongodb/27018/log /mongodb/27018/data
mkdir -p /mongodb/27019/conf /mongodb/27019/log /mongodb/27019/data
mkdir -p /mongodb/27020/conf /mongodb/27020/log /mongodb/27020/data
# 2修改配置文件:
cat >> /mongodb/27018/conf/mongodb.conf << EOF
systemLog:
destination: file
path: /mongodb/27018/log/mongodb.conf
logAppend: true
storage:
journal:
enabled: true
dbPath: /mongodb/27018/data
directoryPerDB: true
#engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 1
directoryForIndexes: true
collectionConfig:
blockCompressor: zlib
indexConfig:
prefixCompression: true
net:
port: 27018
replication:
oplogSizeMB: 2048
replSetName: configReplSet
sharding:
clusterRole: configsvr
processManagement:
fork: true
EOF
cp /mongodb/27018/conf/mongodb.conf /mongodb/27019/conf/
cp /mongodb/27018/conf/mongodb.conf /mongodb/27020/conf/
sed 's#27018#27019#g' /mongodb/27019/conf/mongodb.conf -i
sed 's#27018#27020#g' /mongodb/27020/conf/mongodb.conf -i
# 3启动节点,并配置复制集
mongod -f /mongodb/27018/conf/mongodb.conf
mongod -f /mongodb/27019/conf/mongodb.conf
mongod -f /mongodb/27020/conf/mongodb.conf
mongo --port 27018
use admin
config = {_id: 'configReplSet', members: [
{_id: 0, host: '10.0.0.200:27018'},
{_id: 1, host: '10.0.0.200:27019'},
{_id: 2, host: '10.0.0.200:27020'}]
}
rs.initiate(config)
# 注:configserver 可以是一个节点,官方建议复制集。configserver不能有arbiter。
# 新版本中,要求必须是复制集。
# 注:mongodb 3.4之后,虽然要求config server为replica set,但是不支持arbiter
=============================================================================
mongos节点配置
# 1.创建目录:
mkdir -p /mongodb/27017/conf /mongodb/27017/log
# 2.配置文件:
cat >> /mongodb/27017/conf/mongos.conf <<EOF
systemLog:
destination: file
path: /mongodb/27017/log/mongos.log
logAppend: true
net:
port: 27017
sharding:
configDB: configReplSet/10.0.0.200:27018,10.0.0.200:27019,10.0.0.200:27020
processManagement:
fork: true
EOF
# 3启动mongos
mongos -f /mongodb/27017/conf/mongos.conf
# 除了mongos知道有config以外,都没有任何联系
# 分片集群操作:
# 连接到其中一个mongos(10.0.0.200),做以下配置
# 1.连接到mongs的admin数据库
su - mongod
mongo 10.0.0.200:27017/admin
# 2.添加分片
db.runCommand( { addshard : "sh1/10.0.0.200:27021,10.0.0.200:27022,10.0.0.200:27023",name:"shard1"} )
db.runCommand( { addshard : "sh2/10.0.0.200:27024,10.0.0.200:27025,10.0.0.200:27026",name:"shard2"} )
# 3.列出分片
mongos> db.runCommand( { listshards : 1 } )
# 4.整体状态查看
mongos> sh.status();
# 现在都搭建好了,除了分片功能以外
配置Hash分片
# 对oldguo库下的vast大表进行hash
# 创建哈希索引
# 1.对于oldguo开启分片功能
# 首先连接到mongos
mongo --port 27017 admin
use admin
admin> db.runCommand( { enablesharding : "oldguo" } )
# 2.对于oldguo库下的vast表建立hash索引
use oldguo
oldguo> db.vast.ensureIndex( { id: "hashed" } )
# 3.开启分片
use admin
admin > sh.shardCollection( "oldguo.vast", { id: "hashed" } )
# 4.录入10w行数据测试
use oldguo
for(i=1;i<=100000;i++){ db.vast.insert({"id":i,"name":"shenzheng","age":70,"date":new Date()}); }
# 5.hash分片结果测试
mongo --port 27021
use oldguo
db.vast.count();
mongo --port 27024
use oldguo
db.vast.count();
# 6.判断是否Shard集群
admin> db.runCommand({ isdbgrid : 1})
# 7.列出所有分片信息
admin> db.runCommand({ listshards : 1})
# 8.列出开启分片的数据库
admin> use config
config> db.databases.find( { "partitioned": true } )
或者:
config> db.databases.find() //列出所有数据库分片情况
# 9.查看分片的片键
config> db.collections.find().pretty()
{
"_id" : "test.vast",
"lastmodEpoch" : ObjectId("58a599f19c898bbfb818b63c"),
"lastmod" : ISODate("1970-02-19T17:02:47.296Z"),
"dropped" : false,
"key" : {
"id" : 1
},
"unique" : false
}
# 10.查看分片的详细信息
admin> db.printShardingStatus()
或
admin> sh.status()
# 11.删除分片节点(谨慎)
(1)确认blance是否在工作
sh.getBalancerState()
(2)删除shard2节点(谨慎)
mongos> db.runCommand( { removeShard: "shard2" } )
注意:删除操作一定会立即触发blancer。
- 感谢你赐予我前进的力量