mongoDB分片实践

mongoDB分片实践

单片遇到的问题

  • 单台主机的硬盘资源限制,一台主机的硬盘空间是一定的,当存放数据量达到硬盘的容量极限后,就只能为主机增加硬盘空间;
  • 入库速度的限制,如果某个功能实现每分每秒有大量的数据插入 (如:日志收集、网页抓取等),单台主机入库的速度存在则极限( 后面会介绍 ),一旦入库请求操作此极限,入库速度会急剧下降;
    为提高项目的入库速度,尝试使用mongoDB的分片技术。

分片解决的问题

  • 分片可以进行水平扩展,一旦现行的设备无法满足需求之后,只需要添加分片即可,添加之后 MongoDB 会根据设置自动平衡数据(将数据移动到新插入的分片中);

  • 分片之后,入库将会是分布式方式入库,mongos会将所有的入库请求,根据设置分配到各个分片上,降低单个分片入库的压力,使得一些对入库要求很高的的程序能稳定运行;

分片带来的问题

分片提高了入库速度,但相应的查询更新速度就有所降低。

如果不合理的查询(没有提供分片字段),将导致遍历整个数据集合,这里的不合理查询一般都是查询条件中不包含分片的字段,MongoDB 则无法定位查询在哪个分配上进行,进而导致遍历整个集合。

分片集群的架构

Sharded cluster由Shard、Mongos和Config server3个组件构成

本地分片集群配置

配置服务器: 27020 27021 27022

分片服务器: 27030 27031 27032

路由服务器: 27040

创建配置服务器及其副本集

1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/config1 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/config1.log --replSet conServer --configsvr --port 27020 --fork
1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/config2 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/config2.log --replSet conServer --configsvr --port 27021 --fork
1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/config3 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/config3.log --replSet conServer --configsvr --port 27022 --fork

config = {
_id : “conServer”,
members : [
{_id : 0, host : “192.168.18.30:27022” },
{_id : 1, host : “192.168.18.30:27021” },
{_id : 2, host : “192.168.18.30:27020” }
]
}

rs.initiate(config)

创建分片服务器

1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/sh1 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/sh1.log  --shardsvr --port 27030 --fork

mongod –dbpath /data/mongodata/sh1 –logpath /Users/chenyangfan/work/BNGJ/projects/data/log/sh1.log –shardsvr –port 27030 –fork

1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/sh2 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/sh2.log  --shardsvr --port 27031 --fork

1
mongod --dbpath /Users/chenyangfan/work/BNGJ/projects/data/sh3 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/sh3.log  --shardsvr --port 27032 --fork

创建路由服务器

1
mongos --configdb conServer/localhost:27020,localhost:27021,localhost:27022 --port 27040 --logpath /Users/chenyangfan/work/BNGJ/projects/data/log/mongos.log --fork

为路由服务器配置分片服务器

1
2
3
4
5
mongo --port 27040 --host localhost

> sh.addShard('localhost:27030');
> sh.addShard('localhost:27031');
> sh.addShard('localhost:27032');

配置片键

选择分片键要选择唯一性大的,随机性大的,推荐使用 Hash Sharding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mongo localhost:27040/admin
MongoDB shell version: 2.6.7
connecting to: admin

# 添加分片,MongoDB 会将启动的分片的信息保持到配置服务器上;
mongos> sh.addShard('192.168.18.30:27030')
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> sh.addShard('192.168.18.30:27031')
{ "shardAdded" : "shard0001", "ok" : 1 }
mongos> sh.addShard('192.168.18.30:27032')
{ "shardAdded" : "shard0002", "ok" : 1 } 通过`sh.status()` 查看分片结果:

mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("55471025f722cd42a930ecee")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:27037" }
{ "_id" : "shard0001", "host" : "localhost:27038" }
{ "_id" : "shard0002", "host" : "localhost:27039" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" }

启动数据库分片sh.enableSharding(<databaseName>)

1
2
3
4
5
6
7
8
mongos> sh.enableSharding('test')
{ "ok" : 1 }
# 当启动数据库test的分片过后,再通过`sh.status()`命令查看,可发现相应的改变:

# ... 前面相同
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" }

在databases一项中,对应的多了一项分片的test数据库,其中:

  • _id,表示分片的数据库名,此处为test
  • partitioned,表示是否启动分片,为true表示启用分片;
  • primary,表示数据库存放的“主片”,一旦主片存放满了,或达到了分片设置的阈值,才会将数据存放到其它片,否则将一直存放于该主片上。

之后我们需要配置片键,否则所有的操作都将在随机的一个主分片上操作,这是因为分片服务器不知道怎么进行分片,所以我们还需要配置片键来告诉分片服务器按照什么来分片。

分片是基于数据库集合中的文档的一个键进行分片的,比如选择username键,那么会根据这个键的顺序就行分片,而mongodb会自动平衡分片的数据。

Mongodb要求作为片键的键必须是索引过的,所以我们在建立片键之前需要对键进行索引,建立后片键就是集合中的最重要的索引。

在生产环境中应先想好数据建构建立索引和片键后开始操作数据,这样会减轻分片服务器的负载。

然后在开启分片的数据库中的test集合插入测试数据,注意此时我们还没有进行配置片键,所以所有的数据操作都在分片服务器随机分配的一个主分片上面进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> use test

> for(var i = 0; i < 100000; i++){
db.test.insert({
username: 'user' + i,
idNum: i
})
}

# 这时候以username为片键,通过sh.shardCollection方法进行建立
> sh.shardCollection(namespace, key, unique, options)

# 首先给我们要建立的片键建立索引:
> db.test.ensureIndex({'username': 1});

# 建立片键
> sh.shardCollection('test.test', {username:1});

等待几分钟后,可以通过sh.status方法查看数据分片的情况,可以从中很清楚的看见哪些数据在哪个分片服务器上面,并且通过explain方法来查看我们查询的过程中哪些分片服务器参与了查询

db.settings.save( { _id:”chunksize”, value: 1 } )
注意:
一旦对一个集合分片,分片键和分片值就不可改变。 如:不能给集合选择不同的分片键、不能更新分片键的值。

sh.shardCollection( namespace, key )

  • namespace 参数是 database.collection,指定目标集合完整的命名空间
  • 关键参数由包含字段和该字段的索引遍历方向的文档组成。

hash片键

range片键

索引

  • 所有的分片集合必须有一个支撑 shard key 的索引。

  • 如果集合为空,如果索引为空,则 sh.shardCollection() 在 shard key 上创建索引

  • 如果集合不为空,你必须在使用 sh.shardCollection() 之前创建索引

  • 如果删除分片键的最后一个有效索引,请通过仅在分片键上重新创建索引来恢复。

唯一索引

对于一个已分片的集合,只有 _id 字段索引和 shard key 上的单索引或者混合索引是唯一的索引

你不能分片一个唯一索引在其他字段上的集合

你不能在一个已分片的集合上创建不同字段的唯一索引

通过使用shard key上的唯一索引,MongoDB可以强制shard键值的唯一性。 MongoDB对整个key组合,而不是分片密钥的单个组件实施唯一性。为了 shard 强制一致性,可以在 sh.shardCollection() 方法的 unique 参数传入 true 值:

  • 如果集合为空,如果该集合的索引不存在则 sh.shardCollection() 在 shard key 上创建唯一索引

  • 如果集合不为空,在使用 sh.shardCollection() 之前必须创建索引

片键上的唯一约束
过程
要使用 unique 条件进行分片,需要像下面这样执行 shardCollection :

1
db.runCommand( { shardCollection : "test.users" , key : { email : 1 } , unique : true } );

记住 _id 字段总是唯一的,默认情况下,MongoDB会将 ObjectId 写到 _id 字段.然而,你也可以将自己生成的值写到 _id 字段并用这个字段作为片键.使用以下操作将 _id 用作片键:

1
db.runCommand( { shardCollection : "test.users" } )

文档

mongod –dbpath /data/mongodata/data/sh1 –logpath /data/mongodata/log/sh1.log –bind_ip 192.168.18.30 –shardsvr –port 27030 –fork

mongod –dbpath /data/mongodata/data/sh2 –logpath /data/mongodata/log/sh2.log –bind_ip 192.168.18.30 –shardsvr –port 27031 –fork

mongod –dbpath /data/mongodata/data/sh3 –logpath /data/mongodata/log/sh3.log –bind_ip 192.168.18.30 –shardsvr –port 27032 –fork

mongos –configdb conServer/192.168.18.30:27020,192.168.18.30:27021,192.168.18.30:27022 –bind_ip 192.168.18.30 –port 27040 –logpath /data/mongodata/log/mongos.log –fork

mongod –dbpath /data/mongodata/data/config1 –logpath /data/mongodata/log/config1.log –replSet conServer –configsvr –bind_ip 192.168.18.30 –port 27020 –fork

mongod –dbpath /data/mongodata/data/config2 –logpath /data/mongodata/log/config2.log –replSet conServer –configsvr –bind_ip 192.168.18.30 –port 27021 –fork

mongod –dbpath /data/mongodata/data/config3 –logpath /data/mongodata/log/config3.log –replSet conServer –configsvr –bind_ip 192.168.18.30 –port 27022 –fork