看到这里相信你已经掌握了 MongoDB 的大部分基本知识,现在在单机环境下操作 MongoDB 已经不存在问题,但是单机环境只适合学习和开发测试,在实际的生产环境中,MongoDB 基本是以集群的方式工作的。集群的工作方式能够保证在生产遇到故障时及时恢复,保障应用程序正常地运行和数据的安全。
重点介绍 MongoDB 的集群工作方式,以及在集群工作方式下,MongoDB 是如何使用分片和复制的机制来完成对数据的管理和恢复的。
MongoDB 有三种集群部署模式,分别为主从复制(Master-Slaver)、副本集(Replica Set)和分片(Sharding)模式。
在实际生产环境中,通常将 Replica Set 和 Sharding 两种技术结合使用。
虽然 MongoDB 官方建议用副本集替代主从复制,但还是让大家了解 MongoDB 的复制机制。
主从复制是 MongoDB 中最简单的数据库同步备份的集群技术,其基本的设置方式是建立一个主节点(Primary)和一个或多个从节点(Secondary),如图。
这种方式比单节点的可用性好很多,可用于备份、故障恢复、读扩展等。集群中的主从节点均运行 MongoDB 实例,完成数据的存储、查询与修改操作。
主从复制模式的集群中只能有一个主节点,主节点提供所有的增、删、查、改服务,从节点不提供任何服务,但是可以通过设置使从节点提供查询服务,这样可以减少主节点的压力。
另外,每个从节点要知道主节点的地址,主节点记录在其上的所有操作,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。
在主从复制的集群中,当主节点出现故障时,只能人工介入,指定新的主节点,从节点不会自动升级为主节点。同时,在这段时间内,该集群架构只能处于只读状态。
副本集的集群架构如下图所示。
此集群拥有一个主节点和多个从节点,这一点与主从复制模式类似,且主从节点所负责的工作也类似,但是副本集与主从复制的区别在于:当集群中主节点发生故障时,副本集可以自动投票,选举出新的主节点,并引导其余的从节点连接新的主节点,而且这个过程对应用是透明的。
可以说,MongoDB 的副本集是自带故障转移功能的主从复制。
MongoDB 副本集使用的是 N 个 mongod 节点构建的具备自动容错功能、自动恢复功能的高可用方案。在副本集中,任何节点都可作为主节点,但为了维持数据一致性,只能有一个主节点。
主节点负责数据的写入和更新,并在更新数据的同时,将操作信息写入名为 oplog 的日志文件当中。主节点还负责指定其他节点为从节点,并设置从节点数据的可读性,从而让从节点来分担集群读取数据的压力。
另外,从节点会定时轮询读取 oplog 日志,根据日志内容同步更新自身的数据,保持与主节点一致。
在一些场景中,用户还可以使用副本集来扩展读性能,客户端有能力发送读写操作给不同的服务器,也可以在不同的数据中心获取不同的副本来扩展分布式应用的能力。
在副本集中还有一个额外的仲裁节点(不需要使用专用的硬件设备),负责在主节点发生故障时,参与选举新节点作为主节点。
副本集中的各节点会通过心跳信息来检测各自的健康状况,当主节点出现故障时,多个从节点会触发一次新的选举操作,并选举其中一个作为新的主节点。为了保证选举票数不同,副本集的节点数保持为奇数。
副本集可以解决主节点发生故障导致数据丢失或不可用的问题,但遇到需要存储海量数据的情况时,副本集机制就束手无策了。副本集中的一台机器可能不足以存储数据,或者说集群不足以提供可接受的读写吞吐量。这就需要用到 MongoDB 的分片(Sharding)技术,这也是 MongoDB 的另外一种集群部署模式。
分片是指将数据拆分并分散存放在不同机器上的过程。有时也用分区来表示这个概念。将数据分散到不同的机器上,不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载。
MongoDB 支持自动分片,可以使数据库架构对应用程序不可见,简化系统管理。对应用程序而言,就如同始终在使用一个单机的 MongoDB 服务器一样。
MongoDB 的分片机制允许创建一个包含许多台机器的集群,将数据子集分散在集群中,每个分片维护着一个数据集合的子集。与副本集相比,使用集群架构可以使应用程序具有更强大的数据处理能力。
MongoDB 分片的集群模式如下图所示。
构建一个 MongoDB 的分片集群,需要三个重要的组件,分别是分片服务器(Shard Server)、配置服务器(Config Server)和路由服务器(Route Server)。
以上介绍了 MongoDB 的三种集群模式,副本集已经替代了主从复制,通过备份保证集群的可靠性,分片机制为集群提供了可扩展性,以满足海量数据的存储和分析的需求。
在实际生产环境中,副本集和分片是结合起来使用的,可满足实际应用场景中高可用性和高可扩展性的需求。
在实际生产环境中,MongoDB 的集群架构是分布式的,如下图所示,集群会结合副本集和分片机制保证生产过程的高可靠性和高可扩展性。
从上图的集群中可以看到,整个生产集群与分片集群的架构类似,由三个重要组件组成,包括 Shard Server、Config Server 和 Route Server。不同之处在于每个组件可以使用多个实例来保证集群的可靠性。
接下来通过分布式集群的部署了解 MongoDB 的副本集和分片机制。假设目前有三台机器, 操作系统为 Ubuntu 16.04,均安装了 MongoDB 3.4,信息如下表所示。在这三台机器上部署副本集和部署分片集群。
主机名 | IP | 端口信息 |
---|---|---|
Node1 | 10.90.9.101 | momgod shard1 : 27018(rs1) momgod shard2 : 27018(rs2) mongod config1 : 27030 mongos router1 : 27017 |
Node2 | 10.90.9.102 | momgod shard1 : 27018(rs1) momgod shard2 : 27018(rs2) mongod config1 : 27030 mongos router1 : 27017 |
Node3 | 10.90.9.103 | momgod shard1 : 27018(rs1) momgod shard2 : 27018(rs2) mongod config1 : 27030 mongos router1 : 27017 |
副本集和分片联合部署的基本思路是先建立副本集,然后将每个副本集作为整体建立分片,如在上表中,集群有两个副本集 rs1 和 rs2,每个副本集由三个成员组成,分别部署在三台机器 Node1、Node2 和 Node3 上。
每个副本集作为一个整体建立一个分片,因此,此集群由两个 Shard Server 组成,每个 Shard Server 由一个三成员的副本集来保证数据的容错和冗余。
另外,在每台机器上启动一个 mongod 和 mongos 实例分别用于实现 Config Server 和 Route Server 的功能,使用三台机器备份的方式保证集群的可靠性。
标准副本集一般会部署三个成员,即一个 Primary 和两个 Secondary,实现数据的冗余和容错。 以下步骤为配置表 1 集群中的副本集 rs1、rs2 的部署。
systemLog:
path: /root/mongodb/data/mongodrsl.log #副本集rs1的日志文件位置
storage:
dbPath: /root/mongodb/data/rsl #副本集rs1的数据库存储位置
net:
port: 27018 #副本集rs1 mongod进程使用的端口号。
bindIp: 10.90.9.101 #即本机地址,允许mongo客户端连接。
在 /root/mongodb 文件夹下创建 /data/rs1 目录,用来保存副本集rsl的数据库和日志文件。
mongod --shardsvr --replSet rs1 --config /etc/mongodrs1.conf
参数说明:
在 Nodel 机器上启动副本集为 rs1 的 MongoDB 实例,如下图所示。
同样地,在 Node2 和 Node3 节点上以同样的方式启动 mongod 服务,在此阶段,三台机器上的副本集成员都是 Secondary 节点,只有经过初始化才会称为 Primary 节点。
mongo --host 10.90.9.102 --port 27018
参数说明:
此命令运行后进入 mongo shell 的交互界面,如图下所示。
然后在此界面使用 rs.initiate() 对副本集进行初始化,经过初始化后,执行 rs.status。
查看副本集状态,如下图所示,初始化后的 Node2 已经作为副本集 rs1 的 Primary 节点。
rs.add( "10.90.9.101 : 27018" )
rs.add( "10.90.9.103 : 27018" )
至此副本集的启动配置已完成,通过 rs.stutas() 命令可以看到 rs1 副本集中已经有一个 Primary 节点(10.90.9.102)和两个 Secondary 节点(10.90.9.101 和 10.90.9.103)。副本集 rs2 以同样的步骤部署即可。
db.getMongo().setSlaveOk()
然后查询 Secondary 节点上的数据,查询结果如下图所示。
修改副本集属性可通过如下命令实现:
con=rs.conf()
con.members[1].priority=2
rs.reconfig(con)
首先定义对象 con,将副本集的配置信息赋给 con,之后将 con 成员列表中的第 2 个(编号从 0 开始)成员的优先级设为 2,最后以 con 为参数,使用 rs.reconfig() 函数对副本集属性进行重设。
在 MongoDB 中只能通过主节点将 Mongo 服务添加到副本集中,可以使用命令 db.isMaster() 判断当前运行的 Mongo 服务是否为主节点,其他副本集的操作可查看 rs.help 来了解。
分片集群由配置服务器、路由服务器、分片服务器和客户端组成。客户端可以是 Shell 终端,也可以是具体的应用程序。
配置服务器(Config Server)是普通的 mongod 服务器,保存着集群的配置信息:集群中有哪些分片、分片的是哪些集合,以及数据块的分布。分片服务器(Shard Server)存储具体的分片数据。启动集群后,路由服务器(Route Server)加载 Config Server 中的分片信息, 客户端通过连接 Route Server 来获取集群中的数据信息。
在表 1 的分布式集群中,有两个分片,分别由副本集 rs1、rs2 组成。集群中的 Shard Server 已经在前面配置好,接下来需要建立 Config Server 和 Route Server。
因此,在集群中,Config Server 也通常配置成副本集模式来保证数据的可靠性。由于 mongos 需从配置服务器获取配置信息,因此配置服务器应先于任何 mongos 进程启动。
配置服务器是独立的 mongod 进程,所以可以像启动“普通的”mongod 进程一样启动配置服务器:
mongod --replSet config --configsvr --dbpath /home/ubuntu/mongodb/data/config --port 27030 -logpath /home/ubuntu/mongodb/data/config.log --logappend --fork
分别在三台机器上执行以上命令来启动配置服务器,使用 replSet config 选项,表示该实例归属于名为 config 的副本集。
参数说明:
配置 config 副本集的过程请参考本节前面的内容。
使用副本集选项实现了配置信息的冗余存储。配置服务器并不需要太多的空间和资源。配置服务器的 1KB 空间约等于 200MB 真实数据,它保存的只是数据的分布表。
由于配置服务器并不需要太多的资源,因此可将其部署在运行着其他程序的机器上,如应用服务器、分片的 mongod 服务器或 mongos 进程的服务器上。
mongos --configdb config/10.90.9.101:27030,10.90.9.102:27030,10.90.9.103:27030 -logpath /home/ubuntu/mongodb/data/mongos.log --logappend --fork
在默认情况下,mongos 运行在 27017 端口。注意,并不需要指定数据目录(mongos 自身并不保存数据,它会在启动时从配置服务器加载集群数据)。确保正确设置了 logpath,以便将 mongos 日志保存到安全的地方。
可启动任意数量的 mongos 进程,通常的设置是每个应用程序服务器使用一个 mongos 进程(与应用服务器运行在同一台机器上)。每个 mongos 进程必须按照列表顺序,使用相同的配置服务器列表,如 --configdb 后面输入的是一个带有三个服务器列表的 config 的副本集的名称。
至此,分布式集群中的分片服务已经启动完毕,接下来进行分片服务器的设置和数据的分片存储。
首先执行以下命令连接 mongos:
mongo --host 10.90.9.101 --port 27017
然后进入 mongos 的 Shell 界面,执行下面两条命令,将两个副本集 rs1 和 rs2 加入分片集中:
db.runCommand ({ addshard : "rs1/10.90.9.102:27020,10.90.9.101:27020,10.90.9.103 : 27020", name : "s1", maxsize : 10240 });
db.runCommand ({ addshard : "rs2/10.90.9.102:27021,10.90.9.101:27021,10.90.9.103 : 27021", name : "s2", maxsize : 1024 });
可在参数中指定副本集的所有成员,但并非一定要这样做。mongos 能够自动检测到没有包含在副本集成员表中的成员。name 选项表示此分片的名称,maxsize 选项指定此分片的最大存储容量。
如果之后需要移除这个分片或是向这个分片迁移数据,可使用分片名称标识这个分片。这比使用特定的服务器名称要好,因为副本集成员和状态是不断改变的。
将副本集作为分片添加到集群后,就可以将应用程序设置从连接到副本集改为连接到 mongos。添加分片后,mongos 会将副本集内的所有数据库注册为分片的数据库,因此,所有查询都会被发送到新的分片上。与客户端库相同,mongos 会自动处理应用故障,将错误返回客户端。
用户也可以创建单 mongod 服务器的分片(而不是副本集分片),但不建议在生产中使用。直接在 addShard() 中指定单个 mongod 的主机名和端口,就可以将其添加为分片了 :
sh.addShard("some-server:port")
单一服务器分片默认会被命名为 shard0000、shard0001 …… 以此类推。
如打算以后切换为副本集,应先创建一个单成员副本集再添加为分片,而不是直接将单一服务器添加为分片。如需将单一服务器分片转换为副本集,则需停机进行操作。
假设希望对 myDB 数据库中的 Mytest 集合按照 _id 键进行分片。首先对 myDB 数据库执行以下命令启用分片:
sh.enableSharding("myDB")
命令执行成功后,用 sh.status() 查询分片状态,如下图所示,数据库 myDB 的 patitioned 属性值为true。
对数据库分片是对集合分片的先决条件。数据库启用分片后,就可以使用如下 shardCollection() 命令对集合进行分片了:
sh.shardCollection("myDB.myColletion", {"_id" : 1})
现在,集合会按照 _id 键进行分片。如果是对已存在的集合进行分片,则 _id 键上必须包含索引,否则 shardCollection() 会返回错误。如果出现了错误,则先创建索引,然后重试 shardCollection() 命令;如要进行分片的集合不存在,则 mongos 会自动在片键上创建索引。
shardCollection() 命令会将集合拆分为多个数据块,这是 MongoDB 迁移数据的基本单元。
成功执行分片操作后,MongoDB 会均衡地将集合数据分散到集群的分片上。这个过程不是瞬间完成的,对于比较大的集合,可能需要花费几个小时才能完成。