赞
踩
大家好,MongoDB是一个面向文档的NoSQL数据库,以其灵活性、性能和可扩展性而闻名。而分片(Sharding)是MongoDB提供的一种水平扩展数据的方式,允许将数据分布在多个物理服务器上,以应对大规模数据的存储和处理需求。
前面跟大家分享了关于 MongoDB副本集介绍与部署,可以参考:
分片(shard)是集群中负责管理数据的各个子集的一台或多台服务器。一个分片可以由多台服务器组成。在分片中,不同的服务器负责保存数据的不同部分,它们共同组成了整个数据集。
为了保证数据在各个分片间的均匀分布,MongoDB会自动在不同的分片之间移动数据子集。这些数据子集的移动是基于片键(shard key)来决定的。
分片最简单的方式就是每个分片负责存储一个区间段的数据,如用户名由4个分片进行存储,由用户名作为分片的依据那么将是如下情况:
这种方式有一个问题,在数据迁移时,可能会造成级联效应,即如果要将第一分片中的数据均衡,可能要影响很多分片,造成移动的数据量过大,影响整个系统的正常运行。原因很简单,每个分片要保持一区间模式。
这种方式可以有效的避免一分片一区间的数据迁移问题,宗旨是每个分片可以存储多个区间的数据。例子,如果[a, f)和[f, n)区间的数据比后面的两个分片都大,需要进行数据移动,则可以将[a, f)分为[a, c)和[c, f),[f, n)分为[f, j)和[j, n),并进行数据移动,则最终成为下面的分片状态:
在添加新分片后,MongoDB可以从每个分片取出部分数据,移动到新分片上。
在决定如何分配数据时,必须选择一个键来定义数据的块区间,这个键被称为片键(shard key)。片键可以是任意字段或字段的组合。
在最初的情况下,MongoDB只会创建一个数据块,该数据块的区间覆盖了整个数据集。只有当数据量达到一定程度时,MongoDB才会进行分片。一旦设置了分片配置,就需要指定片键。您可以通过使用命令 sh.shardCollection
来完成这一设置。
如果存在多个可用的分片,只要块的数量足够多,MongoDB就会将数据迁移到其他分片上。这个迁移过程被称为平衡,由平衡器进程负责执行。
触发平衡器的条件是:一个分片的块数量必须比最少块的分片多至少9个。此时,块将从拥挤的分片迁移到其他分片,直到各分片达到平衡为止。
平衡器并不会过于敏感,否则,即使分片之间稍微不平衡,也会频繁触发平衡操作,导致系统资源的过度浪费。
以下是一些方便验证配置的方法:
(1)设置块大小(chunkSize): 可以通过设置块的大小来快速观察数据迁移过程。例如,使用 --chunkSize 1
可以将块的大小设置为1MB。这样,在插入10MB的数据后,就会触发数据迁移。
(2)递增块大小: 可以设置递增的块大小。这样,在创建前十几个块时,MongoDB会自动将块的大小从200MB逐渐降低到64MB。
mongos是用户与MongoDB分片集群之间的交互点,它充当了用户访问集群的唯一入口,将复杂的处理流程隐藏在后台。简言之,所有对集群的操作都通过mongos服务进行,mongos会将用户的请求转发到相应的分片上去处理。
构建一个MongoDB Sharding Cluster需要三种角色:
(1)Shard Server(分片服务器): 这些是mongod实例,负责存储实际的数据块。在实际生产环境中,一个Shard Server角色通常由多台机器组成一个Replica Set来承担,以防止主机单点故障。
(2)Config Server(配置服务器): 这些也是mongod实例,它们存储了整个集群的元数据,包括Chunk信息等。
(3)Route Server(路由服务器): 这些是mongos实例,充当前端路由,客户端通过它们接入集群。它们让整个集群对客户端看起来像单一的数据库,使得前端应用可以透明地使用集群。
这三种角色共同协作,构成了一个MongoDB Sharding Cluster,能够实现数据的水平扩展,并在处理大数据量时表现出色。
分片集群的架构:
由于项目需要,要部署一个副本集的分片集群,有四个服务器用来部署mongoDB,我们将数据分为三个副本集分片,并分别部署三个config server和三个mongos, 则具体分配如下:
- Server 218.30.117.193:
- 10001 shard1
- 10002 shard2
- 10003 shard3
- 20001 config1
- Server 218.30.117.195:
- 10001 shard1
- 10002 shard2
- 10003 shard3
- 20002 config2
- Server 218.30.117.196:
- 10001 shard1
- 10002 shard2
- 10003 shard3
- 20003 config3
- Server 218.30.117.197:
- 30001 mongos1
- 30002 mongos2
- 30003 mongos3
配置数据库(Config Server)是存放集群元数据的mongod实例。使用 `--configsvr` 选项可以将一个mongod实例指定为配置服务器。每个配置服务器都保存了集群的完整元数据。
在生产环境中,必须部署三个配置服务实例,每个实例运行在不同的服务器上,以确保良好的运行时间和数据安全。然而,在测试环境中,你可以在一台独立的服务器上运行三个配置服务实例。
配置服务实例接收相对较小的流量,并且占用系统资源较少。因此,你可以在运行配置服务实例的系统上同时运行其他实例,以最大程度地利用系统资源。
(1) 为每个配置服务实例创建一个数据目录。默认的情况下,配置服务将数据文件存储在/data/configdb目录下。
(2) 运行三个配置服务实例。使用下面命令:
mongod --configsvr --dbpath <path> --port <port> --logpath <logpath>
如端口为20001,数据存放目录为/mongo-data/config1,日志路径为/mongo-data/config1/log.log,并创建服务进程,命令如下:
mongod --fork --configsvr --port 20001 --dbpath /mongo-data/config1/ --logpath /mongo-data/config1/log.log
Mongos实例是轻量级的,不需要数据目录。你可以在运行其他集群组件的系统上运行一个mongos实例,例如一个应用服务或者一个mongod进程。Mongos的默认端口是27017。
当你启动一个mongos实例时,你需要在配置文件或者命令行中指定那三个config server的主机名。为了操作的灵活性,最好使用配置服务器的DNS名而不是IP地址。如果你没有使用可解析的主机名,在没有重启每个mongos和mongod实例的情况下,无法更改配置服务器的名称或IP地址。
启动一个mongos实例,命令行如下:
mongos --configdb <config server hostnames>
举个例子,启动一个mongos连接以下配置服务器:
- cfg0.example.net
- cfg1.example.net
- cfg2.example.net
将运行下面的命令:
mongos --configdb cfg0.example.net:27019 cfg1.example.net:27019 cfg2.example.net:27019
一个分片可以是一个单独的mongod或者是一个副本集。在生产环境中,每个分片应该是一个副本集。
(1) 从mongo shell连接到mongos实例上。执行下面的命令:
mongo --host <hostname of machine running mongos> --port <port mongos listens on>
例如:
mongo --host mongos0.example.net --port 27017
(2) 使用sh.addShard()命令将每个分片添加到集群中,如下面的例子所示。为每个分片单独的使用sh.addShard()命令。如果分片是一个副本集,则要指定副本集名并指定组的一个成员。
下面的例子使用了sh.addShard()命令添加一个分片:
sh.addShard(“rs1/mongodb0.example.net:27017”)
版本2.0.3改变的。
在这个版本之前,必须指定所有的副本集成员,如:
sh.addShard( "rs1/mongodb0.example.net:27017,mongodb1.example.net:27017,mongodb2.example.net:27017" )
sh.addShard( "mongodb0.example.net:27017" )
在对一个集合进行分片之前,你必须先启用该集合所在的数据库的分片功能。启用分片功能并不会重新分配数据,但是它确保了集合可以被分片。
一旦你启用了数据库的分片功能,MongoDB会为其分配一个主分片,使得MongoDB在进行分片之前会将所有的数据保存在该数据库上。
mongo --host <hostname of machine running mongos> --port <port mongos listens on>
sh.enableSharding("<database>")
也可以使用enableSharding命令,语法如下:
db.runCommand( { enableSharding : <database> } )
可以对每个collection开启分片功能。
(1) 决定使用什么作为片键。片键的选择会影响分片的性能。
(2) 如果一个collection已经包含数据,则必须在片键的字段上使用ensureIndex()命令建立一个索引。如果collection是空的,则MongoDB将在sh.shardCollection()阶段建立一个索引。
(3) 通过在mongo shell中执行sh.shardCollection()方法开启一个collection的分片功能。这个方法的使用语法如下:
db.shardCollection(“<database>.<collection>”, shard-key-pattern)
用你的数据库命名空间,由数据库名,一个点和collection的名字组成,替换<database>.<collection>字符串。Shard-key-pattern代表你的片键,你可以使用与一个索引相同的模式。
示例:
- sh.shardCollection("records.people", { "zipcode": 1, "name": 1 } )
- sh.shardCollection("people.addresses", { "state": 1, "_id": 1 } )
- sh.shardCollection("assets.chairs", { "type": 1, "_id": 1 } )
-
- db.alerts.ensureIndex( { _id : "hashed" } )
- sh.shardCollection("events.alerts", { "_id": "hashed" } )
这些分片操作按顺序依次表示为:
这个片键通过字段zipcode的值进行数据分配。如果这个有大量相同的值,则会根据name字段的值对块进行分裂。
这个片键通过字段state的值进行数据分配。如果这个有大量相同的值,则会根据_id字段的值对块进行分裂。
这个片键通过字段type的值进行数据分配。如果这个有大量相同的值,则会根据_id字段的值对块进行分裂。
在版本2.4中最新出现的。
这个片键通过字段_id的散列值进行数据分配。MongoDB计算_id字段的散列值作为散列索引,它将提供集群中文档的均匀分布。
- Server 218.30.117.193:
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10001 --dbpath /usr/tmp/shard1/ --logpath /usr/tmp/shard1/log.log --replSet shard1 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10002 --dbpath /usr/tmp/shard2/ --logpath /usr/tmp/shard2/log.log --replSet shard2 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10003 --dbpath /usr/tmp/shard3/ --logpath /usr/tmp/shard3/log.log --replSet shard3 --rest
- Server 218.30.117.195:
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10001 --dbpath /usr/tmp/shard1/ --logpath /usr/tmp/shard1/log.log --replSet shard1 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10002 --dbpath /usr/tmp/shard2/ --logpath /usr/tmp/shard2/log.log --replSet shard2 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10003 --dbpath /usr/tmp/shard3/ --logpath /usr/tmp/shard3/log.log --replSet shard3 --rest
- Server 218.30.117.196:
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10001 --dbpath /usr/tmp/shard1/ --logpath /usr/tmp/shard1/log.log --replSet shard1 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10002 --dbpath /usr/tmp/shard2/ --logpath /usr/tmp/shard2/log.log --replSet shard2 --rest
- # /usr/local/mongodb/bin/mongod --fork --shardsvr --port 10003 --dbpath /usr/tmp/shard3/ --logpath /usr/tmp/shard3/log.log --replSet shard3 --rest
- Server 218.30.117.193:
- # /usr/local/mongodb/bin/mongod --fork --configsvr --port 20001 --dbpath /usr/tmp/config1/ --logpath /usr/tmp/config1/log.log --rest
- Server 218.30.117.195:
- # /usr/local/mongodb/bin/mongod --fork --configsvr --port 20002 --dbpath /usr/tmp/config1/ --logpath /usr/tmp/config1/log.log --rest
- Server 218.30.117.196:
- # /usr/local/mongodb/bin/mongod --fork --configsvr --port 20003 --dbpath /usr/tmp/config1/ --logpath /usr/tmp/config1/log.log --rest
这里为了方便的看到分片过程,将chunkSize设置为1,也就是每个块大小为1MB。
- 218.30.117.197:
- # /usr/local/mongodb/bin/mongos --fork --port 30001 --logpath /usr/tmp/route1/log.log --chunkSize 1 --configdb 218.30.117.193:20001,218.30.117.195:20002,218.30.117.196:20003
- # /usr/local/mongodb/bin/mongos --fork --port 30002 --logpath /usr/tmp/route2/log.log --chunkSize 1 --configdb 218.30.117.193:20001,218.30.117.195:20002,218.30.117.196:20003
- # /usr/local/mongodb/bin/mongos --fork --port 30003 --logpath /usr/tmp/route3/log.log --chunkSize 1 --configdb 218.30.117.193:20001,218.30.117.195:20002,218.30.117.196:20003
4、在mongod上配置副本集
先连接到某个副本集的成员上,然后初始化副本集配置:
连接到218.30.117.193:10001上初始化副本集shard1:
- # /usr/local/mongodb/bin/mongo 218.30.117.193:10001/admin
- MongoDB shell version: 2.4.2
- connecting to: 218.30.117.193:10001/admin
- > config={_id:'shard1', members:[{_id:0, host:'218.30.117.193:10001'}, {_id:1, host:'218.30.117.195:10001'}, {_id:2, host:'218.30.117.196:10001'}]}
- > rs.initiate(config)
连接到218.30.117.195:10002上初始化副本集shard2:
- # /usr/local/mongodb/bin/mongo 218.30.117.195:10002/admin
- MongoDB shell version: 2.4.2
- connecting to: 218.30.117.195:10002/admin
- > config={_id:'shard2', members:[{_id:0, host:'218.30.117.193:10002'}, {_id:1, host:'218.30.117.195:10002'}, {_id:2, host:'218.30.117.196:10002'}]}
- > rs.initiate(config)
连接到218.30.117.196:10003上初始化副本集shard3:
- # /usr/local/mongodb/bin/mongo 218.30.117.196:10003/admin
- MongoDB shell version: 2.4.2
- connecting to: 218.30.117.196:10003/admin
- > config={_id:'shard3', members:[{_id:0, host:'218.30.117.193:10003'}, {_id:1, host:'218.30.117.195:10003'}, {_id:2, host:'218.30.117.196:10003'}]}
- > rs.initiate(config)
可以通过rs.status()命令查看副本集配置结果:
- > rs.status()
- {
- "set" : "shard3",
- "date" : ISODate("2013-05-14T17:44:05Z"),
- "myState" : 1,
- "members" : [
- {
- "_id" : 0,
- "name" : "218.30.117.193:10003",
- "health" : 1,
- "state" : 2,
- "stateStr" : "SECONDARY",
- "uptime" : 147,
- "optime" : {
- "t" : 1368553298,
- "i" : 1
- },
- "optimeDate" : ISODate("2013-05-14T17:41:38Z"),
- "lastHeartbeat" : ISODate("2013-05-14T17:44:03Z"),
- "lastHeartbeatRecv" : ISODate("2013-05-14T17:44:05Z"),
- "pingMs" : 0,
- "syncingTo" : "218.30.117.196:10003"
- },
- {
- "_id" : 1,
- "name" : "218.30.117.195:10003",
- "health" : 1,
- "state" : 2,
- "stateStr" : "SECONDARY",
- "uptime" : 147,
- "optime" : {
- "t" : 1368553298,
- "i" : 1
- },
- "optimeDate" : ISODate("2013-05-14T17:41:38Z"),
- "lastHeartbeat" : ISODate("2013-05-14T17:44:03Z"),
- "lastHeartbeatRecv" : ISODate("2013-05-14T17:44:03Z"),
- "pingMs" : 1,
- "syncingTo" : "218.30.117.196:10003"
- },
- {
- "_id" : 2,
- "name" : "218.30.117.196:10003",
- "health" : 1,
- "state" : 1,
- "stateStr" : "PRIMARY",
- "uptime" : 1109,
- "optime" : {
- "t" : 1368553298,
- "i" : 1
- },
- "optimeDate" : ISODate("2013-05-14T17:41:38Z"),
- "self" : true
- }
- ],
- "ok" : 1
- }
先通过mongo shell连接到mongos实例:
- # /usr/local/mongodb/bin/mongo 218.30.117.197:30001/admin
- MongoDB shell version: 2.4.2
- connecting to: 218.30.117.197:30001/admin
- mongos> db.runCommand({addshard:"shard1/218.30.117.193:10001", name:"ShardSet1"})
- { "shardAdded" : "ShardSet1", "ok" : 1 }
- mongos> db.runCommand({addshard:"shard2/218.30.117.195:10002", name:"ShardSet2"})
- { "shardAdded" : "ShardSet2", "ok" : 1 }
- mongos> db.runCommand({addshard:"shard3/218.30.117.195:10003", name:"ShardSet3"})
- { "shardAdded" : "ShardSet3", "ok" : 1 }
通过db.runCommand({listshards: 1})查看分片集群的信息:
- mongos> db.runCommand({listshards: 1})
- {
- "shards" : [
- {
- "_id" : "ShardSet1",
- "host" : "shard1/218.30.117.193:10001,218.30.117.195:10001,218.30.117.196:10001"
- },
- {
- "_id" : "ShardSet2",
- "host" : "shard2/218.30.117.193:10002,218.30.117.195:10002,218.30.117.196:10002"
- },
- {
- "_id" : "ShardSet3",
- "host" : "shard3/218.30.117.193:10003,218.30.117.195:10003,218.30.117.196:10003"
- }
- ],
- "ok" : 1
- }
我们的数据库名为page_db,则命令如下:
- mongos> sh.enableSharding("page_db")
- { "ok" : 1 }
- mongos> sh.shardCollection("page_db.users", {_id : "hashed"})
- { "collectionsharded" : "page_db.users", "ok" : 1 }
总结:
分片功能在少数据量时没有必要,只是增加了架构的复杂性。但如果数据量大,访问量大的情况下,分片还是很有必要的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。