Skip to content

Latest commit

 

History

History
1133 lines (746 loc) · 66.7 KB

01.md

File metadata and controls

1133 lines (746 loc) · 66.7 KB

一、安装和启动服务器

在本章中,我们将介绍以下配方:

  • 安装单节点 MongoDB
  • 使用命令行选项启动单个节点实例
  • 使用配置文件中的选项安装单节点 MongoDB
  • 使用 JavaScript 连接到 Mongo shell 中的单个节点
  • 从 Java 客户端连接到单个节点
  • 从 Python 客户端连接到单个节点
  • 作为副本集的一部分启动多个实例
  • 连接到 shell 中的副本集以查询和插入数据
  • 连接到副本集以从 Java 客户端查询和插入数据
  • 连接到副本集以使用 Python 客户端查询和插入数据
  • 启动两个碎片的简单碎片化环境
  • 连接到 shell 中的碎片并执行操作

导言

在本章中,我们将介绍如何启动 MongoDB 服务器。尽管出于开发目的,使用默认设置启动服务器是一个很简单的方法,但是有许多选项可以微调启动行为。我们将作为单个节点启动服务器,然后介绍各种配置选项。我们将通过设置一个简单的副本集并运行分片集群来结束本章。因此,为了简单的开发目的,让我们以最简单的方式开始安装和设置 MongoDB 服务器。

安装单节点 MongoDB

在本教程中,我们将介绍如何在独立模式下安装 MongoDB。这是启动 MongoDB 服务器最简单、最快捷的方法,但很少用于生产用例。但是,这是出于开发目的启动服务器的最常见方法。在这个配方中,我们将启动服务器,而不考虑其他许多启动选项。

准备好了吗

好吧,假设我们已经从下载站点下载了 MongoDB 二进制文件,提取了它,并在操作系统的 path 变量中有了生成的 bin 目录。(这不是强制性的,但这样做后确实很方便。)二进制文件可以从下载http://www.mongodb.org/downloads 选择主机操作系统后。

怎么做…

  1. 创建目录/data/mongo/db(或您选择的任何目录)。这将是我们的数据库目录,它需要有权限通过mongod(mongo 服务器进程)进程写入。

  2. 我们将使用数据目录/data/mongo/db从控制台启动服务器,如下所示:

    > mongod --dbpath  /data/mongo/db

它是如何工作的…

如果在控制台上看到以下行,则表示您已成功启动服务器:

[initandlisten] waiting for connections on port 27017

启动服务器再简单不过了。尽管启动服务器很简单,但有许多配置选项可用于在启动时调整服务器的行为。大多数默认选项都是合理的,不需要更改。使用默认值时,服务器应侦听端口27017以获取新连接,日志将打印到标准输出。

另见

有时我们希望在服务器启动时配置一些选项。在安装单节点 MongoDB配方中,我们将使用更多的启动选项。

使用命令行选项启动单节点实例

在此配方中,我们将看到如何使用一些命令行选项启动独立的单节点服务器。我们将看到一个示例,其中我们希望执行以下操作:

  • 启动服务器监听端口27000
  • 日志应写入/logs/mongo.log
  • 数据库目录为/data/mongo/db

由于服务器是为开发目的而启动的,我们不想预先分配完整大小的数据库文件。(我们很快就会明白这意味着什么。)

准备好了吗

如果您已经看到并执行了安装单节点 MongoDB的方法,那么您不需要做任何不同的事情。如果所有这些先决条件都得到满足,我们就可以使用这个配方。

怎么做…

  1. 应创建数据库的/data/mongo/db目录和日志的/logs/目录,并将其显示在文件系统上,并具有适当的写入权限。

  2. 执行以下命令:

    > mongod --port 27000 --dbpath /data/mongo/db –logpath /logs/mongo.log --smallfiles

它是如何工作的…

好的,这并不太难,与前面的方法类似,但是这次我们有一些额外的命令行选项。MongoDB 实际上在启动时支持很多选项,在我看来,我们将看到最常见和最重要的选项列表:

|

选项

|

描述

| | --- | --- | | --help-h | 用于打印各种可用启动选项的信息。 | | --config-f | 此指定包含所有配置选项的配置文件的位置。我们将在后面的食谱中看到更多关于此选项的内容。这只是在文件中而不是在命令提示符下指定配置的一种方便方法;特别是指定的选项数量较多时。使用在不同 MongoDB 实例之间共享的单独配置文件还将确保所有实例都以相同的配置运行。 | | --verbose-v | 这使得日志更加冗长;我们可以添加更多的 v,使输出更加详细,例如,-vvvvv。 | | --quiet | 这使得的输出更安静;这与 verbose 或-``v选项相反。它将使日志不那么闲聊和干净。 | | --port | 如果您希望启动服务器侦听默认的27017以外的端口,则使用此选项。每当我们希望在同一台机器上启动多个 mongo 服务器时,我们会经常使用此选项,例如,--port 27018将启动服务器,监听端口27018以获得新连接。 | | --logpath | 此提供了日志文件的路径,日志将在其中写入。该值默认为STDOUT。例如,--logpath /logs/server.out将使用/logs/server.out作为服务器的日志文件。请记住,提供的值应该是一个文件,而不是将写入日志的目录。 | | --logappend | 此选项附加到现有日志文件(如果有)。默认行为是重命名现有日志文件,然后为当前启动的 mongo 实例的日志创建一个新文件。假设我们使用的日志文件名为server.out,启动时该文件存在,则默认情况下该文件将重命名为server.out.<timestamp>,其中<timestamp>为当前时间。时间是格林尼治标准时间,而不是当地时间。假设当前日期为 2013 年 10 月 28 日,时间为 12:02:15,则生成的文件将具有以下值作为时间戳:2013-10-28T12-02-15。 | | --dbpath | 此为您提供创建新数据库或存在现有数据库的目录。该值默认为/data/db。我们将使用/data /mongo/db作为数据库目录启动服务器。请注意,该值应该是目录,而不是文件名。 | | --smallfiles | 当我们计划在本地机器上启动多个 mongo 实例时,经常用于开发目的。Mongo 在启动时创建一个大小为 64 MB 的数据库文件(在 64 位计算机上)。这种预分配是出于性能原因,创建的文件中写入了零,以填充磁盘上的空间。在启动时添加此选项只会创建 16 MB 的预分配文件(同样,在 64 位计算机上)。此选项还可以减少数据库和日志文件的最大大小。避免在生产部署中使用此选项。此外,默认情况下,文件大小加倍,最大为 2 GB。如果选择了--smallfile选项,则其最大值将达到 512 MB。 | | --replSet | 此选项用于启动作为副本集成员的服务器。此arg的值是副本集的名称,例如--replSet repl1。您将在稍后的配方中了解更多关于此选项的信息,我们将在其中启动一个简单的 mongo 副本集。 | | --configsvr | 此选项用于启动作为配置服务器的服务器。当我们在本章后面的菜谱中设置一个简单的分片环境时,配置服务器的作用将更加明确。 | | --shardsvr | 此通知已启动的 mongod 进程此服务器正在作为碎片服务器启动。通过提供此选项,服务器还侦听端口27018,而不是默认的27017。当我们启动一个简单的分片服务器时,我们将了解更多关于此选项的信息。 | | --oplogSize | Oplog 是复制的主干。它是一个有上限的集合,其中存储写入主实例的数据,以便复制到辅助实例。此集合位于名为local的数据库中。在副本集初始化时,oplog 的磁盘空间将被预先分配,数据库文件(对于本地数据库)将填充零作为占位符。默认值为磁盘空间的 5%,这对于大多数情况来说应该足够了。oplog 的大小至关重要,因为封顶集合的大小是固定的,它们会在超过其大小时丢弃其中最旧的文档,从而为新文档留出空间。如果 oplog 的大小非常小,则可能会导致在将数据复制到辅助节点之前丢弃数据。较大的 oplog 大小可能会导致不必要的磁盘空间利用率和副本集初始化的较长持续时间。出于开发目的,当我们在同一台主机上启动多个服务器进程时,我们可能希望将 oplog 大小保持在最小值,快速启动副本集,并使用最小的磁盘空间。 | | --storageEngine | 从 MongoDB 3.0 启动后,引入了一个名为 Wired Tiger 的新存储引擎。以前的(默认)存储引擎现在称为mmapv1。要使用 Wired Tiger 而不是mmapv1启动 MongoDB,请使用此选项的wiredTiger值。 | | --dirctoryperdb | 默认情况下,MongoDB 的数据库文件存储在公共目录中(如--dbpath中所述)。此选项允许您将每个数据库存储在上述数据目录中的子目录中。有了这样的粒度控制,您就可以为每个数据库拥有单独的磁盘。 |

还有更多…

有关可用选项的详细列表,请使用--help-h选项。这个选项列表并不详尽,当我们需要时,我们将在后面的食谱中看到更多选项。在下一个配方中,我们将看到如何使用配置文件而不是命令行参数。

另见

  • MongoDB 单节点安装,配置文件中的选项用于使用配置文件提供启动选项
  • 作为副本集的一部分启动多个实例启动副本集
  • 启动两个分片的简单分片环境建立分片环境

使用配置文件中的选项安装 MongoDB 的单节点

正如我们所看到的,从命令行提供选项确实有效,但一旦我们提供的选项数量增加,它就会变得很尴尬。我们有一个很好的干净的替代方案,可以从配置文件而不是作为命令行参数提供启动选项。

准备好了吗

如果您已经执行了安装单节点 MongoDB配方,则无需执行任何不同的操作,因为此配方的所有先决条件都是相同的。

怎么做…

应创建数据库的/data/mongo/db目录和日志的/logs/目录,并将其显示在文件系统上,并具有相应的写入权限,并执行以下步骤:

  1. 创建可以具有任意名称的配置文件。在我们的例子中,假设我们在/conf/mongo.conf中创建了这个。然后,我们编辑该文件并添加以下行:

    port = 27000
    dbpath = /data/mongo/db
    logpath = /logs/mongo.log
    smallfiles = true
  2. 使用以下命令启动 mongo 服务器:

    > mongod --config  /config/mongo.conf

它是如何工作的…

我们在前面的配方中讨论的所有命令行选项,使用命令行选项启动单个节点实例,都是正确的。我们只是在配置文件中提供它们。如果您没有访问过前面的配方,我建议您这样做,因为我们在这里讨论了一些常见的命令行选项。属性指定为<property name> = <value>。对于没有值的所有属性,例如smallfiles选项,给定的值是布尔值,true。如果我们需要一个详细的输出,我们将add v=true(或者多个 v 以使其更详细)添加到配置文件中。如果您已经知道命令行选项是什么,那么很容易猜测文件中属性的值。它几乎与命令行选项相同,只是删除了连字符。

使用 JavaScript 连接到 Mongo shell 中的单个节点

这个方法是关于启动 mongo shell 并连接到 MongoDB 服务器。这里我们还将演示如何在 shell 中加载 JavaScript代码。虽然这并不总是必需的,但当我们有一大块 JavaScript 代码,其中包含变量和函数,其中包含一些需要频繁从 shell 执行的业务逻辑,并且我们希望这些函数在 shell 中始终可用时,这是很方便的。

准备好了吗

虽然可以在不使用mongo --nodb连接 MongoDB 服务器的情况下运行 mongo shell,但我们很少需要这样做。要在本地主机上轻松启动服务器,请查看第一个方法,安装单节点 MongoDB,然后启动服务器。

怎么做…

  1. 首先,我们创建一个简单的 JavaScript 文件并将其命名为hello.js。在hello.js文件中键入以下正文:

    function sayHello(name) {
      print('Hello ' + name + ', how are you?')
    }
  2. 将此文件保存在位置/mongo/scripts/hello.js。(这也可以保存在任何其他位置。)

  3. 在命令提示下,执行以下命令:

    > mongo --shell /mongo/scripts/hello.js
  4. 在执行此操作时,我们应该看到以下内容打印到控制台:

    MongoDB shell version: 3.0.2
    connecting to: test
    >
  5. Test the database that the shell is connected to by typing the following command:

    > db

    这应该打印出test到控制台。

  6. 现在,在 shell 中键入以下命令:

    > sayHello('Fred')
  7. 您应该得到以下响应:

    Hello Fred, how are you?

注:本书是使用 MongoDB 版本 3.0.2 编写的。您很可能正在使用更高版本,因此在 mongo shell 中会看到不同的版本号。

它是如何工作的…

我们在这里执行的 JavaScript 函数没有实际用途,只是用来演示如何在 shell 启动时预加载函数。.js文件中可能包含多个函数,其中包含有效的 JavaScript 代码,可能包含一些复杂的业务逻辑。

在没有任何参数的情况下执行mongo命令时,我们连接到本地主机上运行的 MongoDB 服务器,并在默认端口27017上侦听新的连接。一般来说,该命令的格式如下:

mongo <options> <db address> <.js files>

如果没有参数传递给 mongo 可执行文件,则相当于将db address作为localhost:27017/test传递。

让我们看一下db address命令行选项的一些示例值及其解释:

  • mydb:这将连接到本地主机上运行的服务器,并侦听端口27017上的连接。连接的数据库将为mydb
  • mongo.server.host/mydb:连接mongo.server.host上运行的服务器和默认端口27017。连接的数据库将为mydb
  • mongo.server.host:27000/mydb:连接mongo.server.host上运行的服务器和27000端口。连接的数据库将为mydb
  • mongo.server.host:27000:连接mongo.server.host上运行的服务器和27000端口。连接的数据库将是默认的数据库测试。

现在,mongo客户端上的选项也相当少。我们将在下表中看到其中一些:

|

选项

|

描述

| | --- | --- | | --help-h | 此显示了有关使用各种命令行选项的帮助。 | | --shell | 当.js文件作为参数给出时,这些脚本将被执行,mongo客户端将退出。提供此选项可确保 shell 在 JavaScript 文件执行后保持运行。这些.js文件中定义的所有函数和变量在启动时都可以在 shell 中使用。与前一种情况一样,JavaScript 文件中定义的sayHello函数可以在 shell 中调用。 | | --port | 指定客户端需要连接的 mongo 服务器端口。 | | --host | 此指定客户端需要连接的 mongo 服务器的主机名。如果db address提供了主机名、端口和数据库,则不需要同时指定--host--port选项。 | | --username-u | 当 mongo 启用安全性时,这与相关。它用于提供要登录的用户的用户名。 | | --password-p | 当为 mongo 启用安全性时,此选项是相关的。它用于提供要登录的用户的密码。 |

使用 Java 客户端连接到单个节点

这个食谱是关于为 MongoDB 设置Java 客户端的。在处理其他配方时,您将反复参考此配方,因此请仔细阅读。

准备好了吗

以下是此配方的先决条件:

  • 建议使用 Java SDK 1.6 或更高版本。
  • 使用最新版本的 Maven。3.3.3 版是撰写本书时的最新版本。
  • MongoDB Java 驱动版本 3.0.1 是撰写本书时的最新版本。
  • 连接到 Internet 以访问联机 maven 存储库或本地存储库。或者,您可以选择一个适当的本地存储库,该存储库可从您的计算机访问。
  • Mongo 服务器已启动并在本地主机和端口27017上运行。看看第一个配方,安装单节点 MongoDB,然后启动服务器。

怎么做…

  1. 安装最新版本的 JDKhttps://www.java.com/en/download/ 如果您的机器上还没有。我们不会在这个配方中介绍安装 JDK 的步骤,但是在继续下一步之前,JDK 应该已经出现了。

  2. Maven needs to be downloaded from http://maven.apache.org/download.cgi. We should see something similar to the following image on the download page. Choose the binaries in a .tar.gz or .zip format and download it. This recipe is executed on a machine running on the Windows platform and thus these steps are for installation on Windows.

    How to do it…

  3. 下载存档文件后,我们需要将其解压缩,并将解压缩存档文件中的bin文件夹的绝对路径放入操作系统的 path 变量中。Maven 还需要将 JDK 的路径设置为JAVA_HOME环境变量。记住将 JDK 的根设置为该变量的值。

  4. 我们现在需要做的就是在命令提示符下键入mvn -version,如果我们看到以如下内容开头的输出,我们已经成功地设置了 maven:

    > mvn -version
  5. 在这个阶段,我们已经安装了maven,现在我们准备创建一个简单的项目,用 Java 编写我们的第一个 Mongo 客户端。我们首先创建一个project文件夹。假设我们创建了一个名为Mongo Java的文件夹。然后我们在这个project文件夹中创建一个文件夹结构src/main/java。然后,project文件夹的根目录包含一个名为pom.xml的文件。创建此文件夹后,文件夹结构应如下所示:

          Mongo Java      
          +--src  
          |     +main
          |         +java
          |--pom.xml
  6. 我们只带着项目框架。我们现在将向pom.xml文件添加一些内容。这不需要太多。以下内容是我们在pom.xml文件中所需的全部内容:

    <project>
      <modelVersion>4.0.0</modelVersion>
      <name>Mongo Java</name>
      <groupId>com.packtpub</groupId>
      <artifactId>mongo-cookbook-java</artifactId>
      <version>1.0</version>    <packaging>jar</packaging>
      <dependencies>
        <dependency>
          <groupId>org.mongodb</groupId>
          <artifactId>mongo-java-driver</artifactId>
          <version>3.0.1</version>
        </dependency>
      </dependencies>
    </project>
  7. 最后,我们编写了 Java 客户机,用于连接 Mongo 服务器并执行一些非常基本的操作。下面是com.packtpub.mongo.cookbook包中位置src/main/java中的 Java 类,该类的名称为FirstMongoClient

    package com.packtpub.mongo.cookbook;
    
    import com.mongodb.BasicDBObject;
    import com.mongodb.DB;
    import com.mongodb.DBCollection;
    import com.mongodb.DBObject;
    import com.mongodb.MongoClient;
    
    import java.net.UnknownHostException;
    import java.util.List;
    
    /**
     * Simple Mongo Java client
     *
     */
    public class FirstMongoClient {
    
        /**
         * Main method for the First Mongo Client. Here we shall be connecting to a mongo
         * instance running on localhost and port 27017.
         *
         * @param args
         */
        public static final void main(String[] args) 
    throws UnknownHostException {
            MongoClient client = new MongoClient("localhost", 27017);
            DB testDB = client.getDB("test");
            System.out.println("Dropping person collection in test database");
            DBCollection collection = testDB.getCollection("person");
            collection.drop();
            System.out.println("Adding a person document in the person collection of test database");
            DBObject person = 
    new BasicDBObject("name", "Fred").append("age", 30);
            collection.insert(person);
            System.out.println("Now finding a person using findOne");
            person = collection.findOne();
            if(person != null) {
                System.out.printf("Person found, name is %s and age is %d\n", person.get("name"), person.get("age"));
            }
            List<String> databases = client.getDatabaseNames();
            System.out.println("Database names are");
            int i = 1;
            for(String database : databases) {
                System.out.println(i++ + ": " + database);
            }
      System.out.println("Closing client");
            client.close();
        }
    }
  8. 现在是执行前面的 Java 代码的时间了。我们将使用 shell 中的 maven 执行它。您应该与项目的pom.xml在同一目录中:

    mvn compile exec:java -Dexec.mainClass=com.packtpub.mongo.cookbook.FirstMongoClient

它是如何工作的…

这些都是相当多的步骤要遵循。让我们更详细地看看其中的一些。步骤 6 之前的所有内容都很简单,不需要任何解释。让我们从第 7 步开始看。

我们这里的pom.xml文件非常简单。我们在 mongo 的 Java 驱动上定义了一个依赖项。它依靠在线存储库repo.maven.apache.org来解析工件。对于本地存储库,我们只需在pom.xml中定义存储库和pluginRepositories标记。有关 maven 的更多信息,请参阅位于的 maven文档 http://maven.apache.org/guides/index.html

对于 Java 类,org.mongodb.MongoClient类是主干。我们首先使用一个重载构造函数实例化它,该构造函数提供服务器的主机和端口。在这种情况下,主机名和端口实际上并不需要,因为提供的值是默认值,而且无参数构造函数也可以很好地工作。以下代码段实例化了此客户端:

MongoClient client = new MongoClient("localhost", 27017);

下一步是获取数据库,在本例中,使用getDB方法进行测试。它作为com.mongodb.DB类型的对象返回。请注意,此数据库可能不存在,但getDB不会引发任何异常。相反,每当我们向数据库中的集合添加新文档时,就会创建数据库。类似地,DB 对象上的getCollection将返回表示数据库中集合的com.mongodb.DBCollection类型的对象。这也可能不存在于数据库中,并将在自动插入第一个文档时创建。

我们类中的以下两个代码片段向您展示了如何获取DBDBCollection的实例:

DB testDB = client.getDB("test");
DBCollection collection = testDB.getCollection("person");

在插入文档之前,我们将删除集合,这样即使多次执行程序,我们也将在 person 集合中只有一个文档。在DBCollection对象的实例上使用drop()方法删除集合。接下来,我们创建一个com.mongodb.DBObject的实例。这是一个对象,表示要插入到集合中的文档。这里使用的具体类是BasicDBObject,它是java.util.LinkedHashMap的一种类型,其中键是 String,值是 Object。该值也可以是另一个DBObject,在这种情况下,它是嵌套在另一个文档中的文档。在本例中,我们有两个键,name 和 age,它们是要插入的文档中的字段名,值分别是 String 和 Integer 类型。BasicDBObject的 append 方法向BasicDBObject实例添加一个新的键值对并返回同一个实例,这允许我们链接 append 方法调用以添加多个键值对。然后使用 insert 方法将创建的DBObject插入到集合中。这就是我们为 person 集合实例化DBObject并将其插入集合的方式,如下所示:

DBObject person = new BasicDBObject("name", "Fred").append("age", 30);
collection.insert(person);

DBCollection上的findOne方法简单明了,返回集合中的一个文档。此版本的findOne不接受DBObject作为参数(否则将作为在选择并返回文档之前执行的查询)。这与从外壳中执行db.person.findOne()同义。

最后,我们只需调用getDatabaseNames即可获得服务器中数据库名称的列表。此时,我们至少应该在返回的结果中有testlocal数据库。所有操作完成后,我们关闭客户端。MongoClient类是线程安全的,通常每个应用程序使用一个实例。为了执行程序,我们使用 maven 的 exec 插件。在执行步骤 9 时,我们应该在控制台的末尾看到以下几行:

[INFO] [exec:java {execution: default-cli}]
--snip--
Dropping person collection in test database
Adding a person document in the person collection of test database
Now finding a person using findOne
Person found, name is Fred and age is 30
Database names are
1: local
2: test
INFO: Closed connection [connectionId{localValue:2, serverValue:2}] to localhost:27017 because the pool has been closed.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Tue May 12 07:33:00 UTC 2015
[INFO] Final Memory: 22M/53M
[INFO] ------------------------------------------------------------------------ 

使用 Python 客户端连接到单个节点

在此配方中,我们将使用名为 PyMongo 的 Python MongoDB 驱动连接到单个 MongoDB 实例。通过将 Python 的简单语法和多功能性与 MongoDB 结合起来,许多程序员发现该堆栈可以更快地进行原型设计并缩短开发周期。

准备好了吗

以下是此配方的先决条件:

  • Python 2.7。x(虽然代码与 Python 3x兼容)。
  • Pymongo3.0.1:PythonMongoDB 驱动。
  • Python 包安装程序(pip)。
  • Mongo 服务器已启动并在本地主机和端口27017上运行。看看第一个配方,安装单节点 MongoDB,然后启动服务器。

怎么做…

  1. 根据您的操作系统,可以在 Ubuntu/Debian 系统上安装 pip 实用程序。您可以使用以下命令安装 pip:

    > apt-get install python-pip
  2. 使用 pip:

    > pip install pymongo

    安装最新的 PyMongo 驱动

  3. 最后,创建一个名为my_client.py的新文件,并键入以下代码:

    from __future__ import print_function
    import pymongo
    
    # Connect to server
    client = pymongo.MongoClient('localhost', 27017)
    
    # Select the database
    testdb = client.test
    
    # Drop collection
    print('Dropping collection person')
    testdb.person.drop()
    
    # Add a person
    print('Adding a person to collection person')
    employee = dict(name='Fred', age=30)
    testdb.person.insert(employee)
    
    # Fetch the first entry from collection
    person = testdb.person.find_one()
    if person:
        print('Name: %s, Age: %s' % (person['name'], person['age']))
    
    # Fetch list of all databases
    print('DB\'s present on the system:')
    for db in client.database_names():
        print('    %s' % db)
    
    # Close connection
    print('Closing client connection')
    client.close()
  4. 使用以下命令运行脚本:

    > python my_client.py

它是如何工作的…

我们首先在 pip 包管理器的帮助下在系统上安装 Python MongoDB 驱动 pymongo。在给定的 Python 代码中,我们首先从__future__模块导入print_function,以允许与 Python 3 兼容。x。接下来,我们导入 pymongo,以便在脚本中使用它。

我们分别以 localhost 和27017作为 mongo 服务器主机和端口实例化pymongo.MongoClient()。在 pymongo 中,我们可以使用<client>.<database_name>.<collection_name>约定直接引用数据库及其集合。

在我们的配方中,我们使用客户机处理程序来选择数据库测试,只需参考client.test。即使数据库不存在,也会返回数据库对象。作为此配方的一部分,我们通过调用testdb.person.drop()来删除集合,其中testdb是对client.test的引用,person是我们希望删除的集合。对于此配方,我们有意删除集合,以便重复运行将始终在集合中生成一条记录。

接下来,我们实例化一个名为employee的字典,其中包含一些值,如 name 和 age。现在,我们将使用insert_one()方法将此条目添加到person集合中。

我们现在知道 person 集合中有一个条目,我们将使用find_one()方法获取一个文档。此方法返回集合中的第一个文档,具体取决于磁盘上存储的文档顺序。

在之后,我们还尝试通过向客户端调用get_databases()方法来获取所有数据库的列表。此方法返回服务器上存在的数据库名称列表。当您试图断言服务器上存在数据库时,此方法可能会派上用场。

最后,我们使用close()方法关闭客户端连接。

作为副本集的一部分启动多个实例

在本配方中,我们将考虑在同一主机上启动多个服务器,但将其作为一个集群。启动一台mongo 服务器就足以用于开发目的或非关键任务应用程序。对于关键的生产部署,我们需要高可用性,在这种情况下,如果一个服务器实例出现故障,另一个实例将接管,并且数据仍可用于查询、插入或更新。集群是一个先进的概念,我们不会在一个配方中涵盖整个概念。在这里,我们将触及表面,并在本书后面的管理部分的其他食谱中详细介绍。在此配方中,我们将在同一台机器上启动多个 mongo 服务器进程以进行测试。在生产环境中,它们将运行在相同或甚至不同数据中心的不同机器(或虚拟机)上。

让我们简单地看一下复制集到底是什么。顾名思义,它是一组在数据方面相互复制的服务器。查看它们如何保持彼此和其他内部同步,我们将遵循管理部分后面的一些方法,但需要记住的是,写入操作只会在一个节点上发生,而这个节点是主节点。默认情况下,所有查询也从主实例进行,尽管我们可以显式地允许对辅助实例执行读取操作。需要记住的一个重要事实是,副本集并不意味着通过在副本集中的各个节点之间分发读取操作来实现可伸缩性。其唯一目标是确保高可用性。

准备好了吗

虽然不是先决条件,但看看使用命令行选项启动单节点实例的方法肯定会让事情变得更简单,只是为了防止您在启动 mongo 服务器时不知道各种命令行选项及其重要性。此外,在我们继续此配方之前,必须完成单服务器设置中提到的必要二进制文件和设置。让我们总结一下我们需要做什么。

我们将在本地主机上启动三个 mongod 进程(mongo 服务器实例)。

我们将分别为Node1Node2Node3创建三个数据目录/data/n1/data/n2/data/n3。同样,我们将把日志重定向到/logs/n1.log/logs/n2.log/logs/n3.log。下图将让您了解集群的外观:

Getting ready

怎么做…

让我们详细地看一下这些步骤:

  1. 分别为三个节点的数据和日志创建/data/n1/data/n2/data/n3/logs目录。在 Windows 平台上,您可以分别为数据和日志选择c:\data\n1c:\data\n2c:\data\n3c:\logs\目录或您选择的任何其他目录。确保这些目录具有适当的写入权限,以便mongo 服务器写入数据和日志。

  2. 按如下方式启动三台服务器。Windows 平台上的用户需要跳过--fork选项,因为它不受支持:

    $ mongod --replSet repSetTest --dbpath /data/n1 --logpath /logs/n1.log --port 27000 --smallfiles --oplogSize 128 --fork
    $ mongod --replSet repSetTest --dbpath /data/n2 --logpath /logs/n2.log --port 27001 --smallfiles --oplogSize 128 --fork
    $ mongod --replSet repSetTest --dbpath /data/n3 --logpath /logs/n3.log --port 27002 --smallfiles --oplogSize 128 –fork
  3. 启动 mongo shell 并连接到任何正在运行的 mongo 服务器。在本例中,我们连接到第一个端口(侦听端口27000。执行以下命令:

    $ mongo localhost:27000
  4. Try to execute an insert operation from the mongo shell after connecting to it:

    > db.person.insert({name:'Fred', age:35})

    此操作应失败,因为副本集尚未初始化。更多信息可以在如何工作部分找到。

  5. 下一步是开始配置副本集。我们首先在 shell 中准备一个 JSON 配置,如下所示:

    cfg = {
      '_id':'repSetTest', 'members':[ {'_id':0, 'host': 'localhost:27000'}, {'_id':1, 'host': 'localhost:27001'}, {'_id':2, 'host': 'localhost:27002'} ]
    }
  6. 最后一步是使用前面的配置启动副本集,如下所示:

    > rs.initiate(cfg)
  7. 在 shell 上几秒钟后执行rs.status()以查看状态。几秒钟后,其中一个应该成为主要的,其余两个应该成为次要的。

它是如何工作的…

我们之前用命令行选项配方描述了安装单节点 MongoDB配方中的常见选项,并对所有这些命令行选项进行了详细描述。

当我们启动三个独立的 mongod 服务时,我们在文件系统上有三个专用的数据库路径。类似地,每个进程都有三个单独的日志文件位置。然后,使用指定的数据库和日志文件路径启动三个 mongod 进程。由于此设置用于测试目的,并且在同一台机器上启动,因此我们使用--smallfiles--oplogSize选项。由于这些进程在同一台主机上运行,我们还显式选择端口以避免端口冲突。我们在这里选择的端口是270002700127002。当我们在不同的主机上启动服务器时,我们可能会也可能不会选择一个单独的端口。只要可能,我们完全可以选择使用默认值。

--fork选项需要一些解释。通过选择此选项,我们可以从操作系统的 shell 作为后台进程启动服务器,并在 shell 中获得控制权,然后我们可以启动更多这样的 mongod 进程或执行其他操作。在没有--fork选项的情况下,我们不能为每个 shell 启动多个进程,需要在三个单独的 shell 中启动三个 mongod 进程。

如果我们查看日志目录中生成的日志,应该会看到其中的以下行:

[rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)
[rsStart] replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done

虽然我们使用--replSet选项启动了三个 mongod 进程,但我们仍然没有将它们配置为彼此作为副本集工作。此命令行选项仅用于在启动时告知服务器此进程将作为副本集的一部分运行。副本集的名称与在命令提示符下传递的此选项的值相同。这也解释了为什么在初始化复制集之前在其中一个节点上执行的插入操作失败。在 mongo 副本集中,只能有一个主节点进行所有插入和查询。在所示图像中,N1节点显示为主要节点,并侦听端口27000进行客户端连接。所有其他节点都是从属/辅助实例,它们自身与主节点同步,因此默认情况下也会禁用查询。只有当主节点停机时,其中一个辅助节点才会接管并成为主节点。然而,正如我们在图像中所示,可以查询次要数据;我们将在下一个菜谱中看到如何从第二个实例查询。

现在剩下的就是通过对我们启动的三个进程进行分组来配置副本集。首先定义一个 JSON 对象,如下所示:

cfg = {
  '_id':'repSetTest', 'members':[ {'_id':0, 'host': 'localhost:27000'}, {'_id':1, 'host': 'localhost:27001'}, {'_id':2, 'host': 'localhost:27002'} ]
}

有两个字段,_idmembers分别用于副本集的唯一 ID 和作为该副本集一部分的 mongod 服务器进程的主机名和端口号数组。使用 localhost 引用主机不是一个好主意,通常不鼓励使用;但是,在这种情况下,由于我们在同一台机器上启动了所有进程,因此我们可以接受它。最好通过主机名来引用主机,即使它们在本地主机上运行。请注意,不能在同一配置中混合引用使用 localhost 和 hostname 的实例。它是主机名或本地主机。为了配置副本集,我们连接到三个运行的 mongod 进程中的任意一个;在本例中,我们连接到第一个,然后从 shell 执行以下操作:

> rs.initiate(cfg)

传递的cfg对象中的_id字段的值与我们在启动服务器进程时在命令提示符下给--replSet选项的值相同。不提供相同的值将引发以下错误:

{
 "ok" : 0,
 "errmsg" : "couldn't initiate : set name does not match the set name host Amol-PC:27000 expects"
}

如果一切顺利且 initiate 调用成功,我们应该在 shell 上看到类似于以下 JSON 响应的内容:

{"ok" : 1}

几秒钟后,您将看到执行此命令的 shell 的不同提示。它现在应该成为一个主要的或次要的。以下是连接到副本集主成员的 shell 示例:

repSetTest:PRIMARY>

执行rs.status()应该会给我们一些关于副本集状态的统计信息,我们将在本书后面的管理部分的配方中深入探讨。目前,stateStr字段很重要,包含PRIMARYSECONDARY和其他文本。

还有更多…

查看 shell 中的连接到副本集,查询并插入数据配方,在连接到副本集后从 shell 执行更多操作。复制并不像我们在这里看到的那么简单。有关复制的更多高级方法,请参阅管理部分。

另见

如果要将独立实例转换为副本集,则需要首先将包含数据的实例变为主实例,然后添加空的辅助实例,以便将数据同步到该实例。有关如何执行此操作,请参阅以下 URL:

http://docs.mongodb.org/manual/tutorial/convert-standalone-to-replica-set/

连接到 shell 中的副本集查询插入数据

在前面的配方中,我们启动了一个由三个 mongod 进程组成的复制集。在本配方中,我们将使用 mongo 客户端应用程序将连接到此设置,执行查询,插入数据,并从客户端的角度查看副本集的一些有趣方面。

准备好了吗

此配方的先决条件是应设置并运行副本集。有关如何启动副本集的详细信息,请参阅上一个配方将多个实例作为副本集的一部分启动。

怎么做…

  1. 我们将从这里开始两个 shell,一个用于PRIMARY和一个用于SECONDARY。在命令提示符下执行以下命令:

    > mongo localhost:27000
  2. shell 的提示告诉我们所连接的服务器是PRIMARY还是SECONDARY。它应该显示副本集的名称,后跟一个:,后跟服务器状态。在这种情况下,如果副本集已初始化、启动并正在运行,我们应该看到repSetTest:PRIMARY>repSetTest:SECONDARY>

  3. 假设我们连接的第一台服务器是辅助服务器,我们需要找到主服务器。在 shell 中执行rs.status()命令,查看stateStr字段。这将为我们提供主服务器。使用 mongo shell 连接到此服务器。

  4. 此时,我们应该运行两个 shell,一个连接到主 shell,另一个连接到辅助 shell。

  5. 在连接到主节点的 shell 中,执行以下插入:

    repSetTest:PRIMARY> db.replTest.insert({_id:1, value:'abc'})
  6. 这没什么特别的。我们刚刚在一个集合中插入了一个小文档,用于复制测试。

  7. 通过对主节点执行以下查询,我们应该得到以下结果:

    repSetTest:PRIMARY> db.replTest.findOne()
    { "_id" : 1, "value" : "abc" }
  8. So far, so good. Now, we will go to the shell that is connected to the SECONDARY node and execute the following:

    repSetTest:SECONDARY> db.replTest.findOne()

    执行此操作时,我们应该在控制台上看到以下错误:

     { "$err" : "not master and slaveOk=false", "code" : 13435 }
  9. 现在在控制台上执行以下操作:

    repSetTest:SECONDARY>  rs.slaveOk(true)
  10. 在 shell 上再次执行我们在步骤 7 中执行的查询。现在应该得到如下结果:

```js
repSetTest:SECONDARY>db.replTest.findOne()
{ "_id" : 1, "value" : "abc" }

```
  1. 在辅助节点上执行以下插入操作;它不应成功,并显示以下消息:
```js
repSetTest:SECONDARY> db.replTest.insert({_id:1, value:'abc'})
not master

```

它是如何工作的…

在这个食谱中,我们已经做了很多事情,我们将尝试对一些需要记住的重要概念进行解释。

我们基本上从 shell 连接到主节点和次节点,并执行(我会说,尝试执行)选择和插入。Mongo 副本集的体系结构由一个主节点(只有一个,没有更多,也没有更少)和多个辅助节点组成。所有写入仅在PRIMARY上发生。请注意,复制不是一种分配读取请求负载的机制,这种负载可以扩展系统。其主要目的是确保数据的高可用性。默认情况下,不允许我们从辅助节点读取数据。在步骤 6 中,我们只需从主节点插入数据,然后执行查询以获取插入的文档。这很简单,与集群无关。请注意,我们从主文档中插入了文档,然后将其查询回来。

在下一步中,我们执行相同的查询,但这次是从次要的 shell 执行的。SECONDARY默认不支持查询。复制数据时可能会有一个小的延迟,这可能是由于要复制的数据量大、网络延迟或硬件容量等原因造成的,因此,在次要服务器上查询可能不会反映在主要服务器上进行的最新插入或更新。但是,如果我们可以接受它,并且可以忍受被复制数据的轻微延迟,那么我们需要做的就是通过执行一个命令rs.slaveOk()rs.slaveOk(true)显式地在SECONDARY节点上启用查询。完成后,我们也可以在辅助节点上自由执行查询。

最后,我们尝试将数据插入到从节点的集合中。在任何情况下都不允许这样做,无论我们是否做过rs.slaveOk()。调用rs.slaveOk()时,只允许从SECONDARY节点查询数据。所有写操作仍必须转到主操作,然后向下流到辅助操作。复制的内部内容将在管理部分的不同配方中介绍。

另见

下一个方法,连接到副本集以查询和插入来自 Java 客户端的数据,是关于连接到来自 Java 客户端的副本集。

连接到副本集以查询和插入来自 Java 客户端的数据

在此配方中,我们将演示如何从 Java 客户端连接到副本集,以及在主节点发生故障时,客户端如何自动故障切换到副本集中的另一个节点。

准备好了吗

我们需要看看使用 Java 客户机方法连接到单个节点的*,因为它包含了设置 maven 和其他依赖项的所有先决条件和步骤。当我们处理复制集的 Java 客户机时,必须建立并运行一个复制集。有关如何启动副本集的详细信息,请参阅作为副本集的一部分启动多个实例配方。*

怎么做…

  1. 编写/复制以下代码:(该 Java 类也可从 Packt 网站下载。)

    package com.packtpub.mongo.cookbook;
    
    import com.mongodb.BasicDBObject;
    import com.mongodb.DB;
    import com.mongodb.DBCollection;
    import com.mongodb.DBObject;
    import com.mongodb.MongoClient;
    import com.mongodb.ServerAddress;
    
    import java.util.Arrays;
    
    /**
     *
     */
    public class ReplicaSetMongoClient {
    
      /**
      * Main method for the test client connecting to the replica set.
       * @param args
      */
      public static final void main(String[] args) throws Exception {
        MongoClient client = new MongoClient(
          Arrays.asList(
            new ServerAddress("localhost", 27000), new ServerAddress("localhost", 27001), new ServerAddress("localhost", 27002)
          )
        );
        DB testDB = client.getDB("test");
        System.out.println("Dropping replTest collection");
        DBCollection collection = testDB.getCollection("replTest");
        collection.drop();
        DBObject object = new BasicDBObject("_id", 1).append("value", "abc");
        System.out.println("Adding a test document to replica set");
        collection.insert(object);
        System.out.println("Retrieving document from the collection, this one comes from primary node");
        DBObject doc = collection.findOne();
        showDocumentDetails(doc);
        System.out.println("Now Retrieving documents in a loop from the collection.");
        System.out.println("Stop the primary instance after few iterations ");
        for(int i = 0 ; i < 10; i++) {
          try {
            doc = collection.findOne();
            showDocumentDetails(doc);
          }
          catch (Exception e) {
            //Ignoring or log a message
          }
          Thread.sleep(5000);
        }
      }
    
      /**
      *
      * @param obj
      */
      private static void showDocumentDetails(DBObject obj) {
        System.out.printf("_id: %d, value is %s\n", obj.get("_id"), obj.get("value"));
      }
    }
  2. 连接到副本集中的任何节点,比如说连接到localhost:27000,然后从 shell 执行rs.status()。记下副本集中的主实例,如果localhost:27000不是主实例,则从 shell 连接到它。在这里,切换到管理员数据库如下:

    repSetTest:PRIMARY>use admin
  3. 我们现在从操作系统外壳执行前面的程序,如下所示:

    $ mvn compile exec:java -Dexec.mainClass=com.packtpub.mongo.cookbook.ReplicaSetMongoClient
  4. 通过在连接到主实例的 mongo shell 上执行以下操作来关闭主实例:

    repSetTest:PRIMARY> db.shutdownServer()
  5. 观察控制台上使用 maven 执行com.packtpub.mongo.cookbook.ReplicaSetMongoClient类的输出。

它是如何工作的…

观察一件有趣的事情是我们如何实例化MongoClient实例。具体做法如下:

  MongoClient client = new MongoClient(Arrays.asList(new ServerAddress("localhost", 27000), new ServerAddress("localhost", 27001), new ServerAddress("localhost", 27002)));

构造函数获取com.mongodb.ServerAddress的列表。这个类有很多重载构造函数,但是我们选择使用一个,它接受主机名,然后接受端口。我们所做的是以列表的形式提供副本集中的所有服务器详细信息。我们没有提到什么是PRIMARY节点,什么是SECONDARY节点。MongoClient足够聪明,能够解决这个问题并连接到适当的实例。提供的服务器列表称为种子列表。它不需要在副本集中包含整个服务器集,尽管目标是尽可能多地提供。MongoClient将从提供的子集中找出所有服务器详细信息。例如,如果副本集由五个节点组成,但我们只提供三台服务器,那么它可以正常工作。在与提供的副本集服务器连接时,客户端将查询它们以获取副本集元数据,并计算出副本集中提供的其余服务器。在前面的例子中,我们用副本集中的三个实例实例化了客户机。如果复制集有五个成员,那么仅用其中三个成员实例化客户机就足够了,剩下的两个实例将被自动发现。

接下来,我们使用 maven 从命令提示符启动客户机。一旦客户机在循环中运行,我们将关闭主实例以查找一个文档。我们应该看到控制台的以下输出:

_id: 1, value is abc
Now Retrieving documents in a loop from the collection.
Stop the primary instance manually after few iterations
_id: 1, value is abc
_id: 1, value is abc
Nov 03, 2013 5:21:57 PM com.mongodb.ConnectionStatus$UpdatableNode update
WARNING: Server seen down: Amol-PC/192.168.1.171:27002
java.net.SocketException: Software caused connection abort: recv failed
 at java.net.SocketInputStream.socketRead0(Native Method)
 at java.net.SocketInputStream.read(SocketInputStream.java:150)
 
WARNING: Primary switching from Amol-PC/192.168.1.171:27002 to Amol-PC/192.168.1.171:27001
_id: 1, value is abc

我们可以看到,循环中的查询在主节点关闭时被中断。但是,客户端无缝地切换到新的主服务器。好的,几乎是无缝的,因为客户端可能必须捕获异常并在预定的时间间隔过后重试操作。

使用 Python 客户端连接到副本集以查询和插入数据

在此配方中,我们将演示如何使用Python 客户端连接到副本集,以及如果主节点出现故障,客户端如何自动故障切换到副本集中的另一个节点。

准备好了吗

请参阅使用 Python 客户端方法连接到单个节点,因为它描述了如何设置和安装 PyMongo,这是 MongoDB 的 Python 驱动。此外,副本集必须已设置并正在运行。有关如何启动副本集的详细信息,请参阅作为副本集的一部分启动多个实例配方。

怎么做…

  1. 将以下代码写入/复制到replicaset_client.py:(此脚本也可从 Packt 网站下载。)

    from __future__ import print_function
    import pymongo
    import time
    
    # Instantiate MongoClient with a list of server addresses
    client = pymongo.MongoClient(['localhost:27002', 'localhost:27001', 'localhost:27000'], replicaSet='repSetTest')
    
    # Select the collection and drop it before using
    collection = client.test.repTest
    collection.drop()
    
    #insert a record in
    collection.insert_one(dict(name='Foo', age='30'))
    
    for x in range(5):
        try:
            print('Fetching record: %s' % collection.find_one())
        except Exception as e:
            print('Could not connect to primary')
        time.sleep(3)
  2. 连接到复制品集合中的任一节点,比如说连接到localhost:27000,然后从 shell 执行rs.status()。记下副本集中的主实例,如果localhost:27000不是主实例,则从 shell 连接到它。在这里,切换到管理员数据库如下:

    > repSetTest:PRIMARY>use admin
  3. 我们现在从操作系统 shell 中执行前面的脚本,如下所示:

    $ python replicaset_client.py
  4. 通过在连接到主实例的 mongo shell 上执行以下操作来关闭主实例:

    > repSetTest:PRIMARY> db.shutdownServer()
  5. 在执行 Python 脚本的控制台上查看输出。

它是如何工作的…

您会注意到,在这个脚本中,我们通过提供一个主机列表而不是一个主机来实例化 mongo 客户机。从 3.0 版开始,pymongo 驱动的MongoClient()类可以在初始化期间接受主机列表或单个主机,并拒绝使用MongoReplicaSetClient()。客户端将尝试连接到列表中的第一台主机,如果成功,将能够确定副本集中的其他节点。我们还以独占方式传递replicaSet='repSetTest'参数,确保客户端检查连接的节点是否是该副本集的一部分。

一旦连接,我们将执行正常的数据库操作,如选择测试数据库、删除repTest集合,以及将单个文档插入集合。

在之后,我们为循环输入一个条件,迭代五次。每次,我们提取记录,显示它,然后睡眠三秒。当脚本在此循环中时,我们关闭副本集中的主节点,如步骤 4 所述。我们应该看到类似的输出:

Fetching record: {u'age': u'30', u'_id': ObjectId('5558bfaa0640fd1923fce1a1'), u'name': u'Foo'}
Fetching record: {u'age': u'30', u'_id': ObjectId('5558bfaa0640fd1923fce1a1'), u'name': u'Foo'}
Fetching record: {u'age': u'30', u'_id': ObjectId('5558bfaa0640fd1923fce1a1'), u'name': u'Foo'}
Could not connect to primary
Fetching record: {u'age': u'30', u'_id': ObjectId('5558bfaa0640fd1923fce1a1'), u'name': u'Foo'}

在前面的输出中,客户机中途与主节点断开连接。但是,很快,剩余节点会选择一个新的主节点,mongo 客户端就可以恢复连接。

启动两个碎片的简单碎片化环境

在此配方中,我们将设置一个由两个数据碎片组成的简单碎片设置。不会配置复制,因为这是演示此概念的最基本的碎片设置。我们不会深入讨论切分的内部内容,我们将在管理部分进一步探讨。

在我们继续之前,这里有一点理论。可扩展性和可用性是构建任何任务关键型应用程序的两个重要基石。可用性是由复制集负责的,我们在本章前面的配方中讨论了这一点。现在让我们看看可伸缩性。简单地说,可伸缩性是指系统能够轻松处理不断增加的数据和请求负载。考虑一个电子商务平台。在正常的日子里,对站点和负载的点击次数是相当有限的,系统的响应时间和错误率是最小的。(这是主观的)现在,考虑一下系统负荷的两倍,三倍,甚至超过一天平均负荷的日子,比如感恩节、圣诞节等等。如果该平台能够在这些高负载日提供与其他任何一天类似的服务级别,那么据说该系统已经很好地扩展,以适应请求数量的突然增加。

现在,考虑一个存档应用程序,它需要存储在过去十年中特定网站的所有请求的详细信息。对于每个访问网站的请求,我们在底层数据存储中创建一个新记录。假设每个记录有 250 字节,平均每天的负载为 300 万个请求,我们将在大约五年内跨越 1 TB 的数据标记。这些数据将用于各种分析目的,并且可能经常被查询。当数据大小增加时,查询性能不应受到严重影响。如果该系统能够应对不断增加的数据量,并且仍能提供与低数据量上的性能相当的性能,则可以说该系统扩展得很好。

现在,我们已经简单地了解了什么是可伸缩性,让我告诉您,分片是一种机制,它允许系统根据不断增长的需求进行扩展。关键在于,整个数据被分割成更小的部分,并分布在称为碎片的各个节点上。假设 mongo 集合中总共有 1000 万个文档。如果我们将这个集合分成 10 个碎片,那么在理想情况下,每个碎片上都会有10000000/10=1000000个文档。在给定的时间点,只有一个文档将驻留在一个碎片上(碎片本身就是生产系统中的副本集)。然而,其中有一些神奇之处,可以让查询集合的开发人员不知道这个概念,并且无论碎片的数量如何,都可以获得集合的统一视图。根据查询,由 mongo 决定查询哪个碎片以获取数据并返回整个结果集。在这样的背景下,让我们建立一个简单的切分并仔细研究它。

准备好了吗

除了已经安装的 MongoDB 服务器外,从软件角度来看,没有任何先决条件。我们将创建两个数据目录,每个碎片一个。将有一个数据目录和一个日志目录。

怎么做…

  1. 我们首先为日志和数据创建目录。创建以下目录,/data/s1/db/data/s2/db/logs。在 Windows 上,数据和日志目录可以有c:\data\s1\db等。还有一个配置服务器,用于在分片环境中存储一些元数据。我们将使用/data/con1/db作为配置服务器的数据目录。

  2. 启动以下 mongod 进程,两个碎片各一个,配置数据库一个,mongos 进程一个。对于 Windows 平台,跳过--fork参数,因为它不受支持。

    $ mongod --shardsvr --dbpath  /data/s1/db --port 27000 --logpath /logs/s1.log --smallfiles --oplogSize 128 --fork
    $ mongod --shardsvr --dbpath  /data/s2/db --port 27001 --logpath /logs/s2.log --smallfiles --oplogSize 128 --fork
    $ mongod --configsvr --dbpath  /data/con1/db --port 25000 --logpath  /logs/config.log --fork
    $ mongos --configdb localhost:25000 --logpath  /logs/mongos.log --fork
  3. 在命令提示符下,执行以下命令。这将显示一个 mongos 提示符,如下所示:

    $ mongo
    MongoDB shell version: 3.0.2
    connecting to: test
    mongos>
  4. 最后,我们设置了碎片。在 mongos shell 中,执行以下两个命令:

    mongos> sh.addShard("localhost:27000")
    mongos> sh.addShard("localhost:27001")
  5. On each addition of a shard, we should get an ok reply. The following JSON message should be seen giving the unique ID for each shard added:

    { "shardAdded" : "shard0000", "ok" : 1 }

    我们使用 localhost everywhere 来指代本地运行的服务器。这不是推荐的方法,不鼓励使用。更好的方法是使用主机名,即使它们是本地进程。

它是如何工作的…

让我们看看我们在这个过程中都做了些什么。我们创建了三个数据目录(两个用于碎片,一个用于配置数据库)和一个日志目录。我们也可以使用 shell 脚本或批处理文件来创建目录。事实上,在大型生产部署中,手动设置碎片不仅耗时而且容易出错。

提示

下载示例代码

您可以下载您在账户购买的所有 Packt 书籍的示例代码文件 http://www.packtpub.com 。如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support 并注册,将文件直接通过电子邮件发送给您。

让我们试着了解一下我们到底做了什么,以及我们正在努力实现什么。下面是我们刚刚进行的碎片设置的图像:

How it works…

如果我们查看前面的图像和在步骤 2 中启动的服务器,我们就有了将实际数据存储在集合中的碎片服务器。这是开始监听端口2700027001的四个进程中的前两个。接下来,我们启动了一个配置服务器,如图左侧所示。它是在步骤 2 中启动的四台服务器中的第三台服务器,它侦听端口25000以获取传入连接。此数据库的唯一用途是维护有关碎片服务器的元数据。理想情况下,只有 mongos 进程或驱动连接到此服务器以获取碎片详细信息/元数据和碎片密钥信息。我们将在下一个菜谱中看到切分键是什么,在这里我们将围绕切分集合进行游戏,并看到我们在实际操作中创建的切分键。

最后,我们有一个 mongos 流程。这是一个轻量级流程,它不进行任何数据持久化,只接受来自客户端的连接。这一层充当网关守护者,将客户机从碎片的概念中抽象出来。现在,我们基本上可以将其视为一个路由器,它咨询配置服务器并决定将客户机的查询路由到适当的切分服务器以执行。然后,如果适用,它聚合来自各种碎片的结果,并将结果返回给客户端。可以肯定地说,没有客户机直接连接到配置或碎片服务器;事实上,理想情况下,除了一些管理操作之外,没有人应该直接连接到这些流程。客户端只需连接到 mongos 进程并执行查询、插入或更新操作。

仅仅启动分片服务器、配置服务器和 mongos 进程并不会创建分片环境。在启动 mongos 进程时,我们向其提供了配置服务器的详细信息。存储实际数据的两个碎片呢?但是,作为 shard 服务器启动的两个 mongod 进程尚未在配置中的任何位置声明为 shard 服务器。这正是我们在最后一步中为两个碎片服务器调用sh.addShard()所做的。mongos 进程在启动时提供了配置服务器的详细信息。从 shell 添加碎片将有关碎片的元数据存储在配置数据库中,然后 mongos 进程将查询该配置数据库中的碎片信息。在执行配方的所有步骤时,我们有一个操作碎片,如下所示:

How it works…

在我们得出结论之前,我们在这里设置的碎片远不是理想的,也不是在生产环境中如何实现的。上图为我们提供了一个典型碎片在生产环境中的情况。碎片的数量不会是两个,而是更多。此外,每个碎片将是一个副本集,以确保高可用性。将有三个配置服务器来确保配置服务器的可用性。类似地,将为侦听客户端连接的碎片创建任意数量的 mongos 进程。在某些情况下,它甚至可能在客户端应用程序的服务器上启动。

还有更多…

碎片有什么好处,除非我们将其付诸行动,并查看 shell 在插入和查询数据时会发生什么?在下一个配方中,我们将在这里使用 shard 设置,添加一些数据,并查看它的运行情况。

连接到外壳中的碎片并执行操作

在这个配方中,我们将从命令提示符连接到一个切分,看看如何切分一个集合,并观察一些测试数据上的数据拆分操作。

准备好了吗

显然,我们需要一个分片的 mongo 服务器安装并运行。有关如何设置简单分片的更多详细信息,请参见前面的配方启动包含两个分片的简单分片环境。mongos 进程,如前一个配方一样,应该侦听端口号27017。我们在名为names.js的 JavaScript 文件中找到了一些名称。该文件需要从 Packt 网站下载并保存在本地文件系统中。该文件包含一个名为names的变量,该值是一个数组,其中包含一些 JSON 文档作为值,每个文档代表一个人。内容如下:

names = [
  {name:'James Smith', age:30},
  {name:'Robert Johnson', age:22},

]

怎么做…

  1. 启动 mongo shell 并连接到 localhost 上的默认端口,如下所示。这将确保名称在当前 shell 中可用:

    mongo --shell names.js
    MongoDB shell version: 3.0.2
    connecting to: test
    mongos>
  2. 切换到将用于测试分片的数据库;我们称之为shardDB

    mongos> use shardDB
  3. 在数据库级别启用分片,如下所示:

    mongos> sh.enableSharding("shardDB")
  4. 切分一个名为person的集合,如下所示:

    mongos>sh.shardCollection("shardDB.person", {name: "hashed"}, false)
  5. 将测试数据添加到分片集合:

    mongos> for(i = 1; i <= 300000 ; i++) {
    ... person = names[Math.round(Math.random() * 100) % 20]
    ... doc = {_id:i, name:person.name, age:person.age}
    ... db.person.insert(doc)
    }
  6. 执行以下操作以获取查询计划和每个碎片上的文档数:

    mongos> db.person.getShardDistribution()

它是如何工作的…

这个食谱需要解释一下。我们下载了一个 JavaScript 文件,该文件定义了一个 20 人的数组。数组的每个元素都是一个 JSON 对象,具有nameage属性。我们启动 shell 连接到加载了这个 JavaScript 文件的 mongos 进程。然后我们切换到shardDB,我们使用它进行切分。

对于要分片的集合,需要首先为分片启用将在其中创建集合的数据库。我们使用sh.enableSharding()来实现这一点。

下一步是启用集合分片。默认情况下,所有数据将保留在一个碎片上,而不是在不同的碎片上分割。想想看;Mongo 将如何有意义地分割数据?整个意图是尽可能有意义地、均匀地分割它,以便无论何时我们基于 shard 键进行查询,Mongo 都能够轻松地确定要查询的 shard。如果查询不包含 shard 键,则将在所有 shard 上执行查询,然后 mongos 进程将整理数据,然后再将其返回给客户端。因此,选择正确的切分键非常关键。

现在让我们看看如何分割集合。我们通过调用sh.shardCollection("shardDB.person", {name: "hashed"}, false)来实现这一点。这里有三个参数:

  • <db name>.<collection name>格式集合的完全限定名是shardCollection方法的第一个参数。
  • 第二个参数是要在集合中切分的字段名。这是用于分割碎片上的文档的字段。一个好的切分键的要求之一是它应该具有很高的基数。(可能值的数量应该很高。)在我们的测试数据中,name 值的基数很低,因此作为 shard 键不是一个好的选择。我们将此密钥用作切分密钥时对其进行散列。为此,我们将密钥称为{name: "hashed"}
  • 最后一个参数指定用作分片键的值是否唯一。名称字段肯定不是唯一的,因此它将是错误的。如果该字段是,比如说,此人的社会保险号,则可以将其设置为 true。此外,SSN 是切分密钥的一个好选择,因为它具有很高的基数。记住,为了使查询有效,必须存在切分键。

最后一步是查看查找所有数据的执行计划。此操作的目的是查看如何将数据分割到两个碎片上。对于 300000 个文档,我们预计每个碎片上大约有 150000 个文档。但从分布统计可以看出,shard00001,49,715单据,shard0001150285单据:

Shard shard0000 at localhost:27000
 data : 15.99MiB docs : 149715 chunks : 2
 estimated data per chunk : 7.99MiB
 estimated docs per chunk : 74857

Shard shard0001 at localhost:27001
 data : 16.05MiB docs : 150285 chunks : 2
 estimated data per chunk : 8.02MiB
 estimated docs per chunk : 75142

Totals
 data : 32.04MiB docs : 300000 chunks : 4
 Shard shard0000 contains 49.9% data, 49.9% docs in cluster, avg obj size on shard : 112B
 Shard shard0001 contains 50.09% data, 50.09% docs in cluster, avg obj size on shard : 112B

我建议你做一些额外的建议。

连接到 mongo shell 中的单个碎片,并对 person 集合执行查询。请注意,这些集合中的计数与我们在前面的计划中看到的类似。此外,可以发现两个碎片上没有同时存在的文档。

我们简要地讨论了基数如何影响数据在碎片之间分割的方式。让我们做一个简单的练习。我们首先删除 person 集合并再次执行 shardCollection 操作,但这次使用的是{name: 1}shard 键而不是{name: "hashed"}。这可以确保切分密钥不会散列并按原样存储。现在,使用前面步骤 5 中使用的 JavaScript 函数加载数据,然后在加载数据后对集合执行explain()命令。观察数据现在是如何在碎片之间分割(或不分割)的。

还有更多…

现在必须提出很多问题,比如什么是最佳实践?有什么秘诀和窍门?MongoDB 是如何在幕后以对最终用户透明的方式实现切分的?

这个食谱在这里只解释了基础知识。在行政部分,将回答所有此类问题。