Ubuntu 22.04.5 LTS 部署 mongo 一主二从一选举

  1. 入 MongoDB GPG 密钥

bash

wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -

如果提示 apt-key 已弃用,可以使用以下命令替代:

bash

sudo mkdir -p /etc/apt/keyrings
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo gpg --dearmor -o /etc/apt/keyrings/mongodb.gpg
  1. 添加 MongoDB 软件源 创建并编辑 /etc/apt/sources.list.d/mongodb.list 文件:

bash

echo "deb [ arch=amd64,arm64 signed-by=/etc/apt/keyrings/mongodb.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list

上述命令中的 jammy 对应 Ubuntu 22.04,6.0 表示 MongoDB 的版本号,你可以根据需要调整。

  1. 更新软件包列表并安装 再次更新软件包列表:

bash

sudo apt update

然后尝试安装 mongodb-clients

bash

sudo apt install mongodb-mongosh

在较新的 MongoDB 版本中,mongosh 替代了传统的 mongodb-clients 工具,它是 MongoDB 的交互式 shell,提供了与数据库交互的功能。

文件目录结构如下

shell

.
├── docker-compose.yaml
├── mongodb-keyfile(文件)
├── mongo-arbiter-data(文件夹)
├── mongo1-data(文件夹)
├── mongo2-data(文件夹)
└── mongo3-data(文件夹)

目录结构
目录结构

docker-compose.yaml

yaml

services:
  mongo1:
    image: mongo:6.0.3
    container_name: mongo1
    restart: always
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ./mongo1-data:/data/db
      - ./mongodb-keyfile:/etc/mongodb-keyfile
    command: mongod --replSet rs0 --bind_ip_all --keyFile /etc/mongodb-keyfile --auth
    entrypoint:
      - bash
      - -c
      - |
        chmod 400 /etc/mongodb-keyfile
        chown 999:999 /etc/mongodb-keyfile
        exec docker-entrypoint.sh $$@        

  mongo2:
    image: mongo:6.0.3
    container_name: mongo2
    restart: always
    ports:
      - "27018:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ./mongo2-data:/data/db
      - ./mongodb-keyfile:/etc/mongodb-keyfile
    command: mongod --replSet rs0 --bind_ip_all --keyFile /etc/mongodb-keyfile --auth
    entrypoint:
      - bash
      - -c
      - |
        chmod 400 /etc/mongodb-keyfile
        chown 999:999 /etc/mongodb-keyfile
        exec docker-entrypoint.sh $$@        

  mongo3:
    image: mongo:6.0.3
    container_name: mongo3
    restart: always
    ports:
      - "27019:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ./mongo3-data:/data/db
      - ./mongodb-keyfile:/etc/mongodb-keyfile
    command: mongod --replSet rs0 --bind_ip_all --keyFile /etc/mongodb-keyfile --auth
    entrypoint:
      - bash
      - -c
      - |
        chmod 400 /etc/mongodb-keyfile
        chown 999:999 /etc/mongodb-keyfile
        exec docker-entrypoint.sh $$@        

  mongo-arbiter:
    image: mongo:6.0.3
    container_name: mongo-arbiter
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    volumes:
      - ./mongo-arbiter-data:/data/db
      - ./mongodb-keyfile:/etc/mongodb-keyfile
    command: mongod --replSet rs0 --bind_ip_all --keyFile /etc/mongodb-keyfile --auth
    entrypoint:
      - bash
      - -c
      - |
        chmod 400 /etc/mongodb-keyfile
        chown 999:999 /etc/mongodb-keyfile
        exec docker-entrypoint.sh $$@        

volumes:
  mongo1-data:
  mongo2-data:
  mongo3-data:
  mongo-arbiter-data:

mongodb-keyfile 它用于 MongoDB 节点之间的认证。你可以使用以下命令生成一个密钥文件:

shell

openssl rand -base64 756 > mongo.key

该命令将生成一个 756 字节的随机密钥并将其保存到 mongo.key 文件中。一定一定要修改 mongodb-keyfile 文件为 400 权限

shell

chmod 400 mongodb-keyfile

执行 docker compose up -d 启动容器。将启动四个 MongoDB 实例:

  • mongo1:主节点(Primary)
  • mongo2:从节点(Secondary)
  • mongo3:从节点(Secondary)
  • mongo-arbiter:选举节点(Arbiter)

容器列表
容器列表

连接到其中一个 MongoDB 实例(例如 mongo1):

shell

docker exec -it mongo1 mongo -u root -p example

在 MongoDB shell 中,运行以下命令来初始化副本集:

js

rs.initiate({
    _id: "rs0",
    members: [
        {_id: 0, host: "mongo1:27017"},  // 主节点
        {_id: 1, host: "mongo2:27017"},  // 从节点
        {_id: 2, host: "mongo3:27017"},  // 从节点
        {_id: 3, host: "mongo-arbiter:27017", arbiterOnly: true}  // 选举节点
    ]
})

设置副本
设置副本

过一两分钟,在 MongoDB shell 中,运行以下命令检查副本集的状态:

js

rs.status()

会看到:

  • mongo1 被选为主节点(Primary)。
  • mongo2 和 mongo3 是从节点(Secondary)。
  • mongo-arbiter 是选举节点(Arbiter)。

副本状态
副本状态

在主节点上插入数据,连接到主节点(mongo1):

shell

docker exec -it mongo1 mongosh -u root -p example

插入一些数据:

shell

use testdb
db.testcollection.insert({name: "test"})

插入数据
插入数据

在从节点上查询数据,连接到从节点(例如 mongo2):

shell

docker exec -it mongo2 mongosh -u root -p example

在 MongoDB shell 中,运行以下命令以允许从节点读取数据:

js

rs.secondaryOk()

然后查询数据:

shell

use testdb
db.testcollection.find()

副本读取主节点插入的数据
副本读取主节点插入的数据

能够看到在主节点上插入的数据。

选举节点(Arbiter)不存储数据,仅用于选举。可以通过停止主节点来测试选举过程:

停止主节点(mongo1):

shell

docker stop mongo1

stop主节点
stop主节点

连接到另一个从节点(例如 mongo2),检查副本集状态:

shell

docker exec -it mongo2 mongosh -u root -p example
rs.status()

会看到新的主节点被选举出来(可能是 mongo2 或 mongo3)。

从节点选举成功
从节点选举成功

rs.slaveOk() 是 MongoDB 早期版本(3.x 及之前)中用于允许从节点(Secondary)读取数据的命令。从 MongoDB 4.0 开始,这个命令已经被弃用,取而代之的是 rs.secondaryOk()

默认情况下,MongoDB 副本集的从节点(Secondary)是不允许客户端直接读取数据的。这是为了防止应用程序意外读取到未同步完成的数据。如果希望从从节点读取数据,需要显式地调用 rs.secondaryOk() 来允许读取操作。

  1. 是否可以省略?

如果不调用 rs.secondaryOk(),客户端将无法从从节点读取数据,只能从主节点(Primary)读取。如果的应用程序只需要从主节点读取数据,那么可以省略 rs.secondaryOk()

  1. 自动同步数据到副本

MongoDB 副本集会自动将数据从主节点同步到从节点,这是副本集的核心功能之一。但是,数据同步和允许读取是两个不同的概念。即使数据已经同步到从节点,客户端默认也不能直接从从节点读取数据,除非显式调用 rs.secondaryOk()

如果希望从从节点读取数据,可以在连接 MongoDB 时设置读取偏好(Read Preference),或者在 MongoDB Shell 中调用 rs.secondaryOk()

方法 1:在 MongoDB Shell 中调用 rs.secondaryOk()

连接到从节点后,运行以下命令:

javascript

rs.secondaryOk()

然后就可以从从节点读取数据了。

方法 2:在客户端设置读取偏好

如果使用的是 MongoDB 驱动程序(如 Node.js、Python 等),可以在连接字符串或客户端配置中设置读取偏好为 secondarysecondaryPreferred。例如:

  • Node.js 示例:

    javascript

    const { MongoClient } = require('mongodb');
    
    const uri = 'mongodb://root:example@mongo1:27017,mongo2:27017,mongo3:27017/testdb?replicaSet=rs0';
    const client = new MongoClient(uri, {
      readPreference: 'secondaryPreferred' // 优先从从节点读取
    });
    
    async function run() {
      try {
        await client.connect();
        const db = client.db('testdb');
        const collection = db.collection('testcollection');
        const result = await collection.find({}).toArray();
        console.log(result);
      } finally {
        await client.close();
      }
    }
    
    run().catch(console.dir);
  • Python 示例:

    python

    from pymongo import MongoClient
    
    client = MongoClient(
        'mongodb://root:example@mongo1:27017,mongo2:27017,mongo3:27017/testdb?replicaSet=rs0',
        read_preference='secondaryPreferred'  # 优先从从节点读取
    )
    
    db = client.testdb
    collection = db.testcollection
    result = collection.find({})
    for doc in result:
        print(doc)