【1】mongodb RS 介绍
(1.1)Replicateset 复制集介绍
Mongodb复制集由一组Mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点;
Mongodb Driver(客户端)的所有数据都写入Primary,Secondary从Primary同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。
下图(图片源于云栖社区)是一个典型的Mongdb复制集,包含一个Primary节点和2个Secondary节点。
Primary 中有 oplog,对比mysql的binlog,但mongodb 的 oplog 是存表的;存在如下库:
local库:本地预留库,存储关键日志(oplog,类似于mysqlbinlog)
如上图,注意 Driver 驱动会自动识别,实现读写分离(需要配置),自动寻找故障转移后的新主;
新从节点加入,无需任何操作,会自动同步一份完整数据过去;
MongoDB复制集具有如下四个特点:
1.主是唯一的,但不是固定的。
整个复制集中只有一个主节点,其余为从节点或选举节点,但是因为MongoDB具有自动容灾的功能,所以当唯一的主节点发生宕机时会从从节点的Priority参数不为0当中选举一个为主节点,所以说主是唯一的,但不是固定的。
2.由大多数据原则保证数据的一致性。
即MongoDB复制集内投票成员(参数Vote不为0的其他成员具有选举权,在2.6版本后不能设置Vote大于1,即只能投票一次)数量为N,则大多数为 N/2 + 1;
当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。通常建议将复制集成员数量设置为奇数。
3.从库无法写入。
MongoDB复制集中只有主节点可以Writes,主节点和从节点可以Read。
4.相对于传统的主从结构,复制集可以自动容灾。
即当某个主节点宕机后会自动从从节点(Priority大于0的从节点)中选举出一个作为新的主节点,而某个从节点宕机后能继续正常工作。
(1.2)复制集 5大节点介绍
MongoDB按功能可分为主节点、从节点(隐藏节点、延时节点、“投票”节点)和选举节点。
节点名 说明
{1}主节点(Primary) :可以提供读Writes/Read的节点,整个复制集中只有一个主节点。
{2}隐藏节点(Hidden) :提供Read并对程序不可见的节点。
{3}延时节点(Delayed) :提供Read并能够延时复制的节点。
{4}“投票”节点(Priority) :提供Read并具有投票权的节点。
{5}选举节点(Arbiter) :Arbiter节点,无数据,仅作选举和充当复制集成员。又成为投票节点。
2个重要参数:
Priority参数:选举优先级,默认为1,Priority参数设置范围在0-1000的整数值。Priority0节点的选举优先级为0,永远不会被选举为Primary。
Vote参数:投票权,Vote默认为1,Vote参数的值在2.6版本后只能设置为0或1。Mongodb 3.0里,复制集成员最多50个,参与Primary选举投票的成员最多7个,其他成员的vote属性必须设置为0,即不参与投票。Vote设置为0时永远没有投票权。
详细解释:
Primary节点:复制集通过replSetInitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。
Secondary节点:正常情况下,复制集的Seconary会参与Primary选举(自身也可能会被选为Primary),并从Primary同步最新写入的数据,以保证与Primary存储相同的数据。Secondary可以提供读服务,增加Secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外,Mongodb支持对复制集的Secondary节点进行灵活的配置,以适应多种场景的需求。
Arbiter节点:Arbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据。比如你部署了一个2个节点的复制集,1个Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加一个Arbiter节点,即使有节点宕机,仍能选出Primary。Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个Arbiter节点,以提升复制集可用性。
Hidden节点:Hidden节点不能被选为主(因为Priority为0),并且对Driver不可见。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。
Delayed节点:delayed的配置受到opolg的影响。Delayed节点必须是Hidden节点,并且其数据落后与Primary一段时间(可配置,比如1个小时)。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。
(1.3)基本原理
基本构成是1主2从的结构,自带互相监控投票机制 Raft(mongodb),Paxos(mysql MGRyong de 用的是这种);
如果发生主库宕机,复制集内部会进行投票选举,选择一个新的主库替代原有主库对外提供服务。同时复制集会自动通知客户端程序,主库已经发生了切换,应用就会识别并自动连到新主库;
【2】复制集搭建
注意,local 库是不会同步的,其他库 admin 之类的会同步
(2.1)基本架构
4个节点
机器:192.168.191.25
端口:一个机器上部署4个实例, 28017、28018、28019、28020
(2.2)快速构建 4个实例
从0 安装参考:
前提,已经基本安装好,已经设置好环境变量;
mkdir -p /data/mongodb/{28017,28018,28019,28020}/{data,log}
useradd mongodb -s /sbin/false
# 配置文件
cat <<eof >/data/mongodb/28017/mongod.conf
systemLog:
destination: file
path: /data/mongodb/28017/log/mongodb.log
logAppend: true
storage:
journal:
enabled: true
dbPath: /data/mongodb/28017/data
directoryPerDB: true
#engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 1
directoryForIndexes: true
collectionConfig:
blockCompressor: zlib
indexConfig:
prefixCompression: true
processManagement:
fork: true
net:
bindIp: 0.0.0.0
port: 28017
replication:
oplogSizeMB: 2048
replSetName: my_repl
eof
# 构造不同实例的配置文件
cp /data/mongodb/28017/mongod.conf /data/mongodb/28018/mongod.conf
cp /data/mongodb/28017/mongod.conf /data/mongodb/28019/mongod.conf
cp /data/mongodb/28017/mongod.conf /data/mongodb/28020/mongod.conf
sed -i 's#28017#28018#g' /data/mongodb/28018/mongod.conf
sed -i 's#28017#28019#g' /data/mongodb/28019/mongod.conf
sed -i 's#28017#28020#g' /data/mongodb/28020/mongod.conf
# 启动多个实例
chown -R mongodb:mongodb /data/mongodb
chmod -R 775 /data/mongodb
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28017/mongod.conf &"
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28018/mongod.conf &"
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28019/mongod.conf &"
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28020/mongod.conf &"
# netstat -nltp #查看端口监听
(2.3)搭建集群 1主2从
登录 28017(以它为主库)
mongo --port 28017 admin
config = { _id: 'my_repl', members: [
{_id: 0, host: '192.168.191.25:28017'},
{_id: 1, host: '192.168.191.25:28018'},
{_id: 2, host: '192.168.191.25:28019'}]
}
rs.initiate(config)
# 初始化后,登录上会显示是主还是从 ,如:my_repl:PRIMARY>
rs.status() // 查看集群所有描述
rs.isMaster() // 查看集群成员,以及当前节点是否是 master
rs.conf() // 查看一下集群的配置信息,比如成员的权重、是否是投票节点等等
(2.4)rs.status() 状态查看
my_repl:PRIMARY> rs.status()
{
"set" : "my_repl",
"date" : ISODate("2022-04-25T14:48:08.447Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 3,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1650898088, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1650898088, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"appliedOpTime" : {
"ts" : Timestamp(1650898088, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1650898088, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastDurableWallTime" : ISODate("2022-04-25T14:48:08.022Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1650898068, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2022-04-25T14:45:47.828Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1650897937, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2022-04-25T14:45:47.979Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2022-04-25T14:45:49.525Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.191.25:28017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 21846,
"optime" : {
"ts" : Timestamp(1650898088, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-04-25T14:48:08Z"),
"lastAppliedWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastDurableWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1650897947, 1),
"electionDate" : ISODate("2022-04-25T14:45:47Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "192.168.191.25:28018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 151,
"optime" : {
"ts" : Timestamp(1650898078, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1650898078, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-04-25T14:47:58Z"),
"optimeDurableDate" : ISODate("2022-04-25T14:47:58Z"),
"lastAppliedWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastDurableWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastHeartbeat" : ISODate("2022-04-25T14:48:07.921Z"),
"lastHeartbeatRecv" : ISODate("2022-04-25T14:48:07.431Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.191.25:28017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "192.168.191.25:28019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 151,
"optime" : {
"ts" : Timestamp(1650898078, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1650898078, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-04-25T14:47:58Z"),
"optimeDurableDate" : ISODate("2022-04-25T14:47:58Z"),
"lastAppliedWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastDurableWallTime" : ISODate("2022-04-25T14:48:08.022Z"),
"lastHeartbeat" : ISODate("2022-04-25T14:48:07.921Z"),
"lastHeartbeatRecv" : ISODate("2022-04-25T14:48:07.430Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.191.25:28017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1650898088, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1650898088, 1)
}
很长,一段一段解析:
members:查看每个节点的状态
(1)health:1 为正常
(2)optime:其实就是说的时间戳情况
【3】RS 常规运维操作
(3.0)RS副本及运维命令汇总
# (1)初始化构建 RS
mongo --port 28017 admin
config = { _id: 'my_repl', members: [
{_id: 0, host: '192.168.191.25:28017'},
{_id: 1, host: '192.168.191.25:28018'},
{_id: 2, host: '192.168.191.25:28019'}]
}
rs.initiate(config) // 初始化后,登录上会显示是主还是从 ,如:my_repl:PRIMARY>
# (2)状态查阅
rs.status() // 查看集群所有描述
rs.isMaster() // 查看集群成员,以及当前节点是否是 master
rs.conf() // 查看一下集群的配置信息,比如成员的权重、是否是投票节点等等
# (3)节点增删改(主节点执行才行)
rs.remove("192.168.191.25:28019") // 删除节点
rs.add("ip:port") // 默认 priority 和 votes 均为1,超出7个 votes为1的节点后,则默认为0
rs.add( {_id:1,host: "192.168.146.32:27017",priority: 1, votes: 1 } ) // priority 权重 votes 具有投票权限 1 有 0没有
rs.addArb("ip:port") // 增加 arbiter 角色节点
cfg=rs.config()
cfg.members[3].priority=0 // 修改该成员的优先级参数为0
cfg.members[3].hidden=true
cfg.members[3].slaveDelay=120 // 秒为单位
rs.reconfig(cfg) // 重新应用加载上面修改的配置
# (4)其他运维命令
rs.stepDown() // (不建议人为操作使用,业务繁忙期间操作)主库降级,手动故障转移,把主库变成变成从库,另自动选择主库(根据 priority 优先级参数)
rs.freeze(300) // 单位秒 锁定从,使其不会转变成主库
rs.slaveOk() // 4.4.12版本前使用,从库副本 默认不允许读,在对应从库上执行 该命令后才可以
rs.secondaryOk() // 4.4.13版本后使用
rs.printSlaveReplicationInfo() // 查看主从延时
(3.1)选举节点(Arbiter)
场景:
我们正常情况只需要一主一从即可,另外一个 arbiter 节点做一个投票仲裁即可;
这样可以用一个很差配置的机器 如上图的 A(Arbiter);而无需用一个和主库一样配置的节点
注意:
常规的S 副本不能直接变成 arbiter 角色副本,但可以删除S副本的复制关系后,再把其变成 arbiter 角色副本;
(3.2)删除节点(必须在主节点执行)
现在的集群信息情况如下:
我们现在 把 28019 节点踢出集群
my_repl:PRIMARY> rs.remove("192.168.191.25:28019")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1651389506, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1651389506, 1)
}
再次查看:就只有2个节点的信息了,28019确实是被我们删除掉了
(3.3)添加节点(必须在主节点执行)
我们上面删除了 28019 节点,现在我们想把 28019节点 作为 arbiter 节点重新加入到集群;
my_repl:PRIMARY> rs.addArb("192.168.191.25:28019");
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1651389770, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1651389770, 1)
}
那我们 rs.config() 查看,新加点的节点 192.168.191.25:28019 ,如下图,其 arbiterOnly 参数为 true了
然后我们查看 rs.status() ,如下图,发现我们这个节点是报错的
这是因为我们在 remove 的时候,顺便已经关闭了 20819 节点,我们重启该节点即可
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28019/mongod.conf --shutdown &"
su - mongodb -c "/usr/local/mongodb/bin/mongod --config /data/mongodb/28019/mongod.conf &"
重新起来28019 节点后,再看rs.status() 已经恢复好
查看 rs.isMaster() 函数中,也显示出来了相关副本及的主机信息和角色信息
【4】RS中的3大特殊节点
(4.1)基本介绍
如(1.2)中所述,除了常规的 主、从 节点,还有3大特殊节点
一般情况下,Hidden 与 Delayed 是一起配置使用的;
Arbiter节点:Arbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据,也不提供任何服务。比如你部署了一个2个节点的复制集,1个Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加一个Arbiter节点,即使有节点宕机,仍能选出Primary。Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个Arbiter节点,以提升复制集可用性。
Hidden节点:Hidden节点不能被选为主(因为Priority为0),对Driver不可见,不提供对外服务。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。
Delayed节点:delayed的配置受到opolg的影响,不提供服务不参与选主。Delayed节点必须是Hidden节点,并且其数据落后与Primary一段时间(可配置,比如1个小时)。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。
(4.2)配置演示节点(一般延时节点也配置成 Hidden,默认复制源是从副本节点)
rs.add("192.168.191.25:28020")
# 配置 members[2] 所对应的主机节点参数,注意,这里的 members[里面的数字]
# 这个是数组下标,而不是 _id 的值
cfg=rs.config()
cfg.members[3].priority=0
cfg.members[3].hidden=true
cfg.members[3].slaveDelay=120 // 秒为单位
rs.reconfig(cfg)
# 取消配置
cfg=rs.config()
cfg.members[3].priority=1
cfg.members[3].hidden=false
cfg.members[3].slaveDelay=0
rs.reconfig(cfg)
# 查看修改后的配置
rs.config()
如下左右2图, 我们可以看到,2个图确实是有时间应用差距的,正好是我们配置的 120秒(但如果有数据的时候偶尔这个显示也会有误差);
但下图中还有一个重大的问题,见 syncSourceHost 字段,我们当前复制集的主库是 192.168.191.25:28017;
但这个延时副本它的 syncSourceHost 确是一个从副本 192.168.191.25:28018,
足以说明:延时、隐藏节点的 默认复制源是从副本节点
【疑问】
(1)如果是偶数个节点怎么办?
如下图,我们有4个节点,每个节点一票
votes field value is 2 but must be 0 or 1 该参数只能为 1 或者 0,所以
方法1:
我们把某个节点的投票数设置为0,比如把 延迟复制节点的投票数取消掉
主库操作:
cfg=rs.config()
cfg.members[3].priority=0
cfg.members[3].votes=0 // 修改该成员的投票数为2
rs.reconfig(cfg) // 重新应用加载上面修改的配置
注意,常规可以故障切换成主副本的从副本不建议这样操作,因为会报错 priority must be 0 when non-voting(votes:0)
也就是说没有投票权的节点,也不应该有成为主库的资格;