副本集配置

副本集配置总是以一个文档的形式保存在local.system.replSet集合中。副本集中所有成员的这个文档都是相同的。绝对不要使用update更新这个文档,应该使用rs辅助函数或者replSetReconfig命令修改副本集配置。

创建副本集

创建副本集的步骤很简单,首先启动所有成员服务器,然后使用rs.initiate命令将配置文件传递给其中一个成员:

var config = {
	"_id": setName,
	"members": [
		{"_id": 0, "host": host1},
		{"_id": 1, "host": host2},
		{"_id": 2, "host": host3}
	]
}

rs.initiate(config)

应该总是传递一个配置对象给rs.initiate,否则MongoDB会自动生成一个针对单成员副本集的配置,其中的主机名可能不是你希望的。
只需要对副本集中的一个成员调用rs.initiate就可以了。收到initiate命令的成员会自动将配置文件传递给副本集中的其他成员。

修改副本集成员

连接到主节点并且添加新成员:

rs.add("spock:27017")

也可以以文档的形式为新成员指定更复杂的配置:

rs.add({"_id":5,"host":"spock:27017","priority": 0,"hidden":true})

可以根据"host"字段将成员从副本集中移除:

rs.remove("spock:27017")

可以通过rs.reconfig修改副本集成员的配置。修改副本集成员配置时,有几个限制需要注意:

  • 不能修改成员的"_d"字段;
  • 不能将接收rs.reconfig命令的成员(通常是主节点)的优先级设为0;
  • 不能将仲裁者成员变为非仲裁者成员,反之亦然;
  • 不能将"buildIndexes":false的成员修改为"buildIndexes":true。

需要注意的是,可以修改成员的"ost"字段。这意味着,如果为副本集成员指定
了不正确的主机名(比如使用了公网P而不是内网P),之后可以重新修改成员的主机名。

var config = rs.config()
config.members[0].host = "spock:27017"

rs.reconfig(config)

修改其他选项的方式也是一样的:使用rs.config得到当前配置文件,修改配置文件,将修改后的配置文件传递给rs.reconfig就可以了。

创建比较大的副本集

副本集最多只能拥有12个成员,其中只有7个成员拥有投票权。这是为了减少心跳请求的网络流量(每个成员都要向其他所有成员发送心跳请求)和选举花费的时间。
实际上,副本集还有更多的限制,如果要创建7个以上成员的副本集,只有7个成员可以拥有投票权,需要将其他成
员的投票数量设置为0:

rs.add({"_id":7, "host":"server-7:27017", "votes": 0})

这样可以阻止这些成员在选举中投主动票,虽然它们仍然可以投否决票。应该尽量避免修改成员的投票数量。投票可能会对选举和一致性产生怪异的、不直观的影响。应该只在创建包含7个以上成员的副本集或者是希望阻止自动故障转移使用"votes"选项。很多开发者会误以为让成员拥有更多投票权会使这个成员更容易被选为主节点(实际上根本不会)。如果希望某个成员可以优先被选举为主节点,应该使用优先级。

强制重新配置

如果副本集无法再达到“大多数”要求的话,那么它就无法选举出新的主节点,这时你可能会希望重新配置副本集。这看起来有点奇怪,因为通常都是将配置文件发
送给主节点。在这种情况下,可以在备份节点上调用rs.reconfig强制重新配置(force reconfigure)副本集。在shell中连接到一个备份节点,使用"force"选项执行rs.reconfig命令:

rs.reconfig(config, {"force": true})

强制重新配置与普通的重新配置要遵守同样的规则:必须使用正确的reconfig选项将有效的、格式完好的配置文件发送给成员。"force"选项不允许无效的配置,而且只允许将配置发送给备份节点。

强制重新配置会跳过大量的数值直接将副本的"version"设为一个比较大的值。可能会见到跳过数千的情况,这很正常:这是为了防止"version"字段冲突(以防不同的网络域中都在进行重新配置)。

备份节点收到新的配置文件之后,就会修改自身的配置,并且将新的配置发送给副本集中的其他成员。副本集的其他成员收到新的配置文件之后,会判断配置文件的发送者是否是它们当前配置中的一个成员,如果是,才会用新的配置文件对自己进行重新配置。所以,如果新的配置会修改某些成员的主机名,应该将新的配置发送给主机名不发生变化的成员。如果新的配置文件修改了所有成员的主机名,应该关闭副本集的每一个成员,以单机模式启动,手动修改local…system.replset文档,然后重新启动。

把主节点变为备份节点

可以使用stepDown函数将主节点降级为备份节点:

rs.stepDown()

这个命令可以让主节点退化为备份节点,并维持60秒。如果这段时间内没有新的主节点被选举出来,这个节点就可以要求重新进行选举。如果希望主节点退化为备份
节点并持续更长(或者更短)的时间,可以自己指定时间(以秒为单位):

rs.stepDown(600)//10分钟
阻止选举

如果需要对主节点做一些维护,但是不希望这段时间内将其他成员选举为主节点,那么可以在每个备份节点上执行freeze命令,以强制它们始终处于备份节点状态:

rs.freeze(10000)

这个命令也会接受一个以秒表示的时间,表示在多长时间内保持备份节点状态。

维护完成之后,如果想“释放”其他成员,可以再次执行freeze命令,将时间指定为0即可:

rs.freeze(0)

这样,其他成员就可以在必要时申请被选举为主节点。
也可以在主节点上执行rs.freeze(0),这样可以将退位的主节点重新变为主节点。

使用维护模式

当在副本集成员上执行某个非常耗时的操作时,这个成员就人进入维护模式(maintenance mode):强制成员进入RECOVERING状态。有时,成员会自动进入维护模式,比如在成员上做压缩时。压缩开始之后,成员会进入RECOVERING状态,这样就不会有读请求发送给这个成员。客户端会停止从这个成员读取数据(如果之前有从这个成员读数据的话),这个成员也不能再作为复制源。

也可以通过执行replSetMaintenanceMode命令强制一个成员进入维护模式。如果一个成员远远落后于主节点,你不希望它继续处理读请求时,可以强制让这个成员进人维护模式。例如,下面这个脚本会自动检测成员是否落后于主节点30秒以上,如果是,就强制将这个成员转入维护模式:

function maybeMaintenanceMode() {
	var local = db.getSisterDB("local")

	//如果成员不是备份节点(它可能是主节点或者已经处于维护状态),就直接返回
	if (!local.isMaster().secondary) {
		return;
	}
	
	//查找这个成员最后一次操作的时间
	var last = local.oplog.rs.find().sort({"$natural": -1}).next();
	var lastTime = last['ts']['t'];

	//如果落后主节点30秒以上
	if (lastTime < (new Date()).getTime() - 30) {
		db.adminCommand({"replSetMaintenanceMode": true})	
	}
}