简介
MongoDB 是一个 NoSQL 文档数据库系统,可以在水平方向上很好地扩展,并通过键值系统实现数据存储。作为 Web 应用程序和网站的热门选择,MongoDB 易于实现并可以通过编程方式访问。
MongoDB 通过一种称为“分片”的技术实现扩展。分片是将数据写入不同的服务器以分发读取和写入负载以及数据存储需求的过程。
在之前的教程中,我们介绍了如何在 Ubuntu 12.04 VPS 上安装 MongoDB。我们将以此为起点,讨论如何在多个不同节点上实现分片。
MongoDB 分片拓扑结构
分片通过三个单独的组件实现。每个部分执行特定的功能:
- 配置服务器:每个生产分片实现必须包含确切的三个配置服务器。这是为了确保冗余和高可用性。
配置服务器用于存储将请求的数据与包含该数据的分片相关联的元数据。它组织数据,以便可以可靠和一致地检索信息。 - 查询路由器:查询路由器是您的应用程序实际连接的机器。这些机器负责与配置服务器通信,以确定请求的数据存储在何处。然后从适当的分片中访问和返回数据。
每个查询路由器运行“mongos”命令。 - 分片服务器:分片负责实际的数据存储操作。在生产环境中,一个单独的分片通常由一个副本集而不是单个机器组成。这是为了确保在主分片服务器下线时数据仍然可以访问。
实现副本集超出了本教程的范围,因此我们将配置我们的分片为单个机器而不是副本集。如果您希望为自己的配置配置副本集,可以轻松修改此设置。
初始设置
如果您在上面注意到,您可能已经注意到此配置需要相当多的机器。在本教程中,我们将配置一个示例分片集群,其中包含:
- 3 个配置服务器(生产环境中必需)
- 2 个查询路由器(至少需要 1 个)
- 4 个分片服务器(至少需要 2 个)
这意味着您需要九个 VPS 实例才能完全跟随。实际上,一些功能可以重叠(例如,您可以在用作配置服务器的同一 VPS 上运行查询路由器),并且您只需要一个查询路由器和至少 2 个分片服务器。
我们将超过此最小值,以演示每种类型的组件的多个组件。为了清晰和简单起见,我们还将把所有这些组件视为独立的机器。
设置初始基础镜像
要开始,使用此指南在 Ubuntu 上安装和配置初始 MongoDB 服务器。我们将使用此服务器来引导我们的其他分片组件。
完成第一个服务器的教程后,使用以下命令关闭实例:
sudo shutdown -h now
现在,我们将对此配置的 droplet 进行快照,并使用它来启动我们的其他 VPS 实例。虽然可能对运行中的系统进行快照,但关闭可以确保文件系统处于一致状态。快照费用为每月每 GB 0.05 美元,基于文件系统中使用的空间量,因此最好在完成后删除快照。
在您的 DigitalOcean 控制面板中,选择 droplet。单击“快照”选项卡。输入快照名称,然后单击“拍摄快照”:
!DigitalOcean take snapshot
将拍摄快照,并重新启动初始服务器。
基于快照启动 VPS 实例
现在我们通过快照过程保存了一个镜像,我们可以将其用作其他 MongoDB 组件的基础。
从控制面板中,单击“创建”按钮。输入描述 droplet 在分片配置中将具有的目的的名称:
!DigitalOcean name droplet
选择 droplet 大小和区域。最好为所有组件选择相同的区域。
在“选择镜像”部分,单击“我的镜像”选项卡,然后选择刚刚创建的 MongoDB 快照。
!DigitalOcean select image
添加所需的任何 SSH 密钥,并选择要使用的设置。单击“创建 Droplet”以启动新的 VPS 实例。
对每个分片组件重复此步骤。请记住,要完全按照本教程进行(不是必需的,但具有示范性),您需要 3 个配置服务器,2 个查询服务器和 4 个分片服务器。
为每个组件配置 DNS 子域条目(可选)
MongoDB 文档建议您通过可解析的 DNS 名称引用所有组件,而不是通过特定的 IP 地址。这很重要,因为它允许您更改服务器或重新部署某些组件,而无需重新启动与之关联的每个服务器。
为了方便起见,我建议您为希望使用的域名上的每个服务器都分配一个子域。您可以使用此指南学习如何使用 DigitalOcean 的控制面板设置 DNS 子域。
在本教程中,我们将把组件称为可以在以下子域访问:
- 配置服务器
- config0.example.com
- config1.example.com
- config2.example.com
- 查询路由器
- query0.example.com
- query1.example.com
- 分片服务器
- shard0.example.com
- shard1.example.com
- shard2.example.com
- shard3.example.com
如果您不设置子域,您仍然可以跟随操作,但您的配置将不会那么健壮。如果您选择这种方式,请简单地用您的 droplet IP 地址替换子域规范。
初始化配置服务器
首先必须设置的组件是配置服务器。在配置查询路由器或分片之前,这些服务器必须在线并运行。
以 root 用户登录到您的第一个配置服务器。
我们需要做的第一件事是创建一个数据目录,这是配置服务器将存储关联位置和内容的元数据的地方:
mkdir /mongo-metadata
现在,我们只需使用适当的参数启动配置服务器。提供配置服务器的服务称为 mongod
。此组件的默认端口号为 27019
。
我们可以使用以下命令启动配置服务器:
mongod --configsvr --dbpath /mongo-metadata --port 27019
服务器将开始输出信息,并开始监听来自其他组件的连接。
在其他两个配置服务器上重复此过程。端口号应在所有三个服务器上保持一致。
配置查询路由器实例
此时,您应该已经有了所有三个配置服务器正在运行并监听连接。在继续之前,它们必须是可操作的。
以 root 用户登录到您的第一个查询路由器。
我们需要做的第一件事是停止此实例上的 mongodb
进程(如果已经运行)。查询路由器使用与主 MongoDB 进程冲突的数据锁:
service mongodb stop
接下来,我们需要使用特定的配置字符串启动查询路由器服务。配置字符串必须对每个配置的查询路由器完全相同(包括参数的顺序)。它由每个配置服务器的地址和它正在运行的端口号组成,用逗号分隔。
查询路由器服务称为 mongos
。此进程的默认端口号为 27017
(但配置中的端口号指的是配置服务器的端口号,默认为 27019
)。
最终结果是查询路由器服务以以下形式启动:
mongos --configdb config0.example.com:27019,config1.example.com:27019,config2.example.com:27019
您的第一个查询路由器应开始连接到三个配置服务器。在其他查询路由器上重复这些步骤。请记住,在输入命令之前必须停止 mongodb
服务。
还要记住,必须使用完全相同的命令来启动每个查询路由器。否则将导致错误。
将分片添加到集群
现在我们已经配置了配置服务器和查询路由器,我们可以开始向集群添加实际的分片服务器。这些分片将分别保存总数据的一部分。
以 root 用户登录到您的一个分片服务器。
正如我们在开头提到的,在本指南中,我们将只使用单机分片,而不是副本集。这是为了简洁和演示的简单性。在生产环境中,强烈建议使用副本集,以确保数据的完整性和可用性。要在 MongoDB 中配置副本集,请参考此指南。
要实际将分片添加到集群,我们需要通过现在配置为与集群进行交互的查询路由器进行操作。我们可以通过连接到任何查询路由器来实现这一点,如下所示:
mongo --host query0.example.com --port 27017
这将连接到适当的查询路由器并打开一个 mongo 提示符。我们将从此提示符中添加所有分片服务器。
要添加第一个分片,请键入:
sh.addShard( "shard0.example.com:27017" )
然后在此界面中添加剩余的分片 droplet。您不需要单独登录到每个分片服务器。
sh.addShard( "shard1.example.com:27017" )
sh.addShard( "shard2.example.com:27017" )
sh.addShard( "shard3.example.com:27017" )
如果您正在配置生产集群,并配备有副本集,您必须指定副本集名称和副本集成员,以将每个集合设置为独立的分片。语法看起来会像这样:
sh.addShard( "rep_set_name/rep_set_member:27017" )
如何为数据库集合启用分片
MongoDB 将信息组织成数据库。在每个数据库内,数据通过“集合”进一步分隔。集合类似于传统关系数据库模型中的表。
在本节中,我们将再次使用查询路由器进行操作。如果您仍未连接到查询路由器,可以使用与上一节中相同的 mongo 命令再次访问它:
mongo --host <span class="highlight">config0.example.com</span> --port 27017
在数据库级别启用分片
我们将首先在数据库级别启用分片。为此,我们将创建一个名为(恰当地)test_db
的测试数据库。
要创建此数据库,我们只需要切换到它。当我们首次输入数据时,它将被标记为我们当前的数据库并动态创建:
use test_db
我们可以通过输入以下命令来检查我们当前使用的数据库是否刚刚创建的数据库:
db
test_db
我们可以通过输入以下命令来查看所有可用的数据库:
show dbs
您可能会注意到我们刚刚创建的数据库没有显示出来。这是因为它还没有数据,所以它还不够真实。
我们可以通过发出以下命令在此数据库上启用分片:
sh.enableSharding("test_db")
同样,如果我们输入 show dbs
命令,我们将看不到我们的新数据库。但是,如果我们切换到自动生成的 config
数据库并发出 find()
命令,我们的新数据库将被返回:
use config db.databases.find()
{ "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test_db", "partitioned" : true, "primary" : "shard0003" }
当 MongoDB 向新数据库添加了一些数据后,您的数据库将显示在 show dbs
命令中。
在集合级别启用分片
现在,我们的数据库被标记为可用于分片,我们可以在特定集合上启用分片。
此时,我们需要决定分片策略。分片通过根据指定为文档中的“分片键”的特定字段将数据组织成不同的类别来工作。它将具有匹配的分片键的所有文档放在同一个分片上。
例如,如果您的数据库存储公司的员工,并且您的分片键基于最喜欢的颜色,MongoDB 将把所有最喜欢颜色字段中为 blue
的员工放在一个分片上。这可能会导致不均匀的存储,如果每个人都喜欢几种颜色。
更好的选择是选择一个保证更均匀分布的分片键。例如,在一个大公司中,生日(月和日)字段可能会相当均匀分布。
在您不确定事物将如何分布或没有合适的字段的情况下,您可以基于现有字段创建一个“散列”分片键。这就是我们将为我们的数据所做的。
我们可以创建一个名为 test_collection
的集合,并对其 “_id” 字段进行散列。确保我们正在使用我们的 test_db
数据库,然后发出以下命令:
use test_db db.test_collection.ensureIndex( { _id : "hashed" } )
然后,我们可以通过发出以下命令对集合进行分片:
sh.shardCollection("test_db.test_collection", { "_id": "hashed" } )
这将在所有可用的分片之间对集合进行分片。
向集合插入测试数据
我们可以使用循环创建一些对象来查看我们的分片是否起作用。这个循环直接来自 MongoDB 网站用于生成测试数据。
我们可以使用以下简单的循环将数据插入集合中:
use test_db for (var i = 1; i <= 500; i++) db.test_collection.insert( { x : i } )
这将创建 500 个简单的文档(只有一个 ID 字段和一个包含数字的 “x” 字段)并将它们分布在不同的分片之间。您可以通过输入以下命令来查看结果:
db.test_collection.find()
{ "_id" : ObjectId("529d082c488a806798cc30d3"), "x" : 6 } { "_id" : ObjectId("529d082c488a806798cc30d0"), "x" : 3 } { "_id" : ObjectId("529d082c488a806798cc30d2"), "x" : 5 } { "_id" : ObjectId("529d082c488a806798cc30ce"), "x" : 1 } { "_id" : ObjectId("529d082c488a806798cc30d6"), "x" : 9 } { "_id" : ObjectId("529d082c488a806798cc30d1"), "x" : 4 } { "_id" : ObjectId("529d082c488a806798cc30d8"), "x" : 11 } . . .
要获取更多值,请输入:
it
{ "_id" : ObjectId("529d082c488a806798cc30cf"), "x" : 2 } { "_id" : ObjectId("529d082c488a806798cc30dd"), "x" : 16 } { "_id" : ObjectId("529d082c488a806798cc30d4"), "x" : 7 } { "_id" : ObjectId("529d082c488a806798cc30da"), "x" : 13 } { "_id" : ObjectId("529d082c488a806798cc30d5"), "x" : 8 } { "_id" : ObjectId("529d082c488a806798cc30de"), "x" : 17 } { "_id" : ObjectId("529d082c488a806798cc30db"), "x" : 14 } { "_id" : ObjectId("529d082c488a806798cc30e1"), "x" : 20 } . . .
要获取有关特定分片的信息,您可以输入:
sh.status()
--- Sharding Status --- sharding version: { "_id" : 1, "version" : 3, "minCompatibleVersion" : 3, "currentVersion" : 4, "clusterId" : ObjectId("529cae0691365bef9308cd75") } shards: { "_id" : "shard0000", "host" : "162.243.243.156:27017" } { "_id" : "shard0001", "host" : "162.243.243.155:27017" } . . .
这将提供有关 MongoDB 在分片之间分布的块的信息。
结论
通过本指南的学习,您应该能够实现自己的 MongoDB 分片配置。服务器的具体配置和您为每个集合选择的分片键将对集群的性能产生重大影响。
选择具有最佳分布特性并且最能代表将在数据库查询中反映的逻辑分组的字段。如果 MongoDB 只需访问单个分片来检索数据,它将返回得更快。