正文
MongoDB 中通过 Shard 支持服务器水平扩展,通过 Replication 支持高可用(HA)。这两种技术可以分开来使用,但是在大数据库企业级应用中通常人们会把他们结合在一起使用。
首先我们简要概述一下分片在 MongoDB 中的工作原理。通过分片这个单词我们可以看出,他的意思是将数据库表中的数据按照一定的边界分成若干组,每一组放到一台 MongoDB 服务器上。拿用户数据举例,比如你有一张数据表存放用户基本信息,可能由于你的应用很受欢迎,短时间内就积攒了上亿个用户,这样当你在这张表上进行查询时通常会耗费比较长的时间,这样这个用户表就称为了你的应用程序的性能瓶颈。很显然的做法是对这张用户表进行拆分,假设用户表中有一个
age
年龄字段,我们先做一个简单的拆分操作,按照用户的年龄段把数据放到不同的服务器上,以 20 为一个单位,20 岁以下的用户放到 server1,20 到 40 岁的用户放到 server2,40-60 岁的用户放到 server3,60 岁以上放到 server4,后面我们会讲这样的拆分是否合理。在这个例子中,用户年龄
age
就是我们进行
Sharding
(切分)的
Shard Key
(关于 Shard Key 的选择后面会详细介绍),拆分出来的
server1
,
server2
,
server3
和
server4
就是这个集群中的 4 个
Shard
(分区)服务器。好,Shard 集群已经有了,并且数据已经拆分完好,当用户进行一次查询请求的时候我们如何向这四个 Shard 服务器发送请求呢?例如:我的查询条件是用户年龄在 18 到 35 岁之间,这样个查询请求应当发送到
server1
和
server2
,因为他们存储了用户年龄在 40 以下的数据,我们不希望这样的请求发送到另外两台服务器中因为他们并不会返回任何数据结果。此时,另外一个成员就要登场了,
mongos
,它可以被称为 Shard 集群中的路由器,就像我们网络环境中使用的路由器一样,它的作用就是讲请求转发到对应的目标服务器中,有了它我们刚才那条查询语句就会正确的转发给
server
和
server2
,而不会发送到
server3
和
server4
上。
mongos
根据用户年龄(Shard Key)分析查询语句,并把语句发送到相关的 shard 服务器中。除了
mongos
和
shard
之外,另一个必须的成员是配置服务器,
config server
,它存储 Shard 集群中所有其他成员的配置信息,
mongos
会到这台
config server
查看集群中其他服务器的地址,这是一台不需要太高性能的服务器,因为它不会用来做复杂的查询计算,值得注意的是,在 MongoDB3.4 以后,
config server
必须是一个
replica set
。理解了上面的例子以后,一个 Shard 集群就可以部署成下图所示的结构:
其中:
-
shard: 每一个 Shard 服务器存储数据的一个子集,例如上面的用户表,每一个 Shard 存储一个年龄段的用户数据。
-
mongos: 处理来自应用服务器的请求,它是在应用服务器和Shard 集群之间的一个接口。
-
config server: 存储 shard 集群的配置信息,通常部署在一个 replica set 上。
这样的服务器构架是否合理,或者说是否能够满足数据量不断增长的需求。如果仅仅是通过理论解释恐怕很难服众,我已经信奉理论结合实际的工作方式,所以在我的文章中除了阐述理论之外,一定会有一定的示例为大家验证理论的结果。接下来我们就根据上面的例子做一套本地运行环境。由于 MongoDB 的便捷性,我们可以在任何一台 PC 上搭建这样一个数据库集群环境,并且不限制操作系统类型,任何 Windows/Linux/Mac 的主流版本都可以运行这样的环境。在本文中,我才用 MongoDB3.4 版本。
对于如何创建一个 MongoDB Shard 环境,网上有很多教程和命令供大家选择,创建一个有 3 个 Mongos,每个 Mongos 连接若干个 Shards,再加上 3 个 config server cluster,通常需要 20 几台 MongoDB 服务器。如果一行命令一行命令的打,即便是在非常熟练的情况下,没有半个小时恐怕搭建不出来。不过幸运的是有第三方库帮我们做这个事情,大家可以查看一下
mtools
。他是用来创建各种 MongoDB 环境的命令行工具,代码使用
python
写的,可以通过
pip install
安装到你的环境上。具体的使用方法可以参考
https://github.com/rueckstiess/mtools/wiki/mlaunch
。也可以通过
https://github.com/zhaoyi0113/mongo-cluster-docker
上面的脚本把环境搭载 Docker 上面。
下面的命令用来在本地创建一个 MongoDB Shard 集群,包含 1 个
mongos
路由,3 个
shard
replica,每个 replica 有 3 个
shard
服务器,3 个
config
服务器。这样一共创建 13 个进程。
mlaunch init --replicaset --sharded 3 --nodes 3 --config 3 --hostname localhost --port 38017 --mongos 1
服务器创建好以后我们可以连接到
mongos
上看一下 shard 状态,端口是上面制定的 38017。
mongos> sh.status()
--- Sharding Status ---
...
shards:
{ "_id" : "shard01", "host" : "shard01/localhost:38018,localhost:38019,localhost:38020", "state" : 1 }
{ "_id" : "shard02", "host" : "shard02/localhost:38021,localhost:38022,localhost:38023", "state" : 1 }
{ "_id" : "shard03", "host" : "shard03/localhost:38024,localhost:38025,localhost:38026", "state" : 1 }
active mongoses:
"3.4.0" : 1
...
可以看到刚才创建的 shard 服务器已经加入到这台 mongos 中了,这里有 3 个 shard cluster,每个 cluster包含 3 个 shard 服务器。除此之外,我们并没有看到关于 Shard 更多的信息。这是因为这台服务器集群还没有任何数据,而且也没有进行数据切分。
首先是数据的录入,为了分析我们服务器集群的性能,需要准备大量的用户数据,幸运的是
mtools
提供了
mgenerate
方法供我们使用。他可以根据一个数据模版向 MongoDB 中插入任意条 json 数据。下面的 json 结构是我们在例子中需要使用的数据模版:
{
"user": {
"name": {
"first": {"$choose": ["Liam", "Aubrey", "Zoey", "Aria", "Ellie", "Natalie", "Zoe", "Audrey", "Claire", "Nora", "Riley", "Leah"] },
"last": {"$choose": ["Smith", "Patel", "Young", "Allen", "Mitchell", "James", "Anderson", "Phillips", "Lee", "Bell", "Parker", "Davis"] }
},
"gender": {"$choose": ["female", "male"]},
"age": "$number",
"address": {
"zip_code": {"$number": [10000, 99999]},
"city": {"$choose": ["Beijing", "ShangHai", "GuangZhou", "ShenZhen"]}
},
"created_at": {"$date": ["2010-01-01", "2014-07-24"] }
}
}
把它保存为一个叫
user.json
的文件中,然后使用
mgenerate
插入一百条随机数据。随机数据的格式就按照上面
json
文件的定义。你可以通过调整
--num
的参数来插入不同数量的 Document。(Link to mgenerate wiki)