接下来的一些列文章会为大家介绍日常工作中常用的 NoSQL 产品 MongoDB。
主要涉及到:
MongoDB 的安装及基本使用
MongoDB 文档查询
MongoDB 复制集
MongoDB 分片集群的介绍及搭建
MongoDB 的备份及恢复
MongoDB 安全加密
MongoDB Change Stream 功能介绍及代码演示
MongoDB 其他
我们会用八篇文章近五万字来阐述 MongoDB 运维相关的日常实战事宜。
您当前看到的是系列文章的第四篇:《MongoDB 分片集群的介绍及搭建》。
一般来说,在数据库系统和计算系统中,我们有两种方法来提高其性能。第一个是简单地用更强大的服务器取代我们常规的服务器,一般我们称之为垂直扩容(或纵向扩容)。
►垂直扩容的主要缺点是它有限制:它不能无限扩大,这取决于多方面的因素。诸如:硬件已达到其物理极限、云提供商不能为我们提供更强大的服务器。
提高性能的第二种方法是使用具有相同容量的服务器并增加其数量,一般我们称之为水平扩容(或横向扩容)。

当数据量比较大的时候,我们需要把数据分片运行在不同的机器中,以降低 CPU、内存和 IO 的压力,Sharding 就是数据库分片技术。
MongoDB 分片技术类似 MySQL 的水平切分和垂直切分,数据库主要由两种方式做 Sharding:垂直扩展和横向切分。
- 横向切分:则是通过数据分片的方式,通过集群统一提供服务。
一个 MongoDB 分片集群由以下组件组成:
- shard: 每个分片都包含分片数据的一个子集。每个分片以副本集部署。
- mongos: Mongos 充当查询路由器,在客户端应用程序和分片集群之间提供接口。从 MongoDB 4.4 开始,mongos 可以支持 hedged 读取,以尽量减少延迟。
- config servers: 配置服务器存储集群的元数据和配置信息。

MongoDB 在 Collection 级别进行分片处理,在集群中的分片之间分发这些 Collection 数据。
一个生产环境的集群,请确保数据的冗余性及系统的高可用性。对于一个生产级别的分片集群,需要考虑一下几点:
- 部署一个 3 成员的复制集作为一个配置中心服务
- 每个分片部署为一个 3 成员的复制集
- 部署一个或多个
mongos
路由

环境准备
主机名 | IP | 角色 |
mongo01.tyun.cn | 10.20.20.19 | mongos1(27017),config1(27000),shard1 primary(27010) |
mongo02.tyun.cn | 10.20.20.11 | mongos2(27017),config2(27000),shard1 secondary(27010) |
mongo03.tyun.cn | 10.20.20.41 | mongos3(27017),config3(27000),shard1 secondary(27010) |
mongo04.tyun.cn | 10.20.20.14 | shard2 primary(27010) |
mongo05.tyun.cn | 10.20.20.53 | shard2 secondary(27010) |
mongo06.tyun.cn | 10.20.20.61 | shard2 secondary(27010) |
mongo07.tyun.cn | 10.20.20.62 | shard3 primary(27010) |
mongo08.tyun.cn | 10.20.20.89 | shard3 secondary(27010) |
mongo09.tyun.cn | 10.20.20.99 | shard3 secondary(27010) |
如果大家在演示该文档时,手头上的机器资源不充足的话,可以安排一台多个角色即可(使用不同的端口号),不一定非得一台机器一个角色。

这里我们使用了静态 DNS 解析,如果有条件,可以用 DNS 服务进行域名的配置解析。/etc/hosts
文件如下:
配置 Config Server
01准备配置文件
在 3 台配置节点上分别创建配置文件 /etc/mongo-cfg.conf
,内容如下:
02启动 Config Server
在 3 台配置节点上分别执行如下命令:
检查一下进程是否已经启动成功:
03初始化 Config Server
登录到第一个节点上,这时还没有创建用户及密码,所以登录时没有指定密码也是可以登录的。
除了创建用户,其实什么也不能操作。接下来的第一件事情是创建用户及密码:
接着初始化 Config Server:
需要等待 10 秒钟左右,3 个 Config Server 会通过选举产生主节点。
注意提示符变化:
至此,Config Server 配置完成。
配置 Replica Set
Replica Set 的配置请参考 Replica Set 章节。分片 1 的集群节点为:
- mongo04.tyun.cn:27010
- mongo05.tyun.cn:27010
- mongo06.tyun.cn:27010
配置 Mongos
01准备 mongos 配置文件
一个相对完整的配置文件(以 mongos1 为例):
02启动 mongos
mongos 也可以通过配置文件的形式启动:
启动命令如下:
添加 shard1 分片到分片集
增加第一个分片 shard1 到集群中:
创建分片表
接下来我们创建一个测试库 test
,然后在 test
库上创建集合 shard
,并开启分片。
我们可以看到 shard1 中有 2 chunk。
插入测试数据:
这时我们也可以登录到 shard1 复本集里面查看一下数据(找到主节点进行登录):
添加 shard2 分片到分片集
Replica Set 的配置请参考 Replica Set 章节。分片 1 的集群节点为:
- mongo07.tyun.cn:27010
- mongo08.tyun.cn:27010
- mongo09.tyun.cn:27010
shard2 复本集验证:
[root@mongo01 ~]# mongo --host mongo07.tyun.cn:27010
shard2:PRIMARY> rs.status()
{
"set" : "shard2",
"date" : ISODate("2022-08-06T03:31:26.564Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1659756685, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1659756685, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"appliedOpTime" : {
"ts" : Timestamp(1659756685, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1659756685, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastDurableWallTime" : ISODate("2022-08-06T03:31:25.927Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1659756625, 4),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2022-08-06T03:30:25.877Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1659756615, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2022-08-06T03:30:25.915Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2022-08-06T03:30:26.890Z")
},
"members" : [
{
"_id" : 0,
"name" : "mongo07.tyun.cn:27010",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 213,
"optime" : {
"ts" : Timestamp(1659756685, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-06T03:31:25Z"),
"lastAppliedWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastDurableWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1659756625, 1),
"electionDate" : ISODate("2022-08-06T03:30:25Z"),
"configVersion" : 1,
"configTerm" : -1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "mongo08.tyun.cn:27010",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 71,
"optime" : {
"ts" : Timestamp(1659756675, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1659756675, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-06T03:31:15Z"),
"optimeDurableDate" : ISODate("2022-08-06T03:31:15Z"),
"lastAppliedWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastDurableWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastHeartbeat" : ISODate("2022-08-06T03:31:25.890Z"),
"lastHeartbeatRecv" : ISODate("2022-08-06T03:31:24.933Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "mongo07.tyun.cn:27010",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : -1
},
{
"_id" : 2,
"name" : "mongo09.tyun.cn:27010",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 71,
"optime" : {
"ts" : Timestamp(1659756675, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1659756675, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-08-06T03:31:15Z"),
"optimeDurableDate" : ISODate("2022-08-06T03:31:15Z"),
"lastAppliedWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastDurableWallTime" : ISODate("2022-08-06T03:31:25.927Z"),
"lastHeartbeat" : ISODate("2022-08-06T03:31:25.890Z"),
"lastHeartbeatRecv" : ISODate("2022-08-06T03:31:24.872Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "mongo07.tyun.cn:27010",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : -1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1659756685, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1659756685, 1)
}
shard2:PRIMARY>
- 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.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
接着把 shard2 加入到分片集中(连接任意一台 mongos):
从输出可以看到,shard1 的 2 个 chunk,已经分配到了 shard2 上面了,这是 MongoDB 的自动均衡机制起作用了。
看看每个 Shard 的文档数量有多少?
从两个分片中的文档数量来看,数据存放基本是均衡的。
删除分片:
查看移动的状态:
mongos> db.adminCommand( { removeShard: "shard2" } )
{
"msg" : "draining ongoing",
"state" : "ongoing",
"remaining" : {
"chunks" : NumberLong(406),
"dbs" : NumberLong(1),
"jumboChunks" : NumberLong(0)
},
"note" : "you need to drop or movePrimary these databases",
"dbsToMove" : [
"testdb"
],
"ok" : 1,
"operationTime" : Timestamp(1660385198, 21),
"$clusterTime" : {
"clusterTime" : Timestamp(1660385198, 21),
"signature" : {
"hash" : BinData(0,"HVDmppA+MhUor9a72JKDjWErLKo="),
"keyId" : NumberLong("7128287226089177110")
}
}
}
mongos> db.adminCommand( { removeShard: "shard2" } )
{
"msg" : "draining ongoing",
"state" : "ongoing",
"remaining" : {
"chunks" : NumberLong(345),
"dbs" : NumberLong(1),
"jumboChunks" : NumberLong(0)
},
"note" : "you need to drop or movePrimary these databases",
"dbsToMove" : [
"testdb"
],
"ok" : 1,
"operationTime" : Timestamp(1660385328, 3),
"$clusterTime" : {
"clusterTime" : Timestamp(1660385328, 3),
"signature" : {
"hash" : BinData(0,"Wi6BxDNErUjsHYTdVpvbiEyGUrw="),
"keyId" : NumberLong("7128287226089177110")
}
}
}
mongos> db.adminCommand( { removeShard: "shard2" } )
{
"msg" : "draining ongoing",
"state" : "ongoing",
"remaining" : {
"chunks" : NumberLong(87),
"dbs" : NumberLong(1),
"jumboChunks" : NumberLong(0)
},
"note" : "you need to drop or movePrimary these databases",
"dbsToMove" : [
"testdb"
],
"ok" : 1,
"operationTime" : Timestamp(1660385870, 3),
"$clusterTime" : {
"clusterTime" : Timestamp(1660385870, 6),
"signature" : {
"hash" : BinData(0,"R5LJzYTNv+s+aJaiJZVZ9arr+84="),
"keyId" : NumberLong("7128287226089177110")
}
}
}
- 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.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
移动 DB 到其它分片:
总结
分片具有很大的灵活性。
不过,我们在执行某些操作时还存在一些限制。
我们将在以下列表中突出显示最重要的内容:
01
group()
命令不起作用。我们应该使用 aggregate()
和聚合框架,或者 mapreduce()
。
02
db.eval()
命令不起作用,出于安全原因,在大多数情况下应将其禁用。
03
更新操作时的 $isolated
选项不起作用。这是分片环境中缺少的功能。up
date()
的 $isolated
选项提供了保证,如果我们一次更新多个文档,其他读者和作者将看不到一些更新了新值的文档,而其他文档仍然具有旧值。这是在 unsharded
中实现的方式环境是通过持有全局写锁和/或将操作序列化到单个线程来确保对受 up
date()
影响的文档的每个请求都不会被其他线程/操作访问。此实现意味着它不是高性能的并且不支持任何并发,这禁止在分片环境中使用 $isolated
运算符。
04
不支持查询的 $snapshot
运算符。find()
游标中的 $snapshot
运算符可防止文档由于在更新后被移动到磁盘上的不同位置,在结果中出现不止一次。$snapshot
运算符的操作成本很高,通常不是硬性要求。替代它的方法是对我们查询的字段使用索引,该字段的键在查询期间不会更改。
05
如果我们的查询不包含分片键,索引将无法覆盖我们的查询。分片环境中的结果将来自磁盘,而不仅仅是来自索引。唯一的例外是如果我们只在内置的 _id
字段上查询并且只返回 _id
字段,在这种情况下,MongoDB 仍然可以使用内置索引覆盖查询。
06
up
date()
和remove
()
操作的工作方式不同。分片环境中的所有up
date()
和remove
()
操作必须包括要受影响的文档的_id 或分片键;否则,mongos 路由器将不得不对所有集合、数据库和分片进行全表扫描,这在操作上会非常耗时。
07
跨分片的唯一索引需要包含分片键作为索引的前缀。换句话说,要实现跨分片文档的唯一性,我们需要遵循 MongoDB 对分片遵循的数据分布。
08
分片键的大小不得超过 512 字节。分片键索引必须在被分片的键字段和可选的其他字段上按升序排列,或者在其上的哈希索引。
最后,如果我们使用公有云环境下的 MongoDB 分片集群,那么其费用成本是非常高昂的,一个大概的费用我们已经在系列文章的第一篇中已经提到过。下一节我们将介绍 MongoDB 的备份与恢复,这是运维同学比较关心的话题。
敬请期待,谢谢大家。
文章转载自公众号:新钛云服