使用MongoDB构建数据库集群
MongoDB是一个领先的非关系型数据库管理系统,也是NoSQL运动的重要成员。MongoDB不是使用关系数据库管理系统(RDBMS)的表和固定模式,而是在文档集合中使用键值存储。它还支持许多在大型生产环境中进行水平扩展的选项。在本指南中,我们将解释如何为高可用性分布式数据集设置分片集群。
数据扩展策略有两大类。垂直扩展涉及向服务器添加更多资源,以便它可以处理更大的数据集。好处是该过程通常与迁移数据库一样简单,但通常涉及停机时间并且难以自动化。水平扩展涉及添加更多服务器以增加资源,并且通常在使用快速增长的动态数据集的配置中首选。因为它基于添加更多服务器而不是在一台服务器上增加更多资源的概念,所以数据集通常需要分解为多个部分并分布在服务器上。分片是指将数据分解为子集,以便将其存储在单独的数据库服务器(分片群集)中。
本指南中的命令和文件路径基于Ubuntu 16.04(Xenial)中使用的命令和文件路径。但是,对于运行MongoDB 3.2的任何系统,配置都是相同的。例如,要将本指南与运行CentOS 7的Linode配合使用,只需相应地调整特定于发行版的命令和配置文件即可。
在你开始之前
- 要按照本指南进行操作,您至少需要六个Linode。它们的功能将在下一节中解释。按照我们的指南在您要在群集中使用的每个Linode上安装MongoDB。
- 熟悉我们的入门指南,并完成在每个Linode上设置主机名和时区的步骤。我们建议选择与群集中每个Linode角色相对应的主机名,如下一节所述。
- 完成“ 保护您的服务器 ”部分以创建标准用户帐户,加强SSH访问并为每个Linode删除不必要的网络服务。
- 更新您的系统:
sudo apt-get update && sudo apt-get upgrade
注意本指南是为非root用户编写的。需要提升权限的命令带有前缀
sudo
。如果您不熟悉该sudo
命令,请参阅“ 用户和组”指南。
集群架构
在开始之前,让我们回顾一下我们将要创建的设置的组件:
- 配置服务器 - 存储群集其余部分的元数据和配置设置。在本指南中,为简单起见,我们将使用一个配置服务器,但在生产环境中,这应该是至少三个Linode的副本集。
-
查询路由器 -
mongos
守护程序充当客户端应用程序和集群分片之间的接口。由于数据分布在多个服务器之间,因此需要将查询路由到存储给定信息的分片。查询路由器在应用程序服务器上运行。在本指南中,我们将只使用一个查询路由器,尽管您应该在群集中的每个应用程序服务器上放置一个。 - 分片- 分片只是一个包含部分数据的数据库服务器。数据库中的项目通过范围或散列分割为分片,我们将在本指南的后面部分进行说明。为简单起见,我们将在示例中使用两个单服务器分片。
此配置中的问题是,如果其中一个分片服务器遇到停机,则部分数据将变为不可用。为避免这种情况,您可以为每个分片使用副本集以确保高可用性。有关更多信息,请参阅有关创建MongoDB副本集的指南。
配置主机文件
如果您的Linode都位于同一个数据中心,我们建议为每个Linode 添加一个私有IP地址,并使用这些地址来避免通过公共互联网传输数据。如果您不使用私有IP地址,请务必使用SSL / TLS加密数据。
在群集中的每个Linode上,将以下内容添加到/etc/hosts
文件中:
/etc/hosts文件
1 2 3 4 5 6 |
192.0.2.1 mongo-config-1 192.0.2.2 mongo-config-2 192.0.2.3 mongo-config-3 192.0.2.4 mongo-query-router 192.0.2.5 mongo-shard-1 192.0.2.6 mongo-shard-2 |
---|
将上面的IP地址替换为每个Linode的IP地址。还要在群集中使用Linodes的主机名替换上面的主机名。
注意您还可以为每个主机配置DNS记录,而不是使用主机文件条目。但请注意,公共DNS服务器(例如在DNS管理器中配置记录时使用的服务器)仅支持公共IP地址。
设置MongoDB身份验证
在本节中,您将创建一个密钥文件,用于保护副本集成员之间的身份验证。虽然在此示例中您将使用生成的密钥文件openssl
,但MongoDB建议使用X.509证书来保护生产系统之间的连接。
创建管理用户
- 在您打算用作配置服务器副本集的主要成员的Linode 上,登录到mongoshell:
mongo
- 连接
admin
数据库:use admin
- 创建具有
root
权限的管理用户。将“密码”替换为您选择的强密码:db.createUser({user: "mongo-admin", pwd: "password", roles:[{role: "root", db: "admin"}]})
生成密钥文件
- 输入此命令以生成密钥文件:
openssl rand -base64 756 > mongo-keyfile
生成密钥后,将其复制到副本集的每个成员。 - 应该在副本集的每个成员上执行本节中的其余步骤,以便它们都具有位于同一目录中的密钥文件,具有相同的权限。创建
/opt/mongo
存储密钥文件的目录:sudo mkdir /opt/mongo
- 假设您的密钥文件位于用户的主目录下,请将其移至
/opt/mongo
,并为其分配正确的权限:sudo mv ~/mongo-keyfile /opt/mongo sudo chmod 400 /opt/mongo/mongo-keyfile
- 更新密钥文件的所有权,以使其属于MongoDB用户。使用适当的命令进行分发:
Ubuntu / Debian:
sudo chown mongodb:mongodb /opt/mongo/mongo-keyfile
CentOS:sudo chown mongod:mongod /opt/mongo/mongo-keyfile
- 添加密钥文件后,取消注释每个Linode上
Security
的/etc/mongod.conf
文件部分,并添加以下值:security: keyFile: /opt/mongo/mongodb-keyfile
要应用更改,请重新启动mongod
:sudo systemctl restart mongod
您可以在查询路由器上跳过此步骤,因为您将在本指南的后面为其创建单独的配置文件。请注意,密钥文件身份验证会自动启用基于角色的访问控制,因此您需要创建用户并为其分配访问数据库所需的权限。
初始化配置服务器
在本节中,我们将创建一组配置服务器副本。配置服务器存储数据的状态和组织的元数据。这包括有关数据块位置的信息,这很重要,因为数据将分布在多个分片中。
我们将使用副本集来确保元数据的完整性,而不是使用单个配置服务器。这样可以在三台服务器之间进行主从(主 - 从)复制,并自动进行故障转移,这样,如果主配置服务器关闭,将选出一个新服务器并继续处理请求。
除非另有说明,否则应在每个配置服务器上单独执行以下步骤。
- 在每个配置服务器上,修改以下值/etc/mongod.conf:
1 2 |
port: 27019 bindIp: 192.0.2.1 |
---|
该bindIp
地址应与您在上一节中为hosts文件中的每个配置服务器配置的IP地址相匹配。除非您已配置SSL / TLS加密,否则这应该是专用IP地址。
- 取消注释该replication部分并在其replSetName下添加指令以为配置服务器创建副本集:
1 2 |
replication: replSetName: configReplSet |
---|
ReplSet是要配置的副本集的名称。可以修改此值,但我们建议您使用描述性名称来帮助您跟踪副本集。
- 取消注释
sharding
部分并将主机在集群中的角色配置为配置服务器:
1 2 |
sharding: clusterRole: "configsvr" |
---|
- 完成这些更改后重新启动
mongod
服务:sudo systemctl restart mongod
- 在其中一个配置服务器Linode上,通过端口27019与管理用户连接到MongoDB shell:
mongo mongo-config-1:27019 -u mongo-admin -p --authenticationDatabase admin
- 如果您使用与我们的示例不同的命名约定,请修改主机名以匹配您自己的主机名。在此示例中,我们将连接到第一个配置服务器上的mongo shell,但您可以连接到群集中的任何配置服务器,因为我们将从同一连接添加每个主机。
- 从mongoshell中,初始化副本集:
rs.initiate( { _id: "configReplSet", configsvr: true, members: [ { _id: 0, host: "mongo-config-1:27019" }, { _id: 1, host: "mongo-config-2:27019" }, { _id: 2, host: "mongo-config-3:27019" } ] } )
- 如果适用,替换您自己的主机名。您应该看到一条消息,指示操作成功:
{ "ok" : 1 }
- 请注意,MongoDB shell提示符现在已更改为configReplSet:PRIMARY>或configReplSet:SECONDARY>,具体取决于您用于运行先前命令的Linode。要进一步验证是否已将每个主机添加到副本集:
rs.status()
如果已正确配置副本集,您将看到类似于以下内容的输出:
configReplSet:SECONDARY> rs.status()
{
"set" : "configReplSet",
"date" : ISODate("2016-11-22T14:11:18.382Z"),
"myState" : 1,
"term" : NumberLong(1),
"configsvr" : true,
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "mongo-config-1:27019",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 272,
"optime" : {
"ts" : Timestamp(1479823872, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-11-22T14:11:12Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1479823871, 1),
"electionDate" : ISODate("2016-11-22T14:11:11Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 1,
"name" : "mongo-config-2:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 17,
"optime" : {
"ts" : Timestamp(1479823872, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-11-22T14:11:12Z"),
"lastHeartbeat" : ISODate("2016-11-22T14:11:17.758Z"),
"lastHeartbeatRecv" : ISODate("2016-11-22T14:11:14.283Z"),
"pingMs" : NumberLong(1),
"syncingTo" : "mongo-config-1:27019",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "mongo-config-3:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 17,
"optime" : {
"ts" : Timestamp(1479823872, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-11-22T14:11:12Z"),
"lastHeartbeat" : ISODate("2016-11-22T14:11:17.755Z"),
"lastHeartbeatRecv" : ISODate("2016-11-22T14:11:14.276Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "mongo-config-1:27019",
"configVersion" : 1
}
],
"ok" : 1
}
配置查询路由器
在本节中,我们将设置MongoDB查询路由器。查询路由器从配置服务器获取元数据,对其进行缓存,并使用该元数据将读取和写入查询发送到正确的分片。
此处的所有步骤都应该从您的查询路由器Linode执行(这将与您的应用程序服务器相同)。由于我们只配置一个查询路由器,因此我们只需要执行一次。但是,也可以使用副本集查询路由器。如果您使用多个(即,在高可用性设置中),请在每个查询路由器Linode上执行这些步骤。
- 创建一个名为的新配置文件
/etc/mongos.conf
,并提供以下值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# where to write logging data. systemLog: destination: file logAppend: true path: /var/log/mongodb/mongos.log # network interfaces net: port: 27017 bindIp: 192.0.2.4 security: keyFile: /opt/mongo/mongodb-keyfile sharding: configDB: configReplSet/mongo-config-1:27019,mongo-config-2:27019,mongo-config-3:27019 |
---|
替换192.0.2.4
路由器Linode的专用IP地址,并保存文件。
- 使用以下信息为
mongos
被调用创建新的systemd单元文件/lib/systemd/system
/lib/systemd/system/mongos.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[Unit] Description=Mongo Cluster Router After=network.target [Service] User=mongodb Group=mongodb ExecStart=/usr/bin/mongos --config /etc/mongos.conf # file size LimitFSIZE=infinity # cpu time LimitCPU=infinity # virtual memory size LimitAS=infinity # open files LimitNOFILE=64000 # processes/threads LimitNPROC=64000 # total threads (user+kernel) TasksMax=infinity TasksAccounting=false [Install] WantedBy=multi-user.target |
---|
请注意,上面的示例使用mongodbMongoDB在Ubuntu和Debian上默认运行的用户。如果您使用的是CentOS,请Service在文件部分下面替换以下值:
[Service] User=mongod Group=mongod
- 该mongos服务需要获得与冲突的数据锁mongod,所以一定要mongod在继续之前停止:
sudo systemctl stop mongod
- 启用
mongos.service
以便在重新启动时自动启动,然后初始化mongos
:sudo systemctl enable mongos.service sudo systemctl start mongos
- 确认
mongos
正在运行:systemctl status mongos
您应该看到与此类似的输出:
Loaded: loaded (/lib/systemd/system/mongos.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2017-01-31 19:43:05 UTC; 10s ago Main PID: 3901 (mongos)
CGroup: /system.slice/mongos.service
└─3901 /usr/bin/mongos --config /etc/mongos.conf
将分片添加到群集
现在查询路由器能够与配置服务器通信,我们必须启用分片,以便查询路由器知道哪些服务器将托管分布式数据以及任何给定数据所在的位置。
- 登录每个分片服务器并更改MongoDB配置文件中的以下行:
bindIp: 192.0.2.5
- 此行中的IP地址应更改为与hosts文件中的IP地址对应的地址(因为在我们的设置中将进行地址解析)。例如,如果您使用专用IP地址将分片连接到查询路由器,请使用您的专用IP地址。如果您已配置SSL / TLS加密并计划使用公共IP地址,请使用这些加密。
- 从您的一个分片服务器,连接到我们在上面配置的查询路由器:
mongo mongo-query-router:27017 -u mongo-admin -p --authenticationDatabase admin
如果查询路由器具有不同的主机名,请在命令中替换它。 - 从mongos界面中,单独添加每个分片:
sh.addShard( "mongo-shard-1:27017" )
sh.addShard( "mongo-shard-2:27017" )
这些步骤都可以通过单一mongos连接完成; 您不需要单独登录每个分片并建立连接以添加新分片。如果您使用的是两个以上的分片,则可以使用此格式添加更多分片。如果合适,请务必在上面的命令中修改主机名。 - (可选)如果为每个分片而不是单个服务器配置了副本集,则可以在此阶段使用类似命令添加它们:
sh.addShard( "rs0/mongo-repl-1:27017,mongo-repl-2:27017,mongo-repl-3:27017" )
在此格式中,rs0
是第一个分片的副本集mongo-repl-1
的名称,是分片中第一个主机的名称(使用端口27017
),依此类推。您需要为每个单独的副本集分别运行上述命令。 注意在将副本集添加为分片之前,必须先自行配置副本集。
配置分片
在此阶段,群集的组件都已连接并相互通信。最后一步是启用分片。由于MongoDB中的数据组织,启用分片会分阶段进行。要了解数据的分发方式,让我们简要回顾一下主要的数据结构:
- 数据库 - MongoDB中最广泛的数据结构,用于保存相关数据组。
- 集合 - 类似于传统关系数据库系统中的表,集合是包含数据库的数据结构
- 文档 - MongoDB中最基本的数据存储单元。文档使用JSON格式使用可由应用程序查询的键值对来存储数据
在数据库级别启用分片
首先,我们将在数据库级别启用分片,这意味着给定数据库中的集合可以分布在不同的分片中。
- 访问
mongos
查询路由器上的shell。这可以从群集中的任何服务器完成:mongo mongo-query-router:27017 -u mongo-admin -p --authenticationDatabase admin
如果适用,请替换您自己的查询路由器的主机名。 - 从
mongos
shell中,创建一个新数据库。我们称之为我们的exampleDB
:use exampleDB
- 在新数据库上启用分片:
sh.enableSharding("exampleDB")
- 要验证分片是否成功,请先切换到
config
数据库:use config
接下来,在您的数据库上运行一个方法find()
:db.databases.find()
这将返回所有数据库的列表,其中包含有关它们的一些信息。在我们的例子中,我们刚创建的数据库exampleDB
应该只有一个条目:
{ "_id" : "exampleDB", "primary" : "shard0001", "partitioned" : true }
分片策略
在为集合启用分片之前,我们需要决定分片策略。当数据在分片之间分配时,MongoDB需要一种方法对其进行排序并知道哪些数据在哪个分片上。为此,它使用分片键,mongos
查询路由器使用的文档中的指定字段知道给定数据的存储位置。两种最常见的分片策略是基于范围和基于散列的。
基于范围的分片根据分片键中的特定值范围划分数据。例如,您可能拥有一组客户和相关地址。如果使用基于范围的分片,则邮政编码可能是分片键的不错选择。这将在同一个分片上分配指定范围的邮政编码中的客户。例如,如果您的应用程序在计划交付时运行查询以查找彼此附近的客户,则这可能是一个很好的策略。这样做的缺点是,如果您的客户在地理位置上分布不均匀,您的数据存储可能过于依赖一个分片,因此在选择分片键之前仔细分析数据非常重要。另一个需要考虑的重要因素是,您将运行哪种查询。当应用程序将执行许多复杂的读查询时, 在适当使用时, 基于范围的分片通常是更好的选择。
基于散列的分片通过在分片键上使用散列函数来分配数据,以便在分片之间更均匀地分配数据。再次假设您拥有一组客户和地址。在基于散列的分片设置中,您可以选择客户ID号,例如,作为分片键。此数字由散列函数转换,散列的结果决定了数据存储在哪个分片上。在应用程序主要执行写入操作的情况下,或者如果您的应用程序只需要运行简单的读取查询(例如一次仅查找少数特定客户),基于散列的分片是一种很好的策略。
这不是一个选择分片策略的综合指南。在为生产群集做出此决策之前,请务必分析您的数据集,计算资源以及应用程序将运行的查询。有关更多信息,请参阅MongoDB关于分片的文档。
在集合级别启用分片
现在数据库可用于分片并且我们选择了策略,我们需要在集合级别启用分片。这允许集合中的文档在分片中分发。为简单起见,我们将使用基于散列的分片策略。
注意并不总是需要对数据库中的每个集合进行分片。根据每个集合包含的数据,将某些集合存储在一个位置可能更有效,因为对单个分片的数据库查询更快。在对集合进行分片之前,请仔细分析其预期内容以及应用程序将使用的方式。
- 如果您还没有,请连接到查询路由器上的
mongo shell
:mongo mongo-query-router:27017 -u mongo-admin -p --authenticationDatabase admin
- 切换到我们之前创建的数据库
exampleDB
:use exampleDB
- 创建一个名为的新集合
exampleCollection
并哈希其_id
密钥。该_id
键已经被默认创建为新文档的基本指标:db.exampleCollection.ensureIndex( { _id : "hashed" } )
- 最后,shard集合:
sh.shardCollection( "exampleDB.exampleCollection", { "_id" : "hashed" } )
这样可以在“ 添加碎片到群集”部分中添加到群集中的所有分片中进行分片。
测试您的群集
此部分是可选的。为了确保您在上面配置的示例数据库和集合中均匀分布数据,您可以按照以下步骤生成一些基本测试数据,并查看它在分片之间的划分方式。
- 如果您还没有,请连接到查询路由器上的
mongo shell
:mongo mongo-query-router:27017 -u mongo-admin -p --authenticationDatabase admin
- 切换到您的数据库
exampleDB
:use exampleDB
- 在mongoshell中运行以下代码以生成500个简单文档并将其插入exampleCollection:
for (var i = 1; i <= 500; i++)
db.exampleCollection.insert( { x : i } )
- 检查您的数据分布: db.exampleCollection.getShardDistribution() 这将输出类似于以下内容的信息:
Shard shard0000 at mongo-shard-1:27017
data : 8KiB docs : 265 chunks : 2
estimated data per chunk : 4KiB
estimated docs per chunk : 132
Shard shard0001 at mongo-shard-2:27017
data : 7KiB docs : 235 chunks : 2
estimated data per chunk : 3KiB
estimated docs per chunk : 117
Totals
data : 16KiB docs : 500 chunks : 4
Shard shard0000 contains 53% data, 53% docs in cluster, avg obj size on shard : 33B
Shard shard0001 contains 47% data, 47% docs in cluster, avg obj size on shard : 33B
以...开头的部分Shard提供有关群集中每个分片的信息。由于我们只添加了两个分片,因此只有两个分区,但如果您向群集添加更多分片,它们也会显示在此处。该Totals部分提供有关整个集合的信息,包括其在分片中的分布。请注意,分布并不完全相同。散列函数不保证绝对均匀分布,但是通过精心选择的分片键,它通常会非常接近。
5. 完成后,删除测试数据:
db.dropDatabase()
下一步
在生产环境中使用群集之前,配置防火墙以限制端口27017和27019仅接受群集中主机之间的流量非常重要。根据您正在运行的其他服务,可能需要额外的防火墙配置。欲了解更多信息,请咨询我们的防火墙指南。
您可能还希望创建一个主磁盘映像,其中包含完整的MongoDB安装以及应用程序所需的任何配置设置。通过这样做,您可以使用Linode Manager在数据存储需求增长时动态扩展群集。如果您想自动执行该过程,也可以从Linode CLI执行此操作。有关更多信息,请参阅我们的Linode图像指南。
更多信息
有关此主题的其他信息,您可能需要参考以下资源。虽然提供这些是希望它们有用,但请注意,我们无法保证外部托管材料的准确性或及时性。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 文件操作API
- Kotlin 扩展函数和扩展属性的使用方法
- PHP安全我见
- 使用Android WebSocket实现即时通讯功能
- 虚拟机设置桥接上网(下)
- WebSth 指纹识别插件简要分析
- Android实现3D层叠式卡片图片展示
- Android Studio屏幕方向以及UI界面状态的保存代码详解
- webshell,禁止你执行
- android实现下拉菜单三级联动
- Flutter 假异步的实现示例
- 详解Android4.4 RIL短信接收流程分析
- android 使用kotlin 实现点击更换全局语言(中日英切换)
- EasySec基于XP的渗透平台
- Android集成腾讯X5实现文档浏览功能