# 部署

MongoDB 常见的部署架构:

  • 单实例
  • 主从集群
    • 与 MySQL 的主从集群类似。
    • MongoDB 4.0 版本开始不支持部署主从集群,需要换成副本集群。
  • 副本集群
  • 分片集群

# 单实例

# 部署

  • 用 yum 安装:

    wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-server-4.0.5-1.el7.x86_64.rpm
    wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-shell-4.0.5-1.el7.x86_64.rpm
    wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-tools-4.0.5-1.el7.x86_64.rpm
    wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-mongos-4.0.5-1.el7.x86_64.rpm
    yum install -y mongodb-org-*.rpm
    rm -f mongodb-org-*.rpm
    

    然后启动:

    mongod                      # 启动 mongo 服务器
          -f /etc/mongod.conf   # 使用指定的配置文件
    
    • 服务器启动时,默认不会读取配置文件,在前台运行,监听端口 27017 ,
  • 或者用 docker-compose 部署:

    version: "3"
    
    services:
      mongo:
        container_name: mongo
        image: mongo:5.0.5
        command: [mongod, -f, /etc/mongod.conf]
        restart: unless-stopped
        environment:
          MONGO_INITDB_ROOT_USERNAME: root      # 创建管理员用户
          MONGO_INITDB_ROOT_PASSWORD: ******
        ports:
          - 27017:27017
        volumes:
          - /etc/localtime:/etc/localtime:ro
          - ./mongod.conf:/etc/mongod.conf
          - ./data:/data/db   # 官方镜像将 /data/db 目录声明为了 volume ,不挂载到该目录则会自动挂载一个匿名 volume
    
    • 需要调整挂载目录的权限:
      chown -R 999 data
      

# 管理

  • 终止 Mongo 服务器时,建议执行:

    mongod -f /etc/mongod.conf --shutdown
    

    或者在客户端执行:

    use admin
    db.shutdownServer()
    
  • 当 Mongo 在磁盘的数据文件损坏时,Mongo 可能启动成功,但读取该数据文件时会崩溃退出。

    • 常见原因:某个数据文件被误删、Mongo 被 kill 命令异常终止
    • 此时建议进行 repair 操作:
      mongod --repair --dbpath /data/db     # 直接修复原数据目录
      mongod --repair --dbpath /data/db --repairpath /data/db_new   # 将修复之后的数据文件保存到另一个目录
      
      • repair 会检查每个 collection-xx.wt 文件中的每个数据页 pages 并修复,然后重建 index-xx.wt 文件。每小时大概处理 150G 的磁盘文件。
      • repair 操作不能中断,否则不能启动 Mongo 。
  • 升级 Mongo 服务器版本时:

    • 如果沿用之前的数据目录,则只能升级到最近一个子版本。比如从 v4.0 升级到 V4.2 ,再升级到 v4.4 ,不能跨版本升级。
    • 通过 mongodump 迁移数据,则可以跨版本升级。

# 客户端

命令:

mongo [options] [server_uri] [script.js]    # 启动客户端
      -u <username>
      -p <password>
  • Mongo 服务器的 URI 采用以下格式:
    mongodb://[username:[email protected]]host1[:port1][,hostN[:portN]]...[/db][?options]]
    
    • 例:
      mongodb://127.0.0.1:27017/test                              # mongodb 客户端默认连接到 localhost:6379 服务器的 test 数据库
      mongodb://root:******@127.0.0.1:27017/test?authSource=admin # 传入用户名、密码,并通过 URI 参数指定验证数据库
      mongodb://localhost,localhost:27018,localhost:27019/admin   # 可以同时连接多个服务器
      
  • 启动客户端时,默认是打开一个 JavaScript 终端,因此可以执行 js 命令、定义变量、定义函数。
    • 也可以指定一个要执行的 js 脚本,或者从 stdin 传入要执行的 js 命令。如下:
      mongo $server_uri test.js
      echo 'show dbs' | mongo $server_uri
      

# 备份数据

# 导出

mongodump
          -h <host>[:port]
          --authenticationDatabase=admin
          -u <username>
          -p <password>

          -d <db>                 # 只处理指定的数据库(默认会导出所有数据库)
          -c <collection>         # 只处理指定的集合。只能指定一个集合,如果不指定则处理所有集合
          -j 4                    # 同时最多处理几个集合
          -q <expression>         # --query ,用一个 JSON 格式的表达式筛选文档,比如 '{year: "2021"}'
          -o <path>               # 导出之后保存到指定目录,默认为 ./dump
          --gzip                  # 导出时进行 gzip 压缩
  • 默认会导出所有数据库,在目标目录下按数据库名分别创建子目录,为每个集合保存 <collection>.bson<collection>.metadata.json 两个数据文件。
  • Mongo 的 WiredTiger 存储引擎默认会将文档、索引压缩之后才存储到磁盘,压缩率大概为 50% 。
    • 如果 mongodump 时不启用 gzip 压缩,则导出文件的体积大概为 Mongo 服务器占用磁盘的 200% 。
    • 如果 mongodump 时启用 gzip 压缩,则导出文件的体积大概为 50% 。不过该压缩文件不支持用 tar 命令解压。
    • 如果不压缩,则导出、导入的速度大概为每分钟 4G 。
mongoexport                 # 导出为 JSON 或 CSV 格式,兼容 mongodump 的大部分参数
          --out out.json    # 导出之后保存到指定文件,默认输出到 stdout
          --type json       # 导出格式,默认为 json
          --pretty          # 导出格式为 JSON 时,进行缩进排版。默认是每个文档占一行
          --limit <int>     # 限制导出的文档数

# 导入

  • mongodump 导出的数据要用 mongorestore 命令导入。而 mongoexport 导出的数据,以及其它来源的 JSON、CSV 数据,要用 mongoimport 命令导入。
mongorestore <path>         # 指定要导入的 BSON 文件。也可以指定目录,这会自动发现该目录下的 bson 文件,但不会发现子目录的
          -h <host>[:port]
          --authenticationDatabase=admin
          -u <username>
          -p <password>

          -d <db>
          -c <collection>
          -j 4              # 同时最多处理几个集合。并行数过多可能导致服务器过载,中断导入
          --gzip            # 导入时进行 gzip 解压
          --drop            # 在导入每个集合之前先删除同名的
          --stopOnError     # 遇到报错时立即停止(默认不会立即停止,且命令的返回码依然为 0 )
mongoimport <file>          # 指定文件,导入 JSON 或 CSV 格式的数据,兼容 mongorestore 的大部分参数

# 副本集群

  • MongoDB v4.0 开始弃用主从集群,只支持副本集群(Replication)。

  • 副本集群由多个 MongoDB server 组成,server 的角色分为三种:

    • Primary
      • :主节点,负责处理客户端的读、写请求。
      • 同时只能存在一个。
    • Secondary
      • :从节点,负责复制主节点的数据。
      • 可以部署一个或多个。
      • 主节点会将所有数据库操作记录在 oplog 中,而从节点会定期从主节点复制 oplog ,异步应用其中的操作。
      • 从节点不能处理客户端的写请求,但能处理读请求。
        • 不过从节点采用异步复制,可能没有最新的数据,因此客户端一般默认将读、写请求都发给主节点。
    • Arbiter
      • :仲裁节点。不同步数据(因此不容易出故障),仅参与投票,不能被选举。
      • 可以部署任意个(包括零个)。
  • 与 MongoDB 单实例相比,副本集群能大幅提高可用性。

    • 当主节点不可用时,其它节点会自动选出一个新的主节点。
    • 副本集中要有超过半数的节点在线,才能开始选举。
    • 选举过程有几秒长。
  • MongoDB 限制了一个副本集最多包含 50 个成员,其中最多 7 个投票成员。

    • 建议给副本集部署 1 个主节点 + 至少 2 个从节点。此时即使一个节点发生故障,副本集依然可以工作。
    • 与 zk 集群类似,副本集包含的投票节点的数量建议为奇数,为偶数时并不会提高可靠性。
  • oplog :操作日志(operation log),存储在 local 数据库的一个集合中。

    • oplog 记录的操作具有幂等性,可能将多个写操作合并为一个写操作。
    • oplog 的默认大小为磁盘空间的 5% 。

# 部署

  1. 集群节点之间默认采用密钥文件进行认证。先生成密钥文件:

    openssl rand -base64 512 > mongo.key
    chown mongod mongo.key
    chmod 400 mongo.key
    

    然后将密钥文件拷贝到各个节点。

  2. 在各个节点的配置文件中加入:

    replication:                      # 让 mongod 工作在副本集模式。默认为 Standalone 模式
      replSetName: rs0                # 副本集名称。同一副本集中的所有节点必须采用相同的 replSetName
    security:
      authorization: enabled
      keyFile: /etc/mongo/mongo.key   # 密钥文件的路径
    
  3. 登录一个节点,启动副本集:

    rs.initiate({
        _id: "rs0",             // 副本集的名称
        members: [              // 副本集的成员列表
            { _id: 0, host: "mongo-1:27017" },
            { _id: 1, host: "mongo-2:27017" },
            { _id: 2, host: "mongo-3:27017", priority: 0}
        ]
    })
    
    • 副本集必须初始化一次之后才能启动,也只需要初始化一次。
    • 不允许在 priority 为 0 的节点或仲裁节点上进行初始化。

# 管理

相关命令:

rs.initiate(cfg)                // 启动副本集,可传入一些配置参数进行初始化,其余的配置参数则采用默认值
rs.reconfig(cfg)                // 重新配置副本集,可覆盖已有的配置参数
rs.conf()                       // 显示副本集的配置
rs.status()                     // 显示副本集的状态

rs.add("mongo-1:27017")         // 添加从节点
rs.remove("mongo-1:27017")      // 删除从节点
rs.addArb("mongo-4:27017")      // 添加仲裁节点

db.printReplicationInfo()       // 查看 oplog 的状态
db.printSlaveReplicationInfo()  // 查看从节点的同步状态
  • 例:查看副本集的状态

    rs0:SECONDARY> rs.status()
    {
        "set" : "rs0",
        "date" : ISODate("2019-12-19T09:22:27.736Z"),
        "myState" : 1,
        "term" : NumberLong(1),
        "syncingTo" : "",
        "syncSourceHost" : "",
        "syncSourceId" : -1,
        "heartbeatIntervalMillis" : NumberLong(2000),
        "majorityVoteCount" : 2,
        "writeMajorityCount" : 2,
        ...
        "members" : [                       // 所有节点的信息
            {
                "_id" : 0,
                "name" : "mongo-1:27017",
                "ip" : "10.244.25.178",
                "health" : 1,               // 0 代表下线,1 代表在线
                "state" : 1,                // 该节点的状态
                "stateStr" : "PRIMARY",     // 该节点的状态描述
                "uptime" : 1798,            // 该节点的在线时长
                "optime" : {
                    "ts" : Timestamp(1576746303, 1),
                    "t" : NumberLong(1)
                },
                "optimeDate" : ISODate("2019-12-19T09:05:03Z"),  // 该节点最后一次同步 oplog 的时间
                "syncingTo" : "",
                "syncSourceHost" : "",
                "syncSourceId" : -1,
                "infoMessage" : "could not find member to sync from",
                "configVersion" : 1,
                "self" : true,              // 该节点是否为当前登录的节点
                "lastHeartbeatMessage" : ""
            },
            ...
    
    • 节点的状态分为以下几种,分别对应一个数字编号:
      0   STARTUP     # 已启动,没有加入副本集
      1   PRIMARY
      2   SECONDARY
      3   RECOVERING  # 恢复中,可能是正在启动或回滚
      5   STARTUP2    # 已启动,而且加入了某个副本集,正在进行初始同步。随后会变成 PRIMARY、SECONDARY 等状态
      6   UNKNOWN     # 其它节点不能判断该节点的状态
      7   ARBITER     # 仲裁者
      8   DOWN        # 其它节点不能访问该节点
      9   ROLLBACK    # 回滚中
      10  REMOVED     # 该节点从副本集中移除了
      
  • 例:修改副本集的配置

    rs0:PRIMARY> rs.conf()
    {
        "_id" : "rs0",
        "version" : 1,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
            {
                "_id" : 0,
                "host" : "mongo-2:27017",
                "arbiterOnly" : false,
                "buildIndexes" : true,
                "hidden" : false,  // 该节点是否隐藏。隐藏节点的 priority 为 0 ,不能被选举,但是可以投票
                "priority" : 1,    // 该节点在竞选主节点时的优先级,取值范围为 0.0~1000.0 。取值越大则越容易当选,取值为 0 则不会当选
                "tags" : {},
                "slaveDelay" : NumberLong(0),
                "votes" : 1        // 该节点是否有投票权
            },
            ...
    
    
    cfg = rs.conf()
    cfg.members[2].priority = 0
    rs.reconfig(cfg)
    

# 分片集群

  • 分片集群(Shard Cluster):在 MongoDB 中划分多个分片(shard),将数据分散存储到各个分片。这样方便横向扩容,能大幅提高数据容量、并发读写量。

  • 几种分片方式:

    • 哈希分片(Hash Sharding)
      • :选取 Mongo 文档的一个或复合字段作为 shard key ,计算 hash(shard_key) / shard_count,从而决定将文档分配到第几个分片。
      • 优点:分片很均匀,各分片的文档数量差不多。
      • 缺点:存储不连续。比如要查询一串 key 连续的文档时,需要分别读取多个分片,效率低。类似顺序读写磁盘与随机读写磁盘的区别。
    • 范围分片(Range Sharding)
      • :将 shard key 取值分为几个范围,与几个分片一对一。比如 key 取值为 0~10000 的文档都分配到第 1 个分片。
      • 优点:存储连续。
      • 缺点:分片不均匀。
    • 区域分片(Zone Sharding)
      • :将 shard key 取值分为几个区域,每个区域可以分配一个或多个分片。
      • 优点:与范围分片相比,可以分片得更均匀。
  • 分片集群需要部署 3 种服务器,每种服务器都可以部署多实例以提高可用性:

    • shard mongo
      • :负责存储分片的数据。
      • 每个分片可以部署一个 mongo 单实例,或副本集群。
    • config server
      • :负责存储集群的元数据、配置信息。
      • 启动 mongod 进程时启用 configsvr 配置,即可担任 config server 。
    • mongos
      • :负责路由。从 config mongo 获取集群信息,将客户端的请求转发到 shard mongo 。
  • shard mongo 以 chunk 为单位存储数据。

    • 默认限制了 chunkSize 为 64MB 。如果写入新文档之后,一个 chunk 超过最大体积,则自动拆分成两个 chunk 。
    • config server 会运行一个 balancer 进程,监控各个 shard 的 chunk 数量,自动迁移 chunk 以使得它们的 chunk 数量差不多,实现负载均衡。
      • 迁移需要遵循分片规则。
      • 如果 chunk size 设置得较小,则容易经常迁移,导致大量读写磁盘、网络传输,增加服务器负载。