1、简介

MongoDB复制是将数据同步到多个服务器的过程;复制集提供了数据的冗余备份并提高了数据的可用性,通常可以保证数据的安全性;复制集还允许从硬件故障和服务中断中恢复数据。

  • 保障数据的安全
  • 数据高可用性 (24*7)
  • 灾难恢复
  • 无需停机维护(如备份,重建索引,压缩)
  • 分布式读取数据
  • 副本集对应用层是透明的

复制集至少需要两个节点,其中一个是主节点,负责处理客户端请求,剩余均是从节点,负责复制主节点上的数据;各节点常见搭配方式有一主一从、一主多从;主节点记录在其上的所有操作,从节点定期轮询主节点复制这些操作,随后对自己的数据副本执行这些操作(同步)。

2、MongoDB复制基本结构

基本的架构由3台服务器组成(三成员的复制集)

1)三个存储数据的复制集

一个主库、两个从库
主库宕机时,两个从库都可以被选为主库

在这里插入图片描述
说明:当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。
在这里插入图片描述

2)当存在arbiter节点

在三个成员的复制集中,有两个正常的主从,一台arbiter节点:
	一个主库,一个从库,从库可以在选举中成为主库
	一个aribiter节点,在选举中,只能进行投票,不能成为主库

在这里插入图片描述
说明:由于arbiter节点没有复制数据,因此这个架构中仅提供一个完整的数据副本,arbiter节点只需要更少的资源,代价是更有限的冗余和容错。

当主库宕机时,将会选择从库成为主,主库修复后,将其加入到现有的复制集群中即可。
在这里插入图片描述

3)Primary选举

复制集通过replSetInitiate命令或mongo shell的rs.initiate()进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点会成为Primary,其余节点成为Secondary。
『大多数』的定义:
假设复制集内投票成员(后续介绍)数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

投票成员数大多数容忍失效数
110
220
321
431
532
642
743

通常建议将复制集成员数量设置为奇数,从上表可以看出3个节点和4个节点的复制集都只能容忍1个节点失效,从『服务可用性』的角度看,其效果是一样的,但无疑4个节点能提供更可靠的数据存储

4)Priority 0节点

可以理解为是一个备胎。在一些复制集中,可能无法在合理的时间内添加新成员,此时,这个备胎假如同步了当前最新数据,即可替换不可用的成员。
在这里插入图片描述

5)Hidden 节点(隐藏节点)

这些隐藏节点无法收到来自应用程序的请求。可以将隐藏节点专用于报表节点、备份节点或延时节点。
即使我们设定了复制集读选项,客户端也不会把读请求分发到隐藏节点上。
在这里插入图片描述

6)Delayed 节点(延时节点)

延时节点的数据集是延时的(类似MySQL的延时同步),可以帮助我们在人为误操作或是其他意外情况下恢复数据。
在这里插入图片描述

7)复制集中成员说明

成员说明
Secondary正常情况下,复制集的Seconary会参与Primary选举(自身也可能会被选为Primary),并从Primary同步最新写入的数据,以保证与Primary存储相同的数据。Secondary可以提供读服务,增加Secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外,Mongodb支持对复制集的Secondary节点进行灵活的配置,以适应多种场景的需求。
ArbiterArbiter节点只参与投票,不能被选为Primary,并且不从Primary同步数据。比如你部署了一个2个节点的复制集,1个Primary,1个Secondary,任意节点宕机,复制集将不能提供服务了(无法选出Primary),这时可以给复制集添加一个Arbiter节点,即使有节点宕机,仍能选出Primary。Arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个Arbiter节点,以提升复制集可用性。
Priority0Priority0节点的选举优先级为0,不会被选举为Primary比如你跨机房A、B部署了一个复制集,并且想指定Primary必须在A机房,这时可以将B机房的复制集成员Priority设置为0,这样Primary就一定会是A机房的成员。(注意:如果这样部署,最好将『大多数』节点部署在A机房,否则网络分区时可能无法选出Primary)
Vote0Mongodb 3.0里,复制集成员最多50个,参与Primary选举投票的成员最多7个,其他成员(Vote0)的vote属性必须设置为0,即不参与投票。
HiddenHidden节点不能被选为主(Priority为0),并且对Driver不可见。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。
DelayedDelayed节点必须是Hidden节点,并且其数据落后与Primary一段时间(可配置,比如1个小时)。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。

3、复制集的特点

- N个节点的集群
- 任何节点均可作为主节点(除Arbiter节点)
- 只能在主节点上进行写入操作
- 自动故障转移
- 自动恢复

4、环境配置

准备了3台机器布置实验环境:
192.168.25.135    master
192.168.25.139    salve
192.168.25.140    salve
系统版本为 centos 7.3

5、安装部署

1)修改mongod配置文件(所有服务器都需要操作)

[root@localhost ~]# vim /etc/mongod.conf 
bindIp: 0.0.0.0		#监听网卡
replication:
  oplogSizeMB: 1024		#数据类型:数字;复制操作日志的最大大小(MreplSetName: myRS		#数据类型:字符;副本集名称(副本集中所有主机名称必须一致)
  
参数说明:
	replication.oplogSizeMB  
	replication.replSetName  

2)重新启动mongodb服务(所有服务器都需要操作)

[root@localhost ~]# systemctl restart mongod

3)在mongodb主服务器(192.168.150.11)运行命令,启动复制集

①进入mongo命令行

[root@localhost ~]# mongo
MongoDB shell version v4.4.14
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("ce1abfcf-674e-443a-89c9-56314519d361") }
MongoDB server version: 4.4.14
---
The server generated these startup warnings when booting: 
        2022-06-07T10:49:05.332+08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
        2022-06-07T10:49:05.332+08:00: You are running this process as the root user, which is not recommended
        2022-06-07T10:49:05.332+08:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
        2022-06-07T10:49:05.332+08:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
        2022-06-07T10:49:05.332+08:00: Soft rlimits too low
        2022-06-07T10:49:05.332+08:00:         currentValue: 1024
        2022-06-07T10:49:05.332+08:00:         recommendedMinimum: 64000
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
> 

②切换至admin库并查看初始复制集状态

> use admin		#切换至admin库
switched to db admin

> rs.status()	#查看当前的ReplSet的状态
{
	"ok" : 0,
	"errmsg" : "no replset config has been received",
	"code" : 94,
	"codeName" : "NotYetInitialized"
}

③初始化ReplSet复制集

方法一:
#初始化主节点
> rs.initiate({_id:'myRS',members:[{_id:1,host:'192.168.25.135:27017'}]})
{ "ok" : 1 }

#后续添加从节点
> rs.add("192.168.25.139:27017");
{ "ok" : 1 }
> rs.add("192.168.25.140:27017");
{ "ok" : 1 }

方法二:
#创建config集合(方便后面调用)
> config = {_id: "myRS", members: [
... {_id:0,host:"192.168.25.135:27017"},
... {_id:1,host:"192.168.25.139:27017"},
... {_id:2,host:"192.168.25.140:27017"}]
... }
{
	"_id" : "myRS",
	"members" : [
		{
			"_id" : 0,
			"host" : "192.168.25.135:27017"
		},
		{
			"_id" : 1,
			"host" : "192.168.25.139:27017"
		},
		{
			"_id" : 2,
			"host" : "192.168.25.140:27017"
		}
	]
}

> rs.initiate(config)	#调用config集合
{ "ok" : 1 }

#再次查看ReplSet复制集状态
myRS:SECONDARY> rs.status()
{
	"set" : "myRS",
	"date" : ISODate("2022-06-07T02:59:49.479Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"majorityVoteCount" : 2,
	"writeMajorityCount" : 2,
	"votingMembersCount" : 3,
	"writableVotingMembersCount" : 3,
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1654570782, 1),
			"t" : NumberLong(1)
		},
		"lastCommittedWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1654570782, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
		"appliedOpTime" : {
			"ts" : Timestamp(1654570782, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1654570782, 1),
			"t" : NumberLong(1)
		},
		"lastAppliedWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
		"lastDurableWallTime" : ISODate("2022-06-07T02:59:42.431Z")
	},
	"lastStableRecoveryTimestamp" : Timestamp(1654570781, 4),
	"electionCandidateMetrics" : {
		"lastElectionReason" : "electionTimeout",
		"lastElectionDate" : ISODate("2022-06-07T02:59:41.079Z"),
		"electionTerm" : NumberLong(1),
		"lastCommittedOpTimeAtElection" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"lastSeenOpTimeAtElection" : {
			"ts" : Timestamp(1654570770, 1),
			"t" : NumberLong(-1)
		},
		"numVotesNeeded" : 2,
		"priorityAtElection" : 1,
		"electionTimeoutMillis" : NumberLong(10000),
		"numCatchUpOps" : NumberLong(0),
		"newTermStartDate" : ISODate("2022-06-07T02:59:41.145Z"),
		"wMajorityWriteAvailabilityDate" : ISODate("2022-06-07T02:59:42.413Z")
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "192.168.25.135:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 645,
			"optime" : {
				"ts" : Timestamp(1654570782, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2022-06-07T02:59:42Z"),
			"lastAppliedWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"lastDurableWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1654570781, 1),
			"electionDate" : ISODate("2022-06-07T02:59:41Z"),
			"configVersion" : 1,
			"configTerm" : 1,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "192.168.25.139:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 19,
			"optime" : {
				"ts" : Timestamp(1654570782, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1654570782, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2022-06-07T02:59:42Z"),
			"optimeDurableDate" : ISODate("2022-06-07T02:59:42Z"),
			"lastAppliedWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"lastDurableWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"lastHeartbeat" : ISODate("2022-06-07T02:59:49.139Z"),
			"lastHeartbeatRecv" : ISODate("2022-06-07T02:59:48.636Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncSourceHost" : "192.168.25.135:27017",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 1,
			"configTerm" : 1
		},
		{
			"_id" : 2,
			"name" : "192.168.25.140:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 19,
			"optime" : {
				"ts" : Timestamp(1654570782, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1654570782, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2022-06-07T02:59:42Z"),
			"optimeDurableDate" : ISODate("2022-06-07T02:59:42Z"),
			"lastAppliedWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"lastDurableWallTime" : ISODate("2022-06-07T02:59:42.431Z"),
			"lastHeartbeat" : ISODate("2022-06-07T02:59:49.140Z"),
			"lastHeartbeatRecv" : ISODate("2022-06-07T02:59:48.636Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncSourceHost" : "192.168.25.135:27017",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 1,
			"configTerm" : 1
		}
	],
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1654570782, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1654570782, 1)
}

#稍过一会,可以发现已变成主节点
myRS:PRIMARY>

所有的statestr对应的状态

- STARTUP:刚加入到复制集中,配置还未加载
- STARTUP2:配置已加载完,初始化状态
- RECOVERING:正在恢复,不适用读
- ARBITER: 仲裁者
- DOWN:节点不可到达
- UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构,脑裂
- REMOVED:移除复制集
- ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态
- FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步
- PRIMARY:主节点
- SECONDARY:备份节点

④测试数据复制集效果,在主节点(192.168.95.14)上进行插入数据测试

myRS:PRIMARY> use db4		#创建db4测试库
switched to db db4

#利用for循环自动创建user集合的同时插入4条数据
myRS:PRIMARY> for (var i=0;i<4;i++) {db.user.insert({username: "my"+i,age:i})}
WriteResult({ "nInserted" : 1 })

#查看结果
myRS:PRIMARY> db.user.find()
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3c"), "username" : "my0", "age" : 0 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3d"), "username" : "my1", "age" : 1 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3e"), "username" : "my2", "age" : 2 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3f"), "username" : "my3", "age" : 3 }

⑤查看从节点同步情况

#从库默认不能进行读写操作
myRS:SECONDARY> rs.slaveOk();	#为从库开启读写功能(危险操作)【仅适用于实验场景】

myRS:SECONDARY> show dbs		#查看所有库(可以看到db4库已同步成功)
admin   0.000GB
config  0.000GB
db1     0.000GB
db4     0.000GB
local   0.000GB

myRS:SECONDARY> use db4			#切换至db4库
switched to db db4

myRS:SECONDARY> db.user.find()	#查看db4库中user集合的所有数据
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3c"), "username" : "my0", "age" : 0 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3d"), "username" : "my1", "age" : 1 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3e"), "username" : "my2", "age" : 2 }
{ "_id" : ObjectId("629ec025adf3ccfbae0cad3f"), "username" : "my3", "age" : 3 }

⑥测试主节点掉线后的故障转移效果

当前情况:
192.168.25.135 主
192.168.25.139 副
192.168.25.140 副

主节点上使用db.shutdownServer()命令模拟关闭主节点

myRS:PRIMARY> use admin		#重新切换至admin库
switched to db admin

myRS:PRIMARY> db.shutdownServer()		#模拟关闭主节点
server should be down...

等待一段时间,在两个从节点上查看现时的状态,192.168.25.139成为主节点,成功实现了故障转移

	{
		"_id" : 2,
		"name" : "192.168.25.139:27017",
		"health" : 1,
		"state" : 1,
		"stateStr" : "PRIMARY",
		"uptime" : 412,
		"optime" : {
			"ts" : Timestamp(1569914181, 1),
			"t" : NumberLong(4)
		},
		"optimeDate" : ISODate("2022-06-07T03:15:22Z"),
		"syncingTo" : "",
		"syncSourceHost" : "",
		"syncSourceId" : -1,
		"infoMessage" : "could not find member to sync from",
		"electionTime" : Timestamp(1569914150, 1),
		"electionDate" : ISODate("2022-06-07T03:15:22Z"),
		"configVersion" : 3,
		"self" : true,
		"lastHeartbeatMessage" : ""
	},

再次启动192.168.25.135(原主节点)

[root@localhost ~]# systemctl restart mongod

⑦测试角色切换(不要人为随便操作)

首先配置192.168.25.140暂停30s不参与选举

myRS:SECONDARY> rs.freeze(30)
{ "ok" : 1 }

随后配置192.168.25.139维持从节点状态不少于60秒,等待30秒使主节点和从节点日志同步

myRS:PRIMARY> rs.stepDown(60,30)

此时再次查看192.168.25.135,可以看到会重现变成主节点

"members" : [
	{
		"_id" : 1,
		"name" : "192.168.25.135:27017",
		"health" : 1,
		"state" : 1,
		"stateStr" : "PRIMARY",
		"uptime" : 418,
		"optime" : {
			"ts" : Timestamp(1569914769, 1),
			"t" : NumberLong(6)
		},
		"optimeDate" : ISODate("2022-06-07T03:15:22Z"),
		"syncingTo" : "",
		"syncSourceHost" : "",
		"syncSourceId" : -1,
		"infoMessage" : "could not find member to sync from",
		"electionTime" : Timestamp(1569914718, 1),
		"electionDate" : ISODate("2022-06-07T03:15:22Z"),
		"configVersion" : 3,
		"self" : true,
		"lastHeartbeatMessage" : ""
	},

6、复制管理操作解析

1)查看复制集状态

rs.status()		#查看整体复制集状态
rs.isMaster()	#查看当前是否是主节点

2)添加删除节点

rs.add("ip:port")			#新增从节点
rs.addArb("ip:port")		#新增仲裁节点
rs.remove("ip:port")		#删除一个节点

注意:添加特殊节点时,可以在搭建过程中设置特殊节点,也可以通过修改配置的方式将普通从节点设置为特殊节点(找到需要改为延迟性同步的数组号)

3)配置延时节点(一般延时节点)

cfg=rs.conf() 
cfg.members[2].priority=0
cfg.members[2].slaveDelay=120
cfg.members[2].hidden=true

注:这里的2是rs.conf()显示的顺序(除主库之外),非ID

重写复制集配置:
rs.reconfig(cfg)  
将延时节点配置为arbiter节点:
cfg.members[2].arbiterOnly=true
以下命令查询配置后的属性:
rs.conf()

副本集其他操作命令

查看副本集的配置信息:
my_repl:PRIMARY> rs.config()
查看副本集各成员的状态:
my_repl:PRIMARY> rs.status()
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐