这是我参与8月更文挑战的第6天,活动详情查看: 8月更文挑战

这篇通过实际操作,搭建一个 3 节点的 MongoDB 复制集。一般情况下,复制集的不同节点位于不同的服务器,但因为是案例演示,所以在一台机器上搭建复制集,它们之间的区别就是需要在同一台机器上监听三个不同的端口号启动三个 MongoDB 实例,其它没什么不同。

主要步骤如下:

  • 安装 MongoDB
  • 在配置文件中配置端口号、数据目录、复制集信息
  • 启动 MongoDb实例
  • 配置复制集
  • 安装 MongoDB

    安装 MongoDB 非常简单,你可以去 官网 下载你是用的操作系统对应的 MongoDB,或者使用包管理器安装。

    在这个事例中,我们在 Linux 环境中,使用从官网下载的 .tgz 包进行安装。

    如上图,选择对应的操作系统和版本号即可下载。或者复制下载链接使用 curl 下载。

    $ curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-5.0.0.tgz $ tar -xvf mongodb-linux-x86_64-rhel80-5.0.0.tgz # 移动到指定的目录(可选) $ mv mongodb-linux-x86_64-rhel80-5.0.0/ /opt/

    为了后面方便地使用 mongod 等命令,还可以将 MongoDB 目录下的 bin 目录添加到系统变量当中。

    $ export PATH=$PATH:/opt/mongodb-linux-x86_64-rhel80-5.0.0/bin
    

    配置 MongoDB 实例

    因为我们要启动 3 个 MongoDB 实例,因此需要创建三个数据目录和配置文件。一般情况下,MongoDB 的数据会被存放在 /data/db 目录下,因此,可以为这三个实例创建 /data/db1/data/db2/data/db3 三个目录。

    $ mkdir -p /data/db{1,2,3}
    

    然后,可以三个实例的配置文件也创建在它们的数据目录中,我们以其中一个为例

    # /data/db1/mongod.conf
    systemLog:
        destination: file
        path: /data/db1/mongod.log
        logAppend: true
    storage:
        dbPath: /data/db1
        bindIp: 0.0.0.0
        port: 28017
    replication:
        replSetName: rs0
    processManagement:
        fork: true
    

    以上是 db1 这个实例的配置文件,两外两个与之类似。这里需要关注的几个点:

  • path: /data/db1/mongod.log 每个实例要配置不同的路径。
  • dbPath: /data/db1 配置的是每个实例的数据目录,也需要每个实例配置不同的路径。
  • port: 28017 这个是 MongoDB 服务监听的端口号,每个实例需要监听不同的端口号。
  • replSetName: rs0 是复制集的名称,每个实例都相同。
  • 启动 MongoDB

    将三个配置文件都创建好,就可以使用三个不同的配置,启动三个 MongoDB 实例:

    $ mongod -f /data/db1/mongo.conf
    $ mongod -f /data/db2/mongo.conf
    $ mongod -f /data/db3/mongo.conf
    

    这里,我使用的三个端口号分别是28017、28018、28019,之后的操作也以这三个端口号为例。

    启动之后就可以开始下一步。

    配置复制集

    首先链接第一个实例(db1)。

    $ mongo --port 28017
    

    进入 db1 实例后,以它作为主节点配置复制集,有两种方法。

    > rs.initiate()
    > rs.add("HOSTNAME:28018")
    > rs.add("HOSTNAME:28019")
    
    > rs.initiate({
        _id: "rs0",
        members: [
            { _id: 0, host: "HOSTNAME:28017" },
            { _id: 1, host: "HOSTNAME:28018" },
            { _id: 2, host: "HOSTNAME:28019" }
    

    两种方法均可,注意,这里面的 HOSTNAME 指的是你本机的 hostname,可以通过 hostname 命令查看。

    下面以第一种方法为例来执行:

    > rs.initiate()
        "info2" : "no configuration specified. Using a default configuration for the set",
        "me" : "ecs-b652-0001:28017",
        "ok" : 1
    rs0:SECONDARY>
    rs0:PRIMARY>
    

    可以看到,在执行 rs.initiate() 之后,回显了一些信息,然后,命令行前面的 > 变成了 rs0:SECONDARY>, 然后又变成了 rs0:PRIMARY>

    这里的 rs0 是指复制集的名字,后面的内容先变成 SECONDARY,再变成 PRIMARY 是因为,复制集刚刚创建的时候,每一个节点都默认是从节点,由于当前复制集中只有一个主节点,因此,随后就被选举成了主节点。

    接着添加另外两个节点。

    rs0:PRIMARY> rs.add("HOSTNAME:28018")
        "ok" : 1,
        "$clusterTime" : {
            "clusterTime" : Timestamp(1626613607, 1),
            "signature" : {
                "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                "keyId" : NumberLong(0)
        "operationTime" : Timestamp(1626613607, 1)
    

    用同样的方法添加两个基点就行了,注意 hostname 和端口号不要错误。

    下面进行测试。

    先在主节点插入数据:

    rs0:PRIMARY> db.test.insertOne({a:1})
        "acknowledged" : true,
        "insertedId" : ObjectId("60f428b929941628aa6be1b0")
    rs0:PRIMARY> db.test.insertOne({a:2})
        "acknowledged" : true,
        "insertedId" : ObjectId("60f428c529941628aa6be1b1")
    

    在连接一个从节点,查询刚刚插入的数据:

    rs0:SECONDARY> db.test.find()
    Error: error: {
        "topologyVersion" : {
            "processId" : ObjectId("60f425f5994233d8ce517f71"),
            "counter" : NumberLong(6)
        "ok" : 0,
        "errmsg" : "not master and slaveOk=false",
        "code" : 13435,
        "codeName" : "NotPrimaryNoSecondaryOk",
        "$clusterTime" : {
            "clusterTime" : Timestamp(1626614043, 1),
            "signature" : {
                "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                "keyId" : NumberLong(0)
        "operationTime" : Timestamp(1626614043, 1)
    

    报错了,这是因为从节点默认不允许执行查询操作。

    可以先执行如下操作:

    rs0:SECONDARY> rs.secondaryOk()
    

    在执行查询

    rs0:SECONDARY> db.test.find()
    { "_id" : ObjectId("60f428b929941628aa6be1b0"), "a" : 1 }
    { "_id" : ObjectId("60f428c529941628aa6be1b1"), "a" : 2 }
    

    就可以查询到数据了

    分类:
    后端