介绍副本集、分片、备份等内容

本文所使用的MongoDB版本为3.4.2

副本集

建立

在本地启动所有成员,分别设置不同的路径和端口,使用相同的标识符student

# terminal-1
mongod --dbpath ~/Desktop/db --port 27017 --replSet "student"

# terminal-2
mongod --dbpath ~/Desktop/db-rs0 --port 27016 --replSet "student"

# terminal-3
mongod --dbpath ~/Desktop/db-rs1 --port 27015 --replSet "student"

配置

连接一个节点

mongo --port 27017

创建配置对象与初始化

> var config = {
    "_id" : "student",
    "members" : [
      {"_id": 0, "host": "127.0.0.1:27017"},
      {"_id": 1, "host": "127.0.0.1:27016"},
      {"_id": 2, "host": "127.0.0.1:27015"}
    ]
}
> rs.initiate(config)

所连接的节点解析这个对象后会将配置文件发给其他成员,所有成员配置完成后会自动选出一个主节点(primary member),其他成员为备份节点(secondary member)。

这里27017端口的副本集被选为主节点,其他两个为备份节点,先在主节点中添加一条数据。

student:PRIMARY> db.stu.insert({"name":"yrq","grade":"3","class":"7"})

默认情况下备份节点是不可读的,需要执行db.setSlaveOk()后才可读取数据。

> mongo --port 27016
student:SECONDARY> db.stu.find()
Error: error: {
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotMasterNoSlaveOk"
}
student:SECONDARY> db.setSlaveOk()
student:SECONDARY> db.stu.find()
{ "_id" : ObjectId("58f58ded93e9bbcb0bceb3c5"), "name" : "yrq", "grade" : "3", "class" : "7" }

查看配置与状态

  1. rs.conf()

    student:PRIMARY> rs.conf()
    {
      "_id" : "student",
      "version" : 1,       # 每个修改配置时这个值都会自增+1
      "protocolVersion" : NumberLong(1),  
      "members" : [        # 副本集成员列表
        {
          "_id" : 0,
          "host" : "127.0.0.1:27017",        
          "arbiterOnly" : false,        
          "buildIndexes" : true,
          "hidden" : false,           
          "priority" : 1,
          "tags" : {
    
          },
          "slaveDelay" : NumberLong(0),
          "votes" : 1
        },
        {
          "_id" : 1,
          "host" : "127.0.0.1:27016",
          "arbiterOnly" : false,
          "buildIndexes" : true,
          "hidden" : false,
          "priority" : 1,
          "tags" : {
    
          },
          "slaveDelay" : NumberLong(0),
          "votes" : 1
        },
        {
          "_id" : 2,
          "host" : "127.0.0.1:27015",
          "arbiterOnly" : false,
          "buildIndexes" : true,
          "hidden" : false,
          "priority" : 1,
          "tags" : {
    
          },
          "slaveDelay" : NumberLong(0),
          "votes" : 1
        }
      ],
      "settings" : {
        "chainingAllowed" : true,
        "heartbeatIntervalMillis" : 2000,
        "heartbeatTimeoutSecs" : 10,
        "electionTimeoutMillis" : 10000,
        "catchUpTimeoutMillis" : 2000,
        "getLastErrorModes" : {
    
        },
        "getLastErrorDefaults" : {
          "w" : 1,
          "wtimeout" : 0
        },
        "replicaSetId" : ObjectId("58f58a448ab72530955abf84")
      }
    }
    
  2. db.isMaster()

    查看当前mongod实例的角色,若mongod实例是副本集中的成员,则通过ismastersecondary可以判断它是主节点还是备份节点。

    student:PRIMARY> db.isMaster()
    {
      "hosts" : [
        "127.0.0.1:27017",
        "127.0.0.1:27016",
        "127.0.0.1:27015"
      ],
      "setName" : "student",        
      "setVersion" : 1,
      "ismaster" : true,        # 是否为主节点
      "secondary" : false,      # 是否为备份节点
      "primary" : "127.0.0.1:27017",   # 主节点成员
      "me" : "127.0.0.1:27017",        # 自己
      ...
    }
    
  3. rs.status()

    显示当前副本集的状态。

    student:PRIMARY> rs.status()
    {
      "set" : "student",
      "date" : ISODate("2017-04-18T04:03:52.781Z"),
      "myState" : 1,
      "term" : NumberLong(1),
      "heartbeatIntervalMillis" : NumberLong(2000),
      ...
      "members" : [
        {
          "_id" : 0,
          "name" : "127.0.0.1:27017",
          "health" : 1,
          "state" : 1,     # 1表示主节点,可进行写操作,有投票资格
          "stateStr" : "PRIMARY",
          ...
          "self" : true
        },
        {
          "_id" : 1,
          "name" : "127.0.0.1:27016",
          "health" : 1,
          "state" : 2,    # 2表示备份节点,有投票资格
          "stateStr" : "SECONDARY",
          ...
        },
        {
          "_id" : 2,
          "name" : "127.0.0.1:27015",
          "health" : 1,
          "state" : 2,
          "stateStr" : "SECONDARY",
          ...
        }
      ],
      "ok" : 1
    }
    

修改配置

  1. 添加成员

    启动一个成员

    mongod --dbpath ~/Desktop/db-rs2 --port 27014 --replSet "student"
    

    添加到副本集中

    student:PRIMARY> rs.add("127.0.0.1:27014")
    { "ok" : 1 }
    student:PRIMARY> rs.status().members.length
    4
    
  2. 删除成员

    student:PRIMARY> rs.remove("127.0.0.1:27014")
    { "ok" : 1 }
    student:PRIMARY> rs.status().members.length
    3
    
  3. 将主节点降级为备份节点

    student:PRIMARY> rs.stepDown()
    
  4. 阻止选举

    若主节点要进行维护,不需要再这期间选举新的主节点,可以在每个备份节点执行freeze命令冻结状态,始终为备份节点。

    student:SECONDARY> rs.freeze(1000000)
    

    输入参数为秒数,需要释放时执行rs.freeze(0)即可。

设计

仲裁成员

仲裁成员(arbiter)不保存数据,不为客户端提供服务,仅参与投票选举,避免出现平票的情况,可部署在性能较差的服务器上。 添加仲裁成员的方法:

student:PRIMARY> rs.addArb("127.0.0.1:27014")

student:PRIMARY> rs.add({"_id":3,"host":"127.0.0.1:27014","arbiterOnly":true})

隐藏成员

隐藏成员对用户不可见,不为客户端提供服务。只有优先级为0的成员才能被隐藏,

隐藏一个成员

student:PRIMARY> var config = rs.conf()
student:PRIMARY> config.members[2].hidden = true;
true
student:PRIMARY> config.members[2].priority = 0;
0
student:PRIMARY> rs.reconfig(config)

隐藏成员对isMaster()是不可见的,对status()和conf()是可见的。

student:PRIMARY> rs.status().members.length
3
student:PRIMARY> rs.conf().members.length
3
student:PRIMARY> db.isMaster().hosts.length
2

取消隐藏需要将hidden属性设为false或者删掉hidden。

延迟备份

config中的每个成员都有一个slaveDelay选项,用来设置备份节点同步主节点的延迟,默认为0,表示当主节点有操作改变时立即同步。

设置延迟可以防止对主节点的误操作,如删除或有严重bug的情况。

设置这个选项要求成员的优先级为0。

创建索引

有时备份节点并不需要像主节点拥有索引,可以通过设置"buildIndexes" : false来阻止备份节点创建索引,这个修改是永久操作,无法再次恢复为可拥有索引的状态。

设置这个选项要求成员的优先级为0。

同步

复制功能是使用操作日志oplog实现的,操作日志包含了主节点每一次写操作,备份节点查询oplog获取需要执行的操作,然后在自己的数据集上执行操作。

心跳

每个成员每隔一段时间都会向其它成员发送心跳请求(heartbeat request),用于检查每个成员的状态。

成员状态

序号 名称 描述
0 STARTUP 成员刚启动的状态,若继续加载配置则进入STARTUP2状态
1 PRIMARY 主节点,有投票资格
2 SECONDARY 备份节点,有投票资格
3 RECOVERING 成员在进行启动自检,或刚完成回滚或重同步操作的过渡,有投票资格
5 STARTUP2 已启动在初始化过程中
6 UNKNOWN 状态对其它成员是未知的
7 ARBITER 仲裁成员,不复制数据,仅参与选举
8 DOWN 其它成员不可达
9 ROLLBACK 成员正在执行回滚操作,不能进行读操作
10 REMOVED 成员被移出副本集

分片

作用

  • 增加可用RAM
  • 增加可用磁盘空间
  • 减轻单台服务器的负载
  • 处理单个mongod无法承受的吞吐量

分片集群

每个分片集群都由三个部分组成:

  1. 分片(shard) - 每个分片包含分片数据中的一个子集,分片可以是副本集。
  2. mongos - mongos作为查询数据的路由,提供客户端与分片集群间的接口。
  3. 配置服务器(config server) - 保存集群的元数据与配置,在3.4版本后要求配置服务器必须为副本集。

生产环境中的配置:

  1. 3个及以上的配置服务器
  2. 3个及以上的分片
  3. 1个及以上的mongos路由

开发环境中的配置:

  1. 一个配置服务器(副本集)
  2. 至少一个分片(副本集)
  3. 一个mongo实例

启动

端口设计:

  • 27014~27016 配置服务器
  • 27001~27003 分片1(副本集)
  • 27004 分片2
  • 27000 mongos
  1. 创建配置服务器副本集

    mongos需要从配置服务器中获取配置信息,因此需要优先启动.

    # terminal-1
    mongod --configsvr --dbpath ~/Desktop/db-conf1 --port 27016 --replSet "config"
    
    # terminal-2
    mongod --configsvr --dbpath ~/Desktop/db-conf2 --port 27015 --replSet "config"
    
    # terminal-3
    mongod --configsvr --dbpath ~/Desktop/db-conf3 --port 27014 --replSet "config"
    

    配置服务器不需要分配太大的空间,它存放数据分布表.

    初始化副本集:

    > rs.initiate(
    ... {
    ...   _id: "config",
    ...   configsvr: true,
    ...   members: [
    ...     { _id : 0, host : "127.0.0.1:27016" },
    ...     { _id : 1, host : "127.0.0.1:27015" },
    ...     { _id : 2, host : "127.0.0.1:27014" }
    ...   ]
    ... })
    
  2. 创建分片 创建副本集分片

    # terminal-4
    mongod --shardsvr --dbpath ~/Desktop/db-sh1 --port 27001 --replSet "shard"
    
    # terminal-5
    mongod --shardsvr --dbpath ~/Desktop/db-sh2 --port 27002 --replSet "shard"
    
    # terminal-6
    mongod --shardsvr --dbpath ~/Desktop/db-sh3 --port 27003 --replSet "shard"
    

    配置副本集

    > rs.initiate(
    ... {
    ...   _id: "shard",
    ...   members: [
    ...     { _id : 0, host : "127.0.0.1:27001" },
    ...     { _id : 1, host : "127.0.0.1:27002" },
    ...     { _id : 2, host : "127.0.0.1:27003" }
    ...   ]
    ... })
    

    创建普通分片

    # terminal-7
    mongod --shardsvr --dbpath ~/Desktop/db-sh4 --port 27004
    
  3. 启动mongos

    mongos --configdb config/127.0.0.1:27016,127.0.0.1:27015,127.0.0.1:27014
    

    config为配置服务器副本集名称,斜杠后为具体数据集地址。

    连接到mongos

    mongo --port 27000
    
  4. 添加分片

    mongos> sh.addShard("shard/127.0.0.1:27001,127.0.0.1:27002,127.0.0.1:27003")
    mongos> sh.addShard("127.0.0.1:27004")
    

    shard为分片副本集名称,斜杠后为具体数据集地址,若为普通数据集则不用添加名称。

  5. 查看状态

    mongos> sh.status().shards
    --- Sharding Status ---
    ...
    shards:
    {  "_id" : "shard",  "host" : "shard/127.0.0.1:27001,127.0.0.1:27002,127.0.0.1:27003",  "state" : 1 }
    {  "_id" : "shard0001",  "host" : "127.0.0.1:27004",  "state" : 1 }
    ...
    databases:
    {  "_id" : "foo",  "primary" : "shard",  "partitioned" : false }
    

    可以看到有两个分片,一个是副本集"shard",一个是普通分片"shard0001"。 databases中的_id为数据库名。primary表示主分片,为随机选择的,所有分片都会在主分片上。partitioned表示是否启用了分片。

    先在mongos中操作添加一些数据

    mongos> for(var i=0;i<5000;i++){
    ... db.user.insert(
    ...   {
    ...     "name":"user_"+i,
    ...     "created": new Date()
    ...   });
    ... }
    
  6. 启用分片

    mongos> use foo
    switched to db foo
    mongos> sh.enableSharding("foo")
    
  7. 创建索引

    对集合分片时需要选择一个片键,为集合的一个键,分片时会根据这个片键拆分数据。只有具有索引的键才能成为片键,因此先要在想使用片键的键上创建索引。

    mongos> db.user.ensureIndex({"name":1})
    
  8. 对集合进行分片

    mongos> sh.shardCollection("foo.user",{"name":1})
    
  9. 分片后的状态

    mongos> sh.status().shards
    --- Sharding Status ---
    ...
    foo.user
      shard key: { "name" : 1 }
      unique: false
      balancing: true
      chunks:
        shard	1
      { "name" : { "$minKey" : 1 } } -->> { "name" : { "$maxKey" : 1 } } on : shard Timestamp(1, 0)
    

    可以看到分片后只有一个块,存储在分片"shard"上,"$minKey"与"$maxKey"表示键所对应值范围的端值,

  10. 查看块(chunk)的状态

    需要在config db下操作

    mongos> use config
    switched to db config
    mongos> db.chunks.find().pretty()
    {
      "_id" : "foo.user-name_MinKey",
      "ns" : "foo.user",
      "min" : {
        "name" : { "$minKey" : 1 }
      },
      "max" : {
        "name" : { "$maxKey" : 1 }
      },
      "shard" : "shard",
      "lastmod" : Timestamp(1, 0),
      "lastmodEpoch" : ObjectId("58f75c5b9cde39278500058c")
    }
    

片键

备份

文件系统快照

使用文件系统快照有两个条件:

  1. 需要文件系统本身支持快照(如Linux的LVM和Amazon EBS的EC2)。
  2. 启用journaling日记系统。

cp或rsync

若系统不支持快照,可直接使用cp、rsync或其它工具备份数据。

  1. 锁定数据库,禁止写入等操作

    > db.fsync()
    
  2. 复制数据库文件到指定目录

    $ cp -R ~/Desktop/db/* ~/Desktop/backup
    
  3. 复制完成后解锁数据库

    > db.fsyncUnlock()
    

mongodump

mongodump和mongorestore是备份小型mongo数据库的有效工具,不适合大型的系统的备份。

  1. 使用mongodump创建备份

    $ mongodump
    2017-04-10T23:20:46.221+0800	writing admin.system.version to
    2017-04-10T23:20:46.222+0800	done dumping admin.system.version (1 document)
    2017-04-10T23:20:46.223+0800	writing test.users to
    2017-04-10T23:20:46.223+0800	writing test.comments to
    2017-04-10T23:20:46.224+0800	done dumping test.comments (40 documents)
    2017-04-10T23:20:46.228+0800	done dumping test.users (1124 documents)
    

    可以设置–dbpath指定数据文件进行备份,若mongod正在运行则不要设置使用这个选项,

  2. 使用mongorestore恢复mongodump产生的备份

    $ mongorestore
    2017-04-10T23:22:26.789+0800	using default 'dump' directory
    2017-04-10T23:22:26.789+0800	preparing collections to restore from
    2017-04-10T23:22:26.790+0800	reading metadata for test.comments from dump/test/comments.metadata.json
    2017-04-10T23:22:26.791+0800	reading metadata for test.users from dump/test/users.metadata.json
    2017-04-10T23:22:26.791+0800	restoring test.comments from dump/test/comments.bson
    2017-04-10T23:22:26.792+0800	restoring test.users from dump/test/users.bson
    ...
    2017-04-10T23:22:26.842+0800	restoring indexes for collection test.users from metadata
    2017-04-10T23:22:26.843+0800	finished restoring test.users (1124 documents)
    2017-04-10T23:22:26.843+0800	done
    

参考