1. 序言

  • 本博客是上一博客的进阶版:mac M2安装单机版 MongoDB 7.x,上一博客可以看做是单机、单节点部署MongoDB
  • 本博客将介绍单机、多服务部署MongoDB,实际就是伪分布式部署

2. 副本集(Replica Set)方式部署

2.1 什么是副本集?

  • 多节点部署、主从部署等其实都是为了 MongoDB 的高可用,支持数据多副本、读写分离等
  • 较新版本的MongoDB Master/slave 被废弃,通过副本集提供高可用和数据冗余
    "msg":"Master/slave replication is no longer supported"
    
  • 一个副本集由多个节点组成,其中一个节点是主节点(Primary),其余是从节点(Secondary)
  • 主节点处理所有的写操作,从节点复制主节点的数据并可以处理读操作(如果启用了读偏好)
  • 副本集的特点:
    • 高可用性: 如果主节点发生故障,从节点会自动选举一个新的主节点。
    • 数据冗余:数据在多个节点上复制,提供数据冗余。
    • 读扩展:可以配置从节点处理读操作,以减轻主节点的负载。

2.2 副本集部署

2.2.1 启动3个MongoDB服务

  • 在本机以前台进程的方式启动3个 MongoDB 服务,且指定副本集为 rs0

    mongod --replSet rs0 --dbpath /Users/bytedance/mongodb11/data --port 27031 --bind_ip_all
    
    mongod --replSet rs0 --dbpath /Users/bytedance/mongodb12/data --port 27032 --bind_ip_all
    
    mongod --replSet rs0 --dbpath /Users/bytedance/mongodb13/data --port 27033 --bind_ip_all
    
  • 如果想通过配置文件部署,服务1的配置文件如下

    # 数据存储相关配置
    storage:
      dbPath: /Users/bytedance/mongodb2/data
      journal:
        enabled: true
    
    # 网络相关配置
    net:
      port: 27030
      bindIp: 0.0.0.0  # 允许从所有IP地址访问
    
    # 副本集相关配置
    replication:
      replSetName: "rs0"
    
    # 日志相关配置
    systemLog:
      destination: file
      logAppend: true
      logRotate: rename
      path: /Users/xxx/mongodb11/log/mongo.log
    
    # 进程管理相关配置
    processManagement:
      fork: true  # 以守护进程方式运行(后台运行)
    
    # 安全相关配置
    security:
      authorization: disabled  # 关闭用户认证
    

2.2.2 初始化副本集

  • 通过mongosh登录访问其中一个服务:

    mongosh --port 27032
    
  • 执行如下命令初始化副本集合:

    rs.initiate({
      _id: "rs0",
      members: [
        { _id: 0, host: "localhost:27031" },
        { _id: 1, host: "localhost:27032" },
        { _id: 2, host: "localhost:27033" }
      ]
    })
    
  • 执行rs.status() 验证副本集状态

    rs0 [direct: primary] test> rs.status()
    {
      set: 'rs0',
      ... # 其他信息省略
      members: [
        {
          _id: 0, # initiate 时指定的id
          name: 'localhost:27031',
          health: 1,
          state: 2,
          stateStr: 'SECONDARY', # 从节点
          ... # 其他信息省略
        },
        {
          _id: 1,
          name: 'localhost:27032',
          health: 1,
          state: 1,
          stateStr: 'PRIMARY', # 主节点
          ... # 其他信息省略
        },
        {
          _id: 2,
          name: 'localhost:27033',
          health: 1,
          state: 2,
          stateStr: 'SECONDARY', # 从节点
          ... # 其他信息省略
        }
      ],
      ok: 1,
      ... # 其他信息省略
    }
    
  • 还可以通过 rs.isMaster() 查看当前访问的服务是不是primary节点

    rs0 [direct: primary] test> rs.isMaster()
    {
      ... # 其他信息省略
      hosts: [ 'localhost:27031', 'localhost:27032', 'localhost:27033' ],
      setName: 'rs0',
      setVersion: 1,
      ismaster: true, # 当前访问的是primary节点(服务)
      secondary: false,
      primary: 'localhost:27032',
      me: 'localhost:27032',
      .. # 其他信息省略
      readOnly: false,
      ... # 其他信息省略
      isWritablePrimary: true # 只能通过primary节点写入
    }
    

2.3 副本集中数据的读写

  • 从上面的信息就可以看出, 副本集的可以支持设置读写偏好、支持读写分离

  • 例如,isWritablePrimary: true 只能在primary节点写入,若在非primary节点插入数据将报错

    Uncaught:
    MongoBulkWriteError[NotWritablePrimary]: not primary
    
  • 在primary节点插入数据

    db.test_data.insertMany([
      { name: "Bob", age: 25, sex: "male", city: "Los Angeles" },
      { name: "Carol", age: 28, sex: "female", city: "Chicago" },
      { name: "Dave", age: 35, sex: "male", city: "San Francisco" }
    ])
    
  • 在任意节点查询数据

    db.test_data.find({'name':"Bob"})
    

3. 部署分片(Sharding)集群

3.1 什么是分片?

  • 在数据存储系统中,除了支持副本集(多副本存储全量数据),还支持分片(Sharding),MongoDB也不例外
  • 分片是 MongoDB 提供的一种水平扩展机制,用于将数据分布在多个服务器上
  • 分片具备以下特性:
    • 水平扩展:通过将数据分布在多个分片上,解决单个服务器的存储和性能限制。
    • 负载均衡:数据和请求可以在多个分片之间均匀分布。
    • 高可用性:结合副本集使用,每个分片可以是一个副本集,从而提供高可用性。

3.2 副本集 vs 分片

  • 相对副本集,分片会按照某种规则将数据拆分成多个split,每个split存储到相应的分片
  • 以销售数据为例,形象化比喻:
    • 副本集就是一个机房有多台机器,每台机器都将存储中国市场的销售数据
    • 分片就是存在多个机房,每个机房存储只存储所在区域的销售数据。例如,华北机房只存储华北地区的销售数据,西南机房只存储西南地区的销售数据
    • 同时,为了提供高可用和数据冗余,分片存储时,每个机房需要有多台机器,支持多副本储存该区域的销售数据
    • 也就是说,一个分片就是一个副本集,分片内部通过副本集实现高可用和数据冗余机制

3.3 部署分片集群(副本集与分片的结合使用)

  • 分片集群由配置服务器(Config Servers)、分片服务器(Shards)、和路由服务器(mongos)组成
    • 配置服务器作为副本集:配置服务器存储集群的元数据,为了确保配置服务器的高可用性,配置服务器也通常设置为一个副本集
    • 每个分片作为一个副本集:在分片集群中,每个分片通常是一个副本集。这意味着每个分片不仅能存储数据的一部分,还能提供高可用性和数据冗余。
    • 路由服务器(mongos):mongos不存储数据,而是作为路由器将客户端请求路由到适当的分片,可以部署多个mongos实例以提供高可用性和负载均衡

3.3.1 部署配置服务器

  • 使用如下命令,在本地启动三个配置服务器,注意提前创建好data目录

    • --configsvr:表示这是一个配置服务器

    • --replSet rs0:设置副本集,配置服务器的本质是一个副本集

      
      mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb1/data/configdb --port 27019 --bind_ip_all
      
      mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb2/data/configdb --port 27020 --bind_ip_all
      
      mongod --configsvr --replSet rs0 --dbpath /Users/xxx/mongodb3/data/configdb --port 27021 --bind_ip_all
      
  • 访问某个配置服务,初始化副本集

    mongosh --port 27019 # 访问配置服务
    
    rs.initiate({
      _id: "rs0",
      configsvr: true,
      members: [
        { _id: 0, host: "localhost:27019" },
        { _id: 1, host: "localhost:27020" },
        { _id: 2, host: "localhost:27021" }
      ]
    })
    
  • PS: 若以后台服务方式启动,上述启动方式对应的配置文件如下

    storage:
      dbPath:  /Users/xxx/mongodb1/data/configdb
      journal:
        enabled: true
    # 网络相关配置
    net:
      port: 27010
      bindIp: 0.0.0.0  # 允许从所有IP地址访问
    # 复制集相关配置
    replication:
      replSetName: "rs0"
    # 设置role:分片集群中的配置服务器
    sharding:
      clusterRole: "configsvr"
    # 日志相关配置
    systemLog:
      destination: file
      logAppend: true
      logRotate: rename
      path: /Users/xxx/mongodb1/log/configd/mongo.log
    # 进程管理相关配置
    processManagement:
      fork: true  # 以守护进程方式运行(后台运行)
    # 安全相关配置
    security:
      authorization: disabled  # 关闭用户认证
    

3.3.2 部署分片服务器

shard1
  • 使用如下命令,在本地启动三个分片服务器,注意提前创建好data目录

    mongod --shardsvr --replSet shard1 --dbpath /Users/bytedance/mongodb1/data/shard --port 27022 --bind_ip_all
    
    mongod --shardsvr --replSet shard1 --dbpath  /Users/bytedance/mongodb2/data/shard --port 27023 --bind_ip_all
    
    mongod --shardsvr --replSet shard1 --dbpath  /Users/bytedance/mongodb3/data/shard --port 27024 --bind_ip_all
    
  • 访问其中某个分片服务器,初始化分片信息

    mongosh --port 27022
    
    rs.initiate({
      _id: "shard1",
      members: [
        { _id: 0, host: "localhost:27022" },
        { _id: 1, host: "localhost:27023" },
        { _id: 2, host: "localhost:27024" }
      ]
    })
    
shard2
  • 使用如下命令,在本地启动三个分片服务器,注意提前创建好data目录

    mongod --shardsvr --replSet shard2 --dbpath /Users/bytedance/mongodb1/data/shard2 --port 27025 --bind_ip_all
    
    mongod --shardsvr --replSet shard2 --dbpath  /Users/bytedance/mongodb2/data/shard2 --port 27026 --bind_ip_all
    
    mongod --shardsvr --replSet shard2 --dbpath  /Users/bytedance/mongodb3/data/shard2 --port 27027 --bind_ip_all
    
  • 访问其中某个分片服务器,初始化分片信息

    mongosh --port 27025
    
    rs.initiate({
     _id: "shard2",
     members: [
       { _id: 0, host: "localhost:27025" },
       { _id: 1, host: "localhost:27026" },
       { _id: 2, host: "localhost:27027" }
     ]
    })
    

3.3.4 部署mongos

  • 先创建好mongos所需的相关目录

    mkdir /Users/xxx/mongos
    cd /Users/xxx/mongos
    mkdir data log etc
    
  • 在etc目录下创建mongos的配置文件,mongos-config.yaml

    net:
      bindIp: 0.0.0.0
      port: 27017
    
    sharding:
      configDB: rs0/localhost:27019,localhost:27020,localhost:27021
    
  • 以前台方式启动mongos

    mongos --config /Users/xxx/mongos/etc/mongos-config.yaml
    
  • 访问mongos,添加分片(之前部署好的分片服务器)

    mongosh --port 27017
    
    sh.addShard("shard1/localhost:27022,localhost:27023,localhost:27024")
    sh.addShard("shard2/localhost:27025,localhost:27026,localhost:27027")
    
  • 通过 sh.status() 查看shard信息

    # 关键信息
    shards
    [
      {
        _id: 'shard1',
        host: 'shard1/localhost:27022,localhost:27023,localhost:27024',
        state: 1,
        topologyTime: Timestamp({ t: 1727872634, i: 3 })
      },
      {
        _id: 'shard2',
        host: 'shard2/localhost:27025,localhost:27026,localhost:27027',
        state: 1,
        topologyTime: Timestamp({ t: 1727939236, i: 4 })
      }
    ]
    

3.5 启用分片

3.5.1 启用数据库分片和集合分片

  • 启用数据库分片和集合分片

    use admin # 一定要先切换到admin,
    sh.enableSharding("test")
    
    use test
    sh.shardCollection("test.test_data", {user_id: 1}) # user_id作为分片的key
    
  • 验证是否成功开启集合分片

    use test
    db.test_data.getShardDistribution()
    
  • 若有如下输出,说明开启成功

3.5.2 插入数据并查看

  • 插入100条数据

    # 数据示例 -- user_id: id000001; name: name1
    for (let i = 1; i <= 2000; i++) {
     let user_id = `id${String(i).padStart(6, '0')}`;  
     let name = `name${i}`;  
     db.test_data.insertOne({ user_id: user_id, name: name });
    }
    
  • 执行sh.status()查看分片信息,但数据只分布到了shard2,要么是集群部署存在问题,要么跟公司的 “chatGPT” 回答一样

    • 分片键选择不当:如果分片键的值分布不均匀,可能会导致数据集中在一个分片上。例如,如果您的分片键是一个递增的字段(如时间戳或自增 ID),那么所有新插入的数据都会集中在同一个分片上
    • 数据量不足:在数据量较小的情况下,MongoDB 可能不会立即将数据分布到多个分片上。MongoDB 需要一定的数据量来决定何时进行分片 (笔者倾向于该原因)
       ... # 其他信息省略,可以看到 'test.test_data' 集合成功开启了分片
       collections: {
          'test.test_data': {
            shardKey: { user_id: 1 },
            unique: false,
            balancing: true,
            chunkMetadata: [ { shard: 'shard2', nChunks: 1 } ],
            chunks: [
              { min: { user_id: MinKey() }, max: { user_id: MaxKey() }, 'on shard': 'shard2', 'last modified': Timestamp({ t: 1, i: 0 }) }
            ],
            tags: []
          }
        }
    
  • PS: 公司的 “chatGPT” 还给出了其他的查看分片信息的方法,但无任何输出

    use config
    db.chunks.find({ ns: "testDB.test_data" }).sort({ min: 1 }).pretty()
    

3.5.3 在shard2的各服务器查询数据

  • 由于上面的集合只分布在shard2,现在连接shard2的任意服务器,执行如下查询命令

    use test
    db.test_data.find({'name':"name1"})
    
  • 均能从shard2的每个服务器查到数据,这也说明了,shard2分片自身就是一个副本集


4. 后记

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部