MongoDB Sharding
基础知识
Sharding是将一个大型数据集划分为更小、更易于管理的片段的过程。
Shared nothing架构是一种分布式计算架构,其中每个计算节点不与其他节点共享数据。
在数据库系统中,它被称为sharding(shared nothing)。
在出现以下三种情况时我们要考虑使用Sharding:
-
大量的数据和更大的读/写吞吐量需求使得商品数据库服务器不够。
-
数据库服务器不能寻址足够的RAM,或者它们可能没有足够的CPU内核来有效地处理工作负载。
-
由于数据量大,在一个磁盘或RAID存储(廉价磁盘的冗余阵列)上存储和管理备份是不实际的。
这些问题的解决方案是将数据库和数据库处理分布到多个服务器上。
mongodb中实现这一功能的方法称为sharding。由于管理和性能开销,sharding使数据库系统变得复杂。
为什么要Sharding?
分片有两个主要原因:
-
存储分配
-
负荷分配
如果对存储容量的监视显示,在某个时刻,数据库应用程序需要的存储量超过了可用的存储量,并且不可能添加更多的存储量,那么sharding是最好的选择。
Mongodb监控意味着运行db.stats()和db.collection.stats()在mongo shell中以获取关于当前数据库的存储使用情况和其中的集合的统计信息,
负载意味着CPU和RAM利用率、I/O带宽、客户端请求使用的网络传输=>响应时间。
如果在某个时刻响应时间不符合客户的期望,那么它会触发一个shard决策。
Shard的决定取决于网络使用情况、磁盘使用情况、CPU使用情况和RAM使用情况。
分配
MongoDB的数据粒度分为四个级别:
- Document: MongoDB中最小的数据单位;文档表示系统中的单个对象(如关系数据库中的一行)。
- Chunk: 按文件中的值聚集的一组文档;块是一个仅存在于分片设置中的概念;块是根据文档的键或键集的值(称为分片键)通过对文档进行逻辑分组而创建的。
- Collection: 数据库中的一组命名的文档;集合允许用户将数据库分离为对应用程序有意义的逻辑分组。
- Database:一套文档集合;数据库名称和集合名称的组合在整个系统中是唯一的,通常称为名称空间。
数据在分片集群中的分布方式如下:
5. 在整个数据库的级别上,每个数据库及其所有集合都放在自己的分片上。
6. 在分区或集合块的级别上,集合中的文档本身根据文档中的一个键或一组键(shard key)的值分布在多个分片上。
Sharding模拟
建立分片集群的过程包括三个步骤:
-
通过生成组成集群的所有mongod和mongos进程来启动mongod和mongos服务器。
-
通过更新配置来配置集群,使得副本集被初始化,碎片被添加到集群中,并且节点能够彼此通信。
-
sharding collections,以便它可以分布在多个切分上。
启动终端并处理以下shell命令来创建配置服务器(只有一个):
mkdir conf1
mkdir conf2
在一个新的终端窗口中处理以下命令:
mongod --configsvr --replSet conf --dbpath conf1 --port 4001
在另一个新的终端窗口中处理以下命令:
mongod --configsvr --replSet conf --dbpath conf2 --port 4002
接着在一个新的终端窗口中处理以下命令:
mongo -port 4001
rs.initiate()
rs.conf()
rs.add("localhost:4002")
rs.status()
exit
mongo --port 4002
rs.slaveOk()
exit
在新的终端窗口中处理以下命令,以创建数据碎片(两个复制集,每个复制集由两个服务器组成):
mkdir data1-1
mkdir data1-2
在一个新的终端窗口中处理以下命令:
mongod --shardsvr --replSet data1 --dbpath data1-1 --port 4003
在另一个新的终端窗口中处理以下命令:
mongod --shardsvr --replSet data1 --dbpath data1-2 --port 4004
在一个新的终端窗口中处理以下命令:
mongo -port 4003
rs.initiate()
rs.conf()
rs.add("localhost:4004")
rs.status()
exit
mongo --port 4004
rs.slaveOk()
exit
在新的终端窗口中处理以下命令:
mkdir data2-1
mkdir data2-2
在新的终端窗口中处理以下命令:
mongod --shardsvr --replSet data2 --dbpath data2-1 --port 4005
在新的终端窗口中处理以下命令:
mongod --shardsvr --replSet data2 --dbpath data2-2 --port 4006
在新的终端窗口中处理以下命令:
mongo -port 4005
rs.initiate()
rs.conf()
rs.add("localhost:4006")
rs.status()
exit
mongo --port 4006
rs.slaveOk()
exit
在新的终端窗口中处理以下命令以启动mongos:
mongos --configdb conf/localhost:4001,localhost:4002 --port 4000
在新的终端窗口中处理以下命令,在端口4003和4005上的复制集上创建碎片:
mongo --port 4000
sh.addShard("data1/localhost:4003")
sh.addShard("data2/localhost:4005")
列出数据碎片:
db.getSiblingDB("config").shards.find()
启用数据库分片:
sh.enableSharding("test")
db.getSiblingDB("config").databases.find()
将集合插入到shard中:
use test
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"000"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"001"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"002"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"003"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"004"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"005"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"006"})
db.testcol.insert({"fname":"James", "lname":"Bond", "shard-key":"007"})
在“shard-key”上创建索引:
db.testcol.createIndex({"shard-key":1})
Shard a collection:
sh.shardCollection("test.testcol", {"shard-key": 1})
获得统计数据:
db.test_collection.stats()
输出结果如下:
"shards" : {
"data2" : {
"ns" : "test.testcol",
"size" : 592,
"count" : 8,
"avgObjSize" : 74,
"storageSize" : 32768,
创建大型集合“test_collection”:
use test
var bulk = db.test_collection.initializeUnorderedBulkOp();
people = ["Marc", "Bill", "George", "Eliot", "Matt", "Trey", "Tracy",
"Greg", "Steve", "Kristina", "Katie", "Jeff"];
for(var i=0; i<1000000; i++){
user_id = i;
name = people[Math.floor(Math.random()*people.length)];
number = Math.floor(Math.random()*10001);
bulk.insert( { "user_id":user_id, "name":name, "number":number });
}
bulk.execute();
在“user_id”上创建索引:
db.test_collection.createIndex({user_id:1})
通过“user_id”分片一个集合:
sh.shardCollection("test.test_collection",{"user_id":1})
找到分片状态:
sh.status()
databases:
{ "_id" : "config", "primary" : "config", "partitioned" : true }
config.system.sessions
shard key: { "_id" : 1 }
unique: false
balancing: true
chunks:
data1 1
{ "_id" : { "$minKey" : 1 } } -->>
{ "_id" : { "$maxKey" : 1 } } on : data1 Timestamp(1, 0)
....
Production
理论上,要启动示例MongoDB shard集群,你必须启动总共9个进程(每个副本集有3个mongod,再加上3个配置服务器。
实际上在实践中不需要这么多过程。
复制的mongod是shard集群中资源最密集的进程,必须给它们自己的机器。
副本集仲裁程序的开销很小,而且它们不需要自己的服务器。
配置服务器存储的数据量相对较小。
这意味着那个配置服务器也不一定需要自己的机器。
References
- Banker K., Bakkum P., Verch S., Garret D., Hawkins T., MongoDB in Action, 2nd ed., Manning Publishers, 2016.
- MongoDB Manual, Sharding https://docs.mongodb.com/v3.6/sharding/