分片集群简介

在之前有说过关于MongoDB的复制集,复制集主要用来实现自动故障转移从而达到高可用的目的,然而,随着业务规模的增长和时间的推移,业务数据量会越来越大,当前业务数据可能只有几百GB不到,一台DB服务器足以搞定所有的工作,而一旦业务数据量扩充大几个TB几百个TB时,就会产生一台服务器无法存储的情况,此时,需要将数据按照一定的规则分配到不同的服务器进行存储、查询等,即为分片集群。分片集群要做到的事情就是数据分布式存储。

分片部署架构

架构设计

先看一张图:

MongoDB分片集群 - 图1

先做一些解释:1.shard片:一般为一台单独的服务器,即为一个数据存储节点,这个存储节点需要做复制集,实现高可用以及自动故障转移,一般一个分片集群有多个shard片;

  1. config服务器:主要是记录shard的配置信息(元信息metadata),如数据存储目录,日志目录,端口号,是否开启了journal等信息,为了保证config服务器的可用性,也做了复制集处理,注意,一旦配置服务器无法使用,则整个集群就不能使用了,一般是独立的三台服务器实现冗余备份,这三台可能每一台是独立的复制集架构。3.Mongos路由进程(Router):应用程序通过驱动程序直接连接router,router启动时从配置服务器复制集中读取shared信息,然后将数据实际写入或读取(路由)到具体的shard中。

部署配置

注意:此处由于我这边只有一台服务器(PC机),所以这里实现的是一个伪集群,部署架构如图所示:MongoDB分片集群 - 图2

一个Shard(复制集模式),一个config进程,一个router进程,均在同一台服务器上面

沿用上一节的复制集作为shard,

启动:

  1. mongod --dbpath=D:\MongoDB\Server\3.2\data\rs0_0 --logpath=D:\MongoDB\Server\3.2\logs\rs0_0.log --port=40000 --replSet=rs0
  2. mongod --dbpath=D:\MongoDB\Server\3.2\data\rs0_1 --logpath=D:\MongoDB\Server\3.2\logs\rs0_1.log --port=40001 --replSet=rs0
  3. mongod --dbpath=D:\MongoDB\Server\3.2\data\rs0_2 --logpath=D:\MongoDB\Server\3.2\logs\rs0_2.log --port=40002 --replSet=rs0

配置服务器启动:

  1. mongod --dbpath=D:\MongoDB\Server\3.2\data\db_config --logpath=D:\MongoDB\Server\3.2\logs\dbconfig.log --port=40003 --configsvr

路由服务器启动

  1. mongos --logpath=D:\MongoDB\Server\3.2\logs\dbrouter.log --port=40004 --configdb=linfl-PC:40003

添加分片信息到集群:

  1. mongo --port 40004
  2. mongos> use admin
  3. switched to db admin
  4. mongos> sh.addShard("rs0/linfl-PC:40000,linfl-PC:40001")
  5. {
  6. "ok" : 0,
  7. "errmsg" : "can't add shard 'rs0/linfl-PC:40000,linfl-PC:40001' because a local database 'config' exists in another config",
  8. "code" : 96
  9. }

报错了,原来的rs0由于已经存在config数据库了,去把他删掉:

  1. D:\MongoDB\Server\3.2\bin>mongo --port 40000
  2. 2017-02-27T16:14:51.454+0800 I CONTROL [main] Hotfix KB2731284 or later update
  3. is not installed, will zero-out data files
  4. MongoDB shell version: 3.2.9
  5. connecting to: 127.0.0.1:40000/test
  6. rs0:PRIMARY> show dbs
  7. cms 0.000GB
  8. config 0.000GB
  9. local 0.000GB
  10. test 0.000GB
  11. rs0:PRIMARY> use config
  12. switched to db config
  13. rs0:PRIMARY> db.dropDatabase()
  14. { "dropped" : "config", "ok" : 1 }

然后重新连接到4004执行添加分片即可,看下状态:

  1. D:\MongoDB\Server\3.2\bin>mongo --port 40004
  2. 2017-02-27T16:15:17.286+0800 I CONTROL [main] Hotfix KB2731284 or later update
  3. is not installed, will zero-out data files
  4. MongoDB shell version: 3.2.9
  5. connecting to: 127.0.0.1:40004/test
  6. mongos> sh.addShard("rs0/linfl-PC:40000,linfl-PC:40001")
  7. { "shardAdded" : "rs0", "ok" : 1 }
  8. mongos> sh.status()
  9. --- Sharding Status ---
  10. sharding version: {
  11. "_id" : 1,
  12. "minCompatibleVersion" : 5,
  13. "currentVersion" : 6,
  14. "clusterId" : ObjectId("58b3d9df84493cb599359c8b")
  15. }
  16. shards:
  17. { "_id" : "rs0", "host" : "rs0/linfl-PC:40000,linfl-PC:40001" }
  18. active mongoses:
  19. "3.2.9" : 1
  20. balancer:
  21. Currently enabled: yes
  22. Currently running: no
  23. Failed balancer rounds in last 5 attempts: 5
  24. Last reported error: remote client 192.168.56.1:51845 tried to initiali
  25. ze this host as shard rs0, but shard name was previously initialized as config
  26. Time of Reported error: Mon Feb 27 2017 16:15:48 GMT+0800
  27. Migration Results for the last 24 hours:
  28. No recent migrations
  29. databases:
  30. { "_id" : "cms", "primary" : "rs0", "partitioned" : false }
  31. { "_id" : "test", "primary" : "rs0", "partitioned" : false }

对config数据库中的各个集合做如下解释:

  1. mongos> use config
  2. switched to db config
  3. mongos> show collections
  4. actionlog //
  5. changelog //保存被分片的集合的任何元数据的改变信息,如chunks的迁移,分割等
  6. chunks //集群中所有的块信息,块的数据范围以及块所在的片
  7. databases //集群中所有的数据库
  8. lockpings //追踪集群中的激活组件
  9. locks//均衡器产生的锁信息
  10. mongos//所有路由信息
  11. settings//分片集群的配置信息,如chunk大小,均衡器状态等
  12. shards//集群中所有的片信息
  13. tags
  14. version//元信息版本

注意:对集群的操作应该都是通过客户端连接mongos路由来执行。

正常来讲,一个完整的生产环境至少需要9个Mongod实例,一个Mongos实例,10台机器才能组成,而由于可以对部分实例采用合并在一台机器上进行部署的操作(对资源消耗较低,或对资源消耗的类型不同),能够得到典型的部署结构如下:

MongoDB分片集群 - 图3

以上部署方案使得每个shard(复制集)中的节点完全分开到不同的服务器上,并将config服务分开,使得其中任何一台服务器宕机,集群都可正常工作,当然我认为,最少只需要三台服务器即可构成稳定的集群(3坏1可正常工作),然而却非典型的架构了。

分片工作机制

MongoDB的分片是基于集合(表)来进行的,要对一个集合分片,就要像使其所在的数据库支持分片。

集合分片

MongoDB的分片是基于返回的,即任何一个文档一定位于指定片键的某个范围内,一单片键选择好以后,chunks就会按照片键将一部分documents从逻辑上组合在一起如:对users集合选择city作为片键来分片,如果city字段中存在 ‘beijing’,’guangzhou’,’changsha’,初始时随机的向集群中插入文档,由于初始时chunks比较小,没有达到阈值64MB或10000个文档,不需要分片,随着继续插入,超过chunk阈值后chunk被分割成两个,最终分步可能如下所示:

开始键值 — 结束键值 —所在分片开始键值 结束键值 所在分片

开始键值 结束键值 所在分片
-∞ beijing rs0
beijing changsha rs1
changsha guangzhou rs0
guangzhou rs1

需要注意的是:chunks所包含的文档并非物理上包含,而是一种逻辑包含,指标是带有片键的文档会落在那个范围内。

使集合支持分片,必须先使数据库支持分片:

  1. sh.enableSharding('cms')

对于已有的集合如果要分片,那么选定片键后,需要在片键上面首先创建索引,如果集合初始状态为空,则自动创建索引查看rs.status()可以看到,如下信息片段:

  1. databases:
  2. { "_id" : "cms", "primary" : "rs0", "partitioned" : true }
  3. { "_id" : "test", "primary" : "rs0", "partitioned" : false }

证明 cms已经支持分片

在已存在的集合中分片先创建索引:

  1. db.user.ensureIndex({city:1})
  2. sh.shardCollection("cms.users",{city:1})

注意:由于我本地是单台PC,做分片后看不到效果,但如果有两个分片,分别为rs0,与rs1,往users中插入数据,最后的结果会是

MongoDB分片集群 - 图4

通过

  1. db.changelog.find()

可以看到具体的块分割过程,当存储的chunk小于64MB时,不分割,当大于64MB时,开始分割成两部分,-∞~beijing,beijing~∞,随着数据的进一步增大,beijing~∞被继续分割成beijing~guangzhou,guangzhou~∞,这样,rs0就有三个chunk了,随后,-∞到beijing的chunk开始发生转移,从rs0迁移到rs1上面去,依次类推,MongDB就是这样实现了海量数据的分布式存储的。

集群平衡器

在上一步的操作中最后讲到了块chunk从rs0迁移到rs1的操作,这个动作是由 平衡器的后台进程自动完成的。

当一个被分片的集合的所有chunk在集群中分布不均衡时(如200个chunk在rs0,50个在rs1上),平衡器就会将chunk从最多的片上迁移到最少的片上,直到chunk数量基本相等为止。平衡器会根据chunk数量的不同,有不同的规则触发块迁移,默认配置为chunk<20,阈值为2,2080,阈值为8,这也就是为什么我们在刚才要在chunk为3时才能看到迁移过程的变化。基本的chunk迁移流程如下所示:

MongoDB分片集群 - 图5

本示例中源片指的是rs0,目标片指的是rs1

集群的写、读

集群的读、写与单个MongoDB的读写是一样的,对应用程序时透明的,这里主要看分片集群对性能的影响。

片键是决定查询落到哪一个chunk上的依据,这个信息保存在配置服务器上,而索引是针对每个片上的集合来说的,每个片都会为自己的集合创建索引数据,因此,查询语句中是否包含片键与索引对查询性能影响较大。可以通过explain解释执行查看,需要注意几个点:

nscanned:查询用到索引时,索引中扫描的文档数nscannedObjects:在集合中扫描的文档数nscannedAllPlans:所有查询计划中扫描的文档总数nscanned:选定的某一种查询计划下扫描的文档总数

片键选择策略

好的片键应该具有如下特质:

  1. 分发写操作
  2. 读操作不能太过随机化(尽量局部化)
  3. 保证chunk能够一直被分割

通常我们需要由几个字段组合作为片键,如city+_id,既能够保证同一city下的文档尽量分布在同一个片上,_id则保证了chunk能够一直被分割。

在实际业务中可以考虑order表中 company_id与create_date或id共同组成片键。

注意

只有当数据量很大、读写请求很高时才适合用分片集群。

参考链接