目录
一、复制基础
副本集是一种创建多个MongoDB实例的方式,这些实例将拥有相同的数据(冗余)和其它相关设置。主从复制、主主复制、复制对等方法都被副本集的概念所取代。在MongoDB中,副本集由一个主节点以及多个辅助或仲裁节点组成,一个副本集最少应该有3个成员。在MongoDB 3.0中,副本集最多可以有50个被动成员和7个主动成员。通常建议副本集有奇数个成员,这条规则主要是为了避免“脑裂”(split brain)问题,也就是说当网络出现问题时,有两台服务器成为主服务器的情况。
1. 主动成员与被动成员
副本集提供了主动成员与被动成员。当前的主服务器不可用时,被动服务器不会参与新的主服务器的选举,但它们可投票否决某个成员的主服务器资格。
2. master
在副本集术语中,主服务器是在特定时间内副本集的数据来源。它是副本集中唯一可以写入的节点。所有其它节点都将从主服务器复制出它们的数据。主服务器由所有主动成员中的大多数投票产生,这被称为法定人数(quorum)。
主服务器的概念是(并且应该是)短暂的。理想情况下不应该固定地认为哪个节点是主服务器。
3. secondary
辅助服务器成员是具有数据的非主服务器成员,理论上它可以成为主服务器。它是一个只读节点,同时它将以尽可能接近于实时的方式从主服务器复制数据。默认情况下,如果连接到辅助服务器但不使用任何读偏好,就不能执行读操作。这是因为读取非主服务器时,如果复制过程中存在延迟,读取的可能是旧数据。可以使用rs.slaveOk()将当前连接设置为可从辅助服务器读取数据。或者如果使用的是某种语言的MongoDB驱动,那么也可以设置读偏好。
MongoDB的读偏好是它选择从哪个副本集成员读取数据的方式。通过为驱动指定一个读偏好,它将知道应该在副本集的哪个成员上执行查询。如果设置了读偏好,就意味着可能从辅助服务器读取数据。必须注意,得到的数据可能不是最新的。
4. arbiter
仲裁服务器是不含数据的节点,如果副本集中的主动成员是偶数,它就用于提供额外的主动成员,决定哪个节点成为主服务器。仲裁服务器用于帮助避免“脑裂”。
5. oplog
oplog(操作日志)是一个固定大小的集合,保存主服务器实例对数据库做出修改的记录,目的是在辅助服务器重做这些操作,保证数据库处于一致状态。副本集的每个成员维护自己的oplog,并且辅助服务器将查询主服务器(或者用过复制链进行其它数据更新的辅助服务器)的oplog,从而获得新条目,并应用到自己的数据库副本中。
oplog将为每个条目创建一个时间戳。通过这种方式,辅助服务器可以记录从上一次读取开始过去了多久,以及有多少oplog需要读取。可将oplog看成主服务器实例最近活动的窗口:如果窗口太小,那么记录中的某些操作可能在被应用到辅助服务器之前丢失。如果当前实例的oplog尚未创建,那么使用--oplogSize启动选项可以设置oplog的大小(以MB为单位)。在64位Linux系统中,oplogSize默认设置为可以磁盘空间的5%,最小为1GB,最大为50GB。
计算oplog大小时,考虑主服务器上所有数据库的更新频率非常关键。通过执行db.printReplicationInfo()命令可以得到一些oplog大小方面的参考,该命令将运行在主服务器上:
testset:PRIMARY> db.printReplicationInfo()
configured oplog size: 1781.708984375MB
log length start to end: 83964secs (23.32hrs)
oplog first event time: Tue Oct 16 2018 15:04:31 GMT+0800 (CST)
oplog last event time: Wed Oct 17 2018 14:23:55 GMT+0800 (CST)
now: Wed Oct 17 2018 14:23:56 GMT+0800 (CST)
testset:PRIMARY>
该命令将显示出oplog当前的大小,以及以当前的更新速率oplog被填满所需的时间。从该信息中可以估计出是需要增加还是减小oplog的大小。
二、配置副本集
1. 创建副本集
环境:
主动成员1:hdp4:27017
主动成员2:hdp3:27017
被动成员1:hdp2:27017
仲裁:hdp1:27017
(1)启动副本集成员
因为启用了auth,所以先要建立autokey文件,否则初始化副本集时会报错:
"errmsg" : "Quorum check failed because not enough voting nodes responded; required 2 but only the following 1 voting nodes responded: hdp4:27017; the following nodes did not respond affirmatively: hdp3:27017 failed with Authentication failed."
以下命令在hdp4上执行:
# 建立autokey文件
openssl rand -base64 756 > autokey
# 修改读写模式
chmod 400 autokey
# 复制到副本集成员节点
scp autokey 172.16.1.126:/home/mongodb/mongodb-4.0.2/
scp autokey 172.16.1.125:/home/mongodb/mongodb-4.0.2/
scp autokey 172.16.1.124:/home/mongodb/mongodb-4.0.2/
准备配置文件如下:
logpath = /home/mongodb/mongodb-4.0.2/data/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data/mongodb.pid
dbpath = /home/mongodb/mongodb-4.0.2/data/
auth = true
bind_ip_all = true
replSet = testset
keyFile = /home/mongodb/mongodb-4.0.2/autokey
在4台服务器上分别执行以下命令启动节点:
mongod -f /home/mongodb/mongodb-4.0.2/mongodb.conf &
(2)初始化副本集
在hdp4实例中执行:
> rs.initiate();
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "hdp4:27017",
"ok" : 1,
"operationTime" : Timestamp(1539761031, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761031, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:SECONDARY>
2. 向副本集添加服务器
在hdp4实例中执行:
testset:SECONDARY> rs.add("hdp3:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761227, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761227, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY> rs.add("hdp2:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761249, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761249, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
3. 设置辅助服务器
在hdp4实例中执行以下命令,将hdp2设置为隐藏,并且优先级为0:
testset:PRIMARY> conf = rs.conf();
{
"_id" : "testset",
"version" : 3,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "hdp4:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "hdp3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "hdp2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5bc6e3873c3e34249ed82765")
}
}
testset:PRIMARY> conf.members[2].hidden = true
true
testset:PRIMARY> conf.members[2].priority = 0
0
testset:PRIMARY> rs.reconfig(conf);
{
"ok" : 1,
"operationTime" : Timestamp(1539761492, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761492, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
这样hdp2不会被选举为主服务器。
4. 向副本集添加仲裁服务器
在hdp4实例中执行:
testset:PRIMARY> rs.addArb("hdp1:27017");
{
"ok" : 1,
"operationTime" : Timestamp(1539761665, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539761665, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
5. 设置被动服务器
现在副本集中有4个节点,需要使主动成员数为奇数,在hdp4实例中执行:
testset:PRIMARY> conf = rs.conf()
{
"_id" : "testset",
"version" : 5,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "hdp4:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "hdp3:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "hdp2:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : true,
"priority" : 0,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 3,
"host" : "hdp1:27017",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : -1,
"catchUpTakeoverDelayMillis" : 30000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5bc6e3873c3e34249ed82765")
}
}
testset:PRIMARY> conf.members[2].votes = 0
0
testset:PRIMARY> rs.reconfig(conf)
{
"ok" : 1,
"operationTime" : Timestamp(1539762432, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539762432, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
testset:PRIMARY>
hdp2的votes值设置为0,现在hdp2完全变成被动服务器,它被客户端看成副本集的一部分,并且不会参与选举,也永远不会变成主服务器。
6. 在服务器上检查和执行操作
(1)副本集链
通常,副本集成员会尝试从副本集的主服务器同步数据。但这不是副本集的辅助服务器同步数据的唯一服务器:它们也可以从其它辅助服务器同步数据。通过这种方式,所有辅助服务器将组成一个“同步链”,每个节点都可以从副本集的其它辅助服务器同步最新数据。副本集链在MongoDB中是默认行为,设置chainingAllowed:false,可以改变这种行为,如下:
cfg=rs.conf()
cfg.settings.chainingAllowed=false
rs.reconfig(cfg)
(2)管理副本集
操作和检测副本集的命令:
命令 | 描述 |
rs.help() | 返回命令列表。 |
rs.status() | 返回副本集当前的状态信息。该命令列出了每个成员服务器及其状态信息,包括最后联系时间。该调用可被用于提供整个集群的简单健康检查。 |
rs.initiate() | 使用默认参数初始化副本集。 |
rs.initiate(replSetcfg) | 使用配置描述初始化副本集。 |
rs.add("host:port") | 使用含有主机名和特定端口(可选)的简单字符串向副本集中添加成员服务器。 |
rs.add(membercfg) | 使用配置描述向副本集中添加成员服务器。如果希望指定特定的属性(如设置新成员服务器的优先级),那么必须使用这种方法。 |
rs.addArb("host:port") | 添加新的成员服务器作为仲裁者。该成员不需要使用—replSet选项:任何运行在可达机器上的mongod实例都可以执行该任务。注意该服务器必须对副本集中的所有成员可达。 |
rs.stepDown() | 在副本集的主服务器成员中使用该命令时,将使主服务器放弃它的角色,并且在集群中重新选举新的主服务器。注意只有主动辅助服务器可用作主服务器的候选,并且在60秒(缺省)之内如果没有出现其它可用的成员,那么原有的主服务器将重新成为主服务器。 |
rs.syncFrom("host:port") | 使辅助服务器从指定的成员同步数据,可用于组成同步链。 |
rs.freeze(secs) | 冻结指定的成员,并使它在指定秒数内无法成为主服务器。 |
rs.remove("host:port") | 从副本集中删除指定成员。 |
rs.slaveOk() | 通过该选项,可以允许从辅助服务器读取数据。 |
rs.conf() | 重新显示当前副本集的配置结构。改配置结构可以被修改,然后用作rs.reconfig()的参数,从而修改结构的配置。 |
db.isMaster() | 该函数不只可作用于副本集:它是一个通用的复制支持函数。通过它,应用或驱动可以判断出被连接的特定实例在复制拓扑结构中是否是主服务器。 |
rs.status字段的值:
值 | 描述 |
_id | 副本集中该成员的ID |
Name | 该成员的主机名 |
Health | replSet的健康值 |
State | 状态数值 |
StateStr | 副本集状态的字符串表示 |
Uptime | 该成员的运行时间 |
optime | 应用在该成员上最后一个操作的时间,格式为一个时间戳和一个整数值 |
optimeDate | 最后一个被应用操作的日期 |
lastHeartbeat | 最后一次发送心跳的日期 |
lastHeartbeatRecv | 最后一次收到心跳的日期 |
configVersion | 这个成员使用的副本集配置的版本 |
syncingTo | 使用哪个副本集成员同步数据 |