如何在 Ubuntu 20.04 上配置 MongoDB 副本集

本教程的早期版本由Justin Ellingwood编写

介绍

MongoDB,也称为Mongo,是许多现代 Web 应用程序中使用的面向文档的数据库。它被归类为NoSQL 数据库,因为它不依赖于传统的基于表的关系数据库结构。相反,它使用具有动态模式的类 JSON 文档。这意味着,与关系数据库不同,MongoDB 在向数据库添加数据之前不需要预定义的模式。

使用数据库时,拥有多个数据副本通常很有用。这在其中一个数据库服务器发生故障时提供了冗余,并且可以提高数据库的可用性和可扩展性,以及减少读取延迟。跨多个独立数据库同步数据的做法称为复制在 MongoDB 中,一组通过复制维护相同数据集的服务器称为副本集

本教程简要概述了 MongoDB 中的复制工作原理,并概述了如何配置和启动具有三个成员的副本集。在此示例配置中,副本集的每个成员将是在单独的 Ubuntu 20.04 服务器上运行的不同 MongoDB 实例。

注意:请注意,本指南中概述的过程旨在演示如何快速设置和运行副本。完成本教程后,您将拥有一个正常运行的副本集,但它不会启用任何安全功能。不建议将此设置用于生产环境

MongoDB 的社区版本带有两种身份验证方法,可以帮助确保您的数据库安全,密钥文件身份验证x.509 身份验证对于采用复制的生产部署,MongoDB 文档建议使用 x.509 身份验证,并将密钥文件描述为“最适合测试或开发环境”的“最低限度的安全形式”。但是,获取和配置 x.509 证书的过程伴随着许多必须根据具体情况做出的警告和决定,这超出了 DigitalOcean 教程的范围。

如果您计划使用您的副本集进行测试或开发,我们强烈建议您遵循我们的教程如何在 Ubuntu 20.04 上为 MongoDB 副本集配置密钥文件身份验证

先决条件

要完成本指南,您需要:

请注意,为了清楚起见,本指南将三个服务器称为mongo0mongo1mongo2任何显示在mongo0 上执行的命令或文件更改的示例都将具有蓝色背景,如下所示:

mongo1 上执行的命令和文件更改将具有粉红色背景:

mongo2上的示例操作将具有绿色背景:

最后,必须在每台服务器上运行的命令或必须进行的文件更改将具有标准的灰色背景,如下所示:

了解 MongoDB 副本集

正如介绍中提到的,MongoDB 通过一个称为副本集的实现来处理复制属于给定副本集的每个正在运行的 MongoDB 实例都被称为它的成员之一每个副本集必须有一个主要成员和至少一个次要成员。

主成员是副本集事务的主要访问点,并且是唯一可以接受写操作的成员。每个副本集一次只能有一个主成员,因为复制是通过复制主的oplog(“操作日志”的缩写)并在辅助的各自数据集上重复记录的更改来发生的。多个primary 接受写操作会导致数据冲突。

默认情况下,应用程序将只查询主要成员的读取和写入操作。您可以将设置配置为从一个或多个辅助成员读取,但由于数据是异步传输的,因此从辅助节点读取可能会导致提供旧数据。因此,这种配置并不适合每个用例。

将 MongoDB 的副本集与其他复制实现区分开来的一项功能是它们的自动故障转移机制。如果主要成员变得不可用,则辅助节点之间会发生自动选举过程以选择新的主要成员。一个副本集最多可以有 50 个成员,但在一次选举中最多可以有 7 个成员投票。

但是,如果辅助成员池包含偶数个节点,则可能会由于投票僵局而导致无法选举新的主要成员。这将需要在副本集中包含第三种类型的成员:仲裁者仲裁者是副本集的一个可选成员,它在这种情况下进行投票以确保该集能够做出决定。但是请注意,仲裁者没有数据集的副本,并且他们被禁止成为副本集的主要副本。如果副本集只有一个次要成员,则需要一个仲裁器。

有时您可能不希望所有次要副本都遵循副本集次要成员的标准规则。MongoDB 允许您配置副本集的辅助成员以承担以下非标准角色:

  • 优先级 0 复制成员:在某些情况下,将某些集合成员选为主要位置可能会对应用程序的性能产生负面影响。例如,如果您将数据复制到远程数据中心或某个次要成员的硬件不足以作为集合的主要访问点,则将其优先级设置为0可以确保该成员不会成为主要成员但可以继续复制数据。
  • 隐藏的复制成员:某些情况要求您保持一组成员对您的客户端可访问和可见,同时隐藏具有不同目的且不应用于读取操作的后台成员。例如,您可能需要一个辅助成员作为分析工作的基础,这将受益于最新的数据集,但会给工作成员带来压力。通过将此成员设置为隐藏,它不会干扰副本集的一般操作。隐藏成员必须设置为优先级0以避免成为主要成员,但他们可以在选举中投票。
  • 延迟复制成员:通过为次要成员设置延迟选项,您可以控制次要等待执行从主要操作日志复制的每个操作的时间。如果您想防止意外删除或从破坏性操作中恢复,这将非常有用。例如,如果您将一个辅助节点延迟半天,它就不会立即对其自己的数据集执行意外操作,并可用于还原更改。延迟成员不能成为主要成员,但可以在选举中投票。在大多数情况下,它们还应该被隐藏以防止应用程序进程读取过时的数据。

步骤 1 — 配置 DNS 解析

当需要在步骤 4 中初始化副本集时,您需要提供一个地址,其中每个副本集成员都可以被副本集中的其他两个成员访问。MongoDB 文档建议在配置副本集时不要使用 IP 地址,因为 IP 地址可能会意外更改。相反,MongoDB建议在配置副本集时使用逻辑 DNS 主机名。

一种方法是为每个复制成员配置子域尽管配置子域是生产环境或其他长期解决方案的理想选择,但本教程将概述如何通过编辑每个服务器的相应hosts文件来配置 DNS 解析

hosts是一个特殊文件,允许您将人类可读的主机名分配给数字 IP 地址。这意味着,如果您的任何服务器的 IP 地址发生更改,您只需更新hosts三台服务器上的文件,而无需重新配置副本集。

在 Linux 和其他类 Unix 系统上,hosts存储在/etc/目录中。您的三台服务器中的每台服务器上,使用您喜欢的文本编辑器编辑文件。在这里,我们将使用nano

  • sudo nano /etc/hosts

在配置localhost的前几行之后,为副本集的每个成员添加一个条目。这些条目采用 IP 地址的形式,后跟您选择的可读名称,如下例所示:

/etc/hosts
IP_address   any_hostname

您可以将服务器配置为使用您喜欢的任何主机名,但让每个主机名都具有描述性会很有帮助。在本指南的示例中,三个服务器将使用以下主机名:

  • mongo0.replset.member
  • mongo1.replset.member
  • mongo2.replset.member

使用这些主机名,您的/etc/hosts文件将类似于以下突出显示的行:

/etc/hosts
. . .
127.0.0.1 localhost

203.0.113.0 mongo0.replset.member
203.0.113.1 mongo1.replset.member
203.0.113.2 mongo2.replset.member
. . .


如果您不知道服务器的 IP 地址,您可以
curl在每台服务器上运行以下命令来检索它们。icanhazip.com是一个网站,显示用于访问它的任何计算机的 IP 地址。通过提供它的 URL 作为curl命令的参数,该命令会将运行它的服务器的 IP 地址打印到标准输出:

  • curl -4 icanhazip.com

如果您使用的是 DigitalOcean Droplets,您还可以在控制面板 中找到您服务器的 IP 地址

您在此处添加的新行在您的集合中的三个主机中的每一个上都应该相同。保存并关闭每个服务器上的文件。如果您曾经nano编辑过这些文件,请按CTRL + XY、 然后ENTER

hosts每台服务器上编辑、保存和关闭文件后,您将完成为副本集配置 DNS 解析。您现在可以继续更新每台服务器的防火墙规则,以允许它们相互通信。

步骤 2 — 使用 UFW 更新每个服务器的防火墙配置

假设您遵循了必备的初始服务器设置指南,您将在安装了 MongoDB 并启用对OpenSSHUFW 配置文件的访问的每台服务器上设置防火墙这是一项重要的安全措施,因为这些防火墙目前会阻止与您服务器上任何端口的ssh连接,除了提供与每个服务器各自authorized_keys文件中的密钥一致的密钥的连接

但是,这些防火墙也会阻止每个服务器上的 MongoDB 实例相互通信,从而阻止您启动副本集。要纠正此问题,您需要添加新的防火墙规则,以允许每个服务器访问 MongoDB 正在侦听连接的其他两台服务器上的端口。

mongo0,运行以下ufw命令允许mongo1访问端口27017mongo0

  • sudo ufw allow from mongo1_server_ip to any port 27017

请务必更改mogno1_server_ip以反映您的mongo1服务器的实际 IP 地址。请注意,ufw命令不适用于hosts文件中配置的主机名,因此请务必在此命令和以下命令中使用服务器的实际 IP 地址。此外,如果您已更新此服务器上的 Mongo 实例以使用非默认端口,请务必更改27017以反映您的 MongoDB 实例实际使用的端口。

然后添加另一个防火墙规则以允许 mongo2访问同一端口:

  • sudo ufw allow from mongo2_server_ip to any port 27017

接下来,更新其他两台服务器的防火墙规则。mongo1上运行以下命令,确保分别更改 IP 地址以反映mongo0mongo2的 IP 地址

  • sudo ufw allow from mongo0_server_ip to any port 27017
  • sudo ufw allow from mongo2_server_ip to any port 27017

最后,在mongo2上运行这两个命令同样,请确保为每个服务器输入正确的 IP 地址:

  • sudo ufw allow from mongo0_server_ip to any port 27017
  • sudo ufw allow from mongo1_server_ip to any port 27017

添加这些 UFW 规则后,您的三台 MongoDB 服务器中的每台都将被允许访问其他两台服务器上 MongoDB 使用的端口。但是,您还不能对此进行测试,因为每个服务器上的 Mongo 实例当前都在阻止任何外部连接。在下一步中通过更新每个 MongoDB 实例的配置文件来启用复制后,您将能够执行此测试。

步骤 3 — 在每个服务器的 MongoDB 配置文件中启用复制

此时,您已经编辑了服务器的/etc/hosts文件以配置将解析为每个人的 IP 地址的主机名。您还打开了每台服务器的防火墙,以允许其他两台服务器访问默认的 MongoDB 端口27107. 现在您已准备好开始在每台服务器上配置 MongoDB 安装以启用复制。

此步骤概述了如何通过编辑 MongoDB 的配置文件/etc/mongod.conf. 您必须在每台服务器上完成此步骤中的每个过程,但出于演示目的,我们将在示例中使用mongo0

mongo0 上,在您首选的文本编辑器中打开 MongoDB 配置文件:

  • sudo nano /etc/mongod.conf

即使您已打开每个服务器的防火墙以允许其他服务器访问 port 27017,但 MongoDB 当前绑定到127.0.0.1本地环回网络接口。这意味着 MongoDB 只能接受源自安装它的服务器上的连接。

要允许远程连接,除了127.0.0.1. 这样,您的 MongoDB 安装将能够侦听从远程机器到您的 MongoDB 服务器的连接。

找到该network interfaces部分。默认情况下它看起来像这样:

/etc/mongod.conf
. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1
. . .

bindIp:mongo0的主机名或公共 IP 地址开头的行中附加一个逗号此示例使用在步骤 1 中配置的主机名:

/etc/mongod.conf
. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo0.replset.member
. . .

接下来,找到#replication:朝文件底部读取的行它看起来像这样:

/etc/mongod.conf
. . .
#replication:
. . . 

通过删除井号 ( #)取消注释此行然后replSetName在此行下方添加一个指令,后跟一个名称,MongoDB 将使用该名称来标识副本集:

/etc/mongod.conf
. . .
replication:
  replSetName: "rs0"
. . . 

在此示例中,replSetName指令的值为"rs0"您可以在此处提供您喜欢的任何名称,但使用描述性名称会有所帮助。但是请记住,每个服务器的mongod.conf文件在replSetName指令后必须具有相同的名称,以便它们的每个 MongoDB 实例成为同一副本集的成员。

请注意,replSetName指令前有两个空格,名称用引号 ( ")括起来,这两个都是正确读取此配置所必需的。

更新后的文件的这两个部分,net并且replication,它们看起来就像这样:

/etc/mongod.conf
. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo0.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 

保存并关闭文件。然后/etc/mongod.confmongo1mongo2文件进行相同的更改这样做之后,这些更新的部分在mongo1的配置文件中将如下所示

/etc/mongod.conf
. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo1.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 

以下是这些部分在mongo2的配置文件中的外观

/etc/mongod.conf
. . .
# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1,mongo2.replset.member
. . .
replication:
  replSetName: "rs0"
. . . 

重申一下,您添加到每个服务器bindIp指令的 IP 地址或主机名必须是mongod.conf您正在编辑文件的服务器的 IP 地址或主机名

对每个服务器的mongod.conf文件进行这些更改后,保存并关闭每个文件。然后,通过发出以下命令重新启动每台服务器上mongod服务

  • sudo systemctl restart mongod

这样,您就为每个服务器的 MongoDB 实例启用了复制。

注意:此时,您可以使用该nc命令来测试您在步骤 2 中添加的防火墙规则是否正确。ncnetcat 的缩写,是一个用于与 TCP 或 UDP 建立网络连接的实用程序。在这种情况下进行测试很有用,因为它允许您在建立连接时同时指定 IP 地址和端口号。

以下示例nc命令包含该-z选项,该选项将实用程序限制为仅扫描目标服务器上的侦听守护程序,而不向其发送任何数据。回想一下先决条件安装教程,MongoDB 作为服务守护程序运行,使此选项可用于测试连接。它还包括v增加命令详细程度选项,使其返回比其他情况更多的信息。

此示例nc命令显示了mongo0到达mongo1的尝试

  • nc -zv mongo1.replset.member 27017

以下输出表明mongo0能够在 MongoDB 使用的端口上访问mongo1

Output
Connection to mongo1.replset.member 27017 port [tcp/*] succeeded!

您可以通过在每台服务器上重复此命令并指定适当的主机名或 IP 地址来测试每对服务器之间的连接。

在编辑每个服务器的mongod.conf文件以启用复制并重新启动mongod服务后,您就可以启动副本集并将每个 Mongo 实例添加为成员。

第 4 步 – 启动副本集并添加成员

现在您已经配置了三个 MongoDB 安装中的每一个,您可以打开一个 MongoDB shell 来启动复制并将每个添加为成员。

出于演示目的,此步骤中的示例将使用mongo0的 MongoDB 实例来启动副本集。但是,您可以从mongod.conf已正确配置文件的任何服务器启动复制

mongo0 上,打开 MongoDB shell:

  • mongo

根据提示,您可以mongo通过运行该rs.initiate()方法shell启动副本集但是,单独运行此方法只会为您运行该方法的机器启动复制,然后您需要通过rs.add()为每个成员发出一个方法来添加其他 Mongo 实例

回想一下,MongoDB 将其数据存储在称为文档的类 JSON 结构中因为您已经mongod.conf在每个服务器上编辑了该文件以配置三个 Mongo 实例进行复制,所以您可以在rs.initiate方法中包含一个包含每个成员配置详细信息的文档这将允许您启动副本集并一次添加每个成员,而不必运行多个单独的方法。

为此,请rs.initiate()通过键入以下内容并按 来开始一个方法ENTER

  • rs.initiate(

rs.initiate在您输入右括号之前,Mongo 不会将该方法注册为完整。在您这样做之前,提示将从大于号 ( >) 变为省略号 ( ...)。

与 JSON 中的对象一样,MongoDB 中的文档以花括号 ( {and })开头和结尾要开始添加副本集的配置文档,请输入一个左花括号:

  • {

MongoDB 文档由任意数量的字段和值对组成,它们采用. 此特定文档的第一个字段和值对必须是提供名称以标识副本集字段;此字段的值必须与您在文件中设置指令相同,这在我们的示例中。field: value_id:replSetNamemongod.conf"rs0"

输入此字段和值对,在其后跟一个逗号,然后按ENTER开始新行:

  • _id: "rs0",

接下来,添加一个members:字段。但是,不是单个值,而是在此members:字段后面添加一个包含多个文档的数组,每个文档代表一个要添加的副本集成员。在 MongoDB 文档中,数组总是放在一对方括号 ([]) 中。

添加members:后跟左方括号字段以开始数组,然后按ENTER移至下一行:

  • members: [

现在添加一个包含两个字段和值对的文档,用逗号分隔,以表示副本集的第一个成员。本文档的第一个字段是另一个_id:字段,它接受用于在内部标识成员的整数。第二个是一个host:字段,后面必须跟一个包含主机名的字符串,该字符串将解析为可以访问成员 Mongo 实例的地址:

  • { _id: 0, host: "mongo0.replset.member" },

注意:如果您的任何 Mongo 实例在 MongoDB 的默认端口以外的端口上运行 — 27017— 您必须在主机名:后面加上冒号 ( ),然后是端口号,如下例所示:

  • { _id: 0, host: "mongo0.replset.member:27018" },

输入第一个后,为副本集的其他成员输入其他文档。确保用逗号分隔每个文档:

  • { _id: 1, host: "mongo1.replset.member" },
  • { _id: 2, host: "mongo2.replset.member" }

接下来,通过输入右方括号来结束数组:

  • ]

最后,用右花括号结束配置文件,然后用右括号关闭方法:

  • })

总之,该rs.initiate()方法将如下所示:

> rs.initiate(
... {
... _id: "rs0",
... members: [
... { _id: 0, host: "mongo0.replset.member" },
... { _id: 1, host: "mongo1.replset.member" },
... { _id: 2, host: "mongo2.replset.member" }
... ]
... })

假设您正确输入了所有详细信息,一旦您ENTER在输入右括号后按下,该方法将运行并启动副本集。如果该方法"ok" : 1在输出中返回,则表示副本集已正确启动:

Output
{ "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1612389071, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1612389071, 1) }

如果副本集按预期启动,您会注意到 MongoDB 客户端的提示将从一个大于号 ( >) 更改为以下内容:

MongoDB 安装了一些内置方法,您可以使用它们来管理和检索有关副本集的信息。其中,该rs.help()方法特别有用,因为它返回这些副本集方法的列表以及它们的作用的描述:

  • rs.help()
Output
rs.status() { replSetGetStatus : 1 } checks repl set status rs.initiate() { replSetInitiate : null } initiates set with default settings rs.initiate(cfg) { replSetInitiate : cfg } initiates set with configuration cfg rs.conf() get the current configuration object from local.system.replset rs.reconfig(cfg) updates the configuration of a running replica set with cfg (disconnects) rs.add(hostportstr) add a new member to the set with default attributes (disconnects) rs.add(membercfgobj) add a new member to the set with extra attributes (disconnects) rs.addArb(hostportstr) add a new member which is arbiterOnly:true (disconnects) rs.stepDown([stepdownSecs, catchUpSecs]) step down as primary (disconnects) rs.syncFrom(hostportstr) make a secondary sync from the given member rs.freeze(secs) make a node ineligible to become primary for the time specified rs.remove(hostportstr) remove a host from the replica set (disconnects) rs.secondaryOk() allow queries on secondary nodes rs.printReplicationInfo() check oplog size and time range rs.printSecondaryReplicationInfo() check replica set members and replication lag db.isMaster() check who is primary db.hello() check who is primary reconfiguration helpers disconnect from the database so the shell will display an error, even if the command succeeds.

运行rs.help()或其中一种方法后,您可能会看到客户端提示再次更改为以下内容:

这意味着您连接到的 MongoDB 实例被选为主要集合成员。

请注意,如果您希望将来添加其他节点到副本集,您可以rs.add()在配置它们之后使用该方法,就像您在前面的步骤中对当前副本集成员所做的那样:

  • rs.add( "mongo3.replset.member" )

您现在可以通过按CTRL + C或运行以下exit命令来关闭 MongoDB 客户端

  • exit

您的副本集现已启动并运行,您可以开始将其与您的应用程序集成。

警告:当您打开 MongoDB 提示以启动副本集时,您可能已经注意到如下警告消息:

. . .
        2021-02-03T21:45:48.379+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
. . .

此消息表明您尚未为您的数据库启用访问控制。根据 MongoDB 文档:

MongoDB 使用基于角色的访问控制 (RBAC) 来管理对 MongoDB 系统的访问。用户被授予一个或多个角色,这些角色决定用户对数据库资源和操作的访问权限。

由于尚未在您的任何 MongoDB 实例上启用访问控制,因此可以访问副本集中三台服务器中的任何一台的任何人也可以访问该服务器上的 Mongo 实例。这带来了重要的安全风险,因为这意味着他们还可以访问您的应用程序数据。

消除此警告并向副本集添加一层安全性的一种方法是配置密钥文件身份验证但是,正如在介绍中提到的,MongoDB 文档将密钥文件描述为“最适合测试或开发环境”的“最低限度的安全形式”。

请注意,对于生产部署,MongoDB 文档建议使用 x.509 证书进行内部成员身份验证。获取和配置 x.509 证书的过程伴随着许多必须根据具体情况做出的警告和决定,这超出了本教程的范围。

如果您计划使用您的副本集进行测试或开发,我们强烈建议您遵循我们的教程如何在 Ubuntu 20.04 上为 MongoDB 副本集配置密钥文件身份验证

结论

数据库复制已被广泛用作提高性能、可用​​性和数据安全性的策略,因此建议在生产环境中使用的任何数据库都启用某种形式的复制。副本也是通用的,可以在数据架构中扮演许多不同的角色,比如报告或灾难恢复。MongoDB 副本集中的自动故障转移功能使它们在帮助确保您的数据在发生中断时保持高度可用方面特别有价值。

如果您想了解有关 MongoDB 的更多信息,我们鼓励您查看我们的整个 MongoDB 教程集

觉得文章有用?

点个广告表达一下你的爱意吧 !😁