MongoDB从入门到实战(十一):副本集

x33g5p2x  于2021-12-25 转载在 Go  
字(11.5k)|赞(0)|评价(0)|浏览(281)

一:简介

MongoDB中的复制集是一组维护相同数据集的mongod服务。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。复制还可以实现从硬件故障和服务中断中恢复数据。是所有生产部署的基础。

​ 也可以说,复制集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库关掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。

(1)冗余和数据可用性

复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。

在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 您还可以为专用目的维护其他副本,例如灾难恢复,报告或备份。

(2)MongoDB中的复制

复制集是一组维护相同数据集的mongod实例。 复制集包含多个数据承载节点和可选的一个仲裁节点。 在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。主节点接收所有写操作。

为什么使用复制集?

  1. 保障数据的安全性;
  2. 数据高可用性 ;
  3. 灾难恢复;
  4. 无需停机维护;
  5. 分布式读取数据等

二:复制集原理

mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点(后备节点),负责复制主节点上的数据。mongodb各个节点常见的搭配方式为:一主一从、一主多从。主节点记录在其上的所有操作oplog(oplog,操作日志的简称,是local库下一个特殊的固定集合,它保存了与数据库中数据存储相关的所有操作记录。从节点就是通过查看主节点 的oplog这个集合来进行复制的。每个节点都有oplog,记录着从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。),从节点定期查询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

说明:辅助(副本)节点复制主节点的oplog并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据集。 如果主节点挂掉,则将在从节点中选举出主节点。

三:复制集的三个角色

复制集有两种类型三种角色。

两种类型:

  • 主节点(Primary)类型:数据操作的主要连接点,可读写。
  • 次要(辅助、从)节点(Secondaries)类型:数据冗余备份节点,可以读或选举。

三种角色:

  • 主要成员(Primary):主要接收所有写操作。就是主节点。
  • 副本成员(Replicate):负责同步主节点的数据操作日志更新本地数据库,从而保证副本节点的数据和主节点上的数据的一致性,不支持写操作;副本节点的从某种意义上来讲有点像赛跑,永远在追赶主节点的数据操作;
  • 仲裁者(Arbiter):不负责保存具体的数据,只是在副本集进行主节点选举时提供自己的一个选票而已。当然也可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。

关于仲裁者的额外说明:

您可以将额外的mongod实例添加到副本集作为仲裁者。 仲裁者不维护数据集。 仲裁者的目的是通过响应其他副本集成员的选举请求来维护副本集中的仲裁。 因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。

如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的“大多数”投票。 仲裁者不需要专用硬件。

仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。

如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。

如果你的副本+主节点的个数是奇数,可以不加仲裁者。

四:复制集配置

一主一副本一仲裁

说明:

​ 本次复制集配置使用一个mongodb数据库,使用了三个节点,一个主节点(端口27000)、一个从节点(端口27001)、一个仲裁节点(端口27002)。在bin同级目录下创建一个data目录、config目录和log目录,分别放置数据库数据、数据库实例配置信息和数据库日志。下面是data和config目录的截图,下面有三个分目录,分别存放每个节点的数据。

在根目录下创建一个data目录,并在data目录下创建三个复制文件夹用于存储各自的配置文件。

在根目录下创建一个config目录,并在config目录下创建三个复制文件夹用于存储各自的配置文件。

config/rs1/mongod.cfg

dbpath=/Users/mengday/Softwares/mongodb-4.4.5/data/rs1
logpath=/Users/mengday/Softwares/mongodb-4.4.5/log/rs1.log
journal=true
port=27000
replSet=rs
logappend=true

config/rs2/mongod.cfg

dbpath=/Users/mengday/Softwares/mongodb-4.4.5/data/rs2
logpath=/Users/mengday/Softwares/mongodb-4.4.5/log/rs2.log
journal=true
port=27002
replSet=rs
logappend=true

config/rs3/mongod.cfg

dbpath=/Users/mengday/Softwares/mongodb-4.4.5/data/rs3
logpath=/Users/mengday/Softwares/mongodb-4.4.5/log/rs3.log
journal=true
port=27002
replSet=rs
logappend=true

注意:以上三个节点的复制集名字统一为rs。

//主节点实例服务启动命令
./bin/mongod --config ./config/rs1/mongod.cfg
//从节点实例服务启动命令
./bin/mongod --config ./config/rs2/mongod.cfg
//仲裁节点实例服务启动命令
./bin/mongod --config ./config/rs3/mongod.cfg

// 连接主节点
mongo 127.0.0.1:27000/admin

// 初始化, 注意此时不要初始化从节点和仲裁节点,不然后面主节点添加从节点的时候会报错。
> rs.initiate()
{
	"info2" : "no configuration specified. Using a default configuration for the set",
	"me" : "localhost:27000",
	"ok" : 1
}

// 初始化配置
rs:SECONDARY> rs.conf()
{
	"_id" : "rs",
	"version" : 1,
	"term" : 1,
	"protocolVersion" : NumberLong(1),
	"writeConcernMajorityJournalDefault" : true,
	"members" : [
		{
			"_id" : 0,
			"host" : "localhost:27000",
			"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("6076544a7353de75a9e532de")
	}
}

// 向主节点添加从节点
rs:PRIMARY> rs.add("localhost:27001")
{
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1618367820, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1618367820, 1)
}

// 向主节点添加仲裁节点
rs:PRIMARY> rs.addArb("localhost:27002")
{
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1618367859, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1618367859, 1)
}

// 查看主节点状态
rs:PRIMARY> rs.status()
{
	"set" : "rs",
	"date" : ISODate("2021-04-14T02:38:11.408Z"),
	"myState" : 1,
	"term" : NumberLong(1),
	"syncSourceHost" : "",
	"syncSourceId" : -1,
	"heartbeatIntervalMillis" : NumberLong(2000),
	"majorityVoteCount" : 2,
	"writeMajorityCount" : 2,
	"votingMembersCount" : 3,
	"writableVotingMembersCount" : 2,
	"optimes" : {
		"lastCommittedOpTime" : {
			"ts" : Timestamp(1618367883, 1),
			"t" : NumberLong(1)
		},
		"lastCommittedWallTime" : ISODate("2021-04-14T02:38:03.166Z"),
		"readConcernMajorityOpTime" : {
			"ts" : Timestamp(1618367883, 1),
			"t" : NumberLong(1)
		},
		"readConcernMajorityWallTime" : ISODate("2021-04-14T02:38:03.166Z"),
		"appliedOpTime" : {
			"ts" : Timestamp(1618367883, 1),
			"t" : NumberLong(1)
		},
		"durableOpTime" : {
			"ts" : Timestamp(1618367883, 1),
			"t" : NumberLong(1)
		},
		"lastAppliedWallTime" : ISODate("2021-04-14T02:38:03.166Z"),
		"lastDurableWallTime" : ISODate("2021-04-14T02:38:03.166Z")
	},
	"lastStableRecoveryTimestamp" : Timestamp(1618367859, 1),
	"electionCandidateMetrics" : {
		"lastElectionReason" : "electionTimeout",
		"lastElectionDate" : ISODate("2021-04-14T02:32:42.923Z"),
		"electionTerm" : NumberLong(1),
		"lastCommittedOpTimeAtElection" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"lastSeenOpTimeAtElection" : {
			"ts" : Timestamp(1618367562, 1),
			"t" : NumberLong(-1)
		},
		"numVotesNeeded" : 1,
		"priorityAtElection" : 1,
		"electionTimeoutMillis" : NumberLong(10000),
		"newTermStartDate" : ISODate("2021-04-14T02:32:42.964Z"),
		"wMajorityWriteAvailabilityDate" : ISODate("2021-04-14T02:32:43.021Z")
	},
	"members" : [
		{
			"_id" : 0,
			"name" : "localhost:27000",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 719,
			"optime" : {
				"ts" : Timestamp(1618367883, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-04-14T02:38:03Z"),
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"electionTime" : Timestamp(1618367562, 2),
			"electionDate" : ISODate("2021-04-14T02:32:42Z"),
			"configVersion" : 3,
			"configTerm" : 1,
			"self" : true,
			"lastHeartbeatMessage" : ""
		},
		{
			"_id" : 1,
			"name" : "localhost:27001",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 71,
			"optime" : {
				"ts" : Timestamp(1618367883, 1),
				"t" : NumberLong(1)
			},
			"optimeDurable" : {
				"ts" : Timestamp(1618367883, 1),
				"t" : NumberLong(1)
			},
			"optimeDate" : ISODate("2021-04-14T02:38:03Z"),
			"optimeDurableDate" : ISODate("2021-04-14T02:38:03Z"),
			"lastHeartbeat" : ISODate("2021-04-14T02:38:09.730Z"),
			"lastHeartbeatRecv" : ISODate("2021-04-14T02:38:09.764Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncSourceHost" : "localhost:27000",
			"syncSourceId" : 0,
			"infoMessage" : "",
			"configVersion" : 3,
			"configTerm" : 1
		},
		{
			"_id" : 2,
			"name" : "localhost:27002",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 31,
			"lastHeartbeat" : ISODate("2021-04-14T02:38:09.730Z"),
			"lastHeartbeatRecv" : ISODate("2021-04-14T02:38:09.807Z"),
			"pingMs" : NumberLong(0),
			"lastHeartbeatMessage" : "",
			"syncSourceHost" : "",
			"syncSourceId" : -1,
			"infoMessage" : "",
			"configVersion" : 3,
			"configTerm" : 1
		}
	],
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1618367883, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1618367883, 1)
}
rs:PRIMARY>

注意members项里面的成员是添加的个数。并且每个节点的“stateStr”分别是“PRIMARY”、“SECONDARY”、“ARBITER”。这时候登录从节点或者仲裁节点(不需要初始化和初始化配置)。查看节点状态,和主节点相同的配置会输出出来。至此,本mongodb数据库的复制集配置完成。

五:Compass 连接副本集

六:添加用户

rs:PRIMARY> use admin
switched to db admin
rs:PRIMARY> db.createUser({
... user: "admin",
...     pwd: "admin",
...     roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
... })
Successfully added user: {
	"user" : "admin",
	"roles" : [
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin"
		}
	]
}
rs:PRIMARY> db.createUser({
... user: "root",
... pwd: "root",
... roles: [ { role: "root", db: "admin" } ]
... })
Successfully added user: {
	"user" : "root",
	"roles" : [
		{
			"role" : "root",
			"db" : "admin"
		}
	]
}
rs:PRIMARY> use test
switched to db test
rs:PRIMARY> db.createUser({
... user: "mongo",
... pwd: "123456",
... roles: [ { role: "dbOwner", db: "test" } ]
... })
Successfully added user: {
	"user" : "mongo",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "test"
		}
	]
}
rs:PRIMARY> show users
{
	"_id" : "test.mongo",
	"userId" : UUID("7f842cc4-9c1f-4932-b570-d2f705e7ab34"),
	"user" : "mongo",
	"db" : "test",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "test"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}
rs:PRIMARY>

// 向主节点插入一条数据
rs:PRIMARY> db.foo.insert({"key": "value"})
WriteResult({ "nInserted" : 1 })
// 登录从节点
./bin/mongo 127.0.0.1:27001/admin

// 查看从节点上的数据库,报错
rs:SECONDARY> show dbs
uncaught exception: Error: listDatabases failed:{
	"topologyVersion" : {
		"processId" : ObjectId("607652cbcf3e7e9151103453"),
		"counter" : NumberLong(4)
	},
	"operationTime" : Timestamp(1618383611, 1),
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotPrimaryNoSecondaryOk",
	"$clusterTime" : {
		"clusterTime" : Timestamp(1618383611, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

// 当前从节点只是一个备份,不是奴隶节点,无法读取数据,写当然更不行。
// 因为默认情况下,从节点是没有读写权限的,可以增加读的权限,但需要进行设置。
// 设置为奴隶节点,允许在从成员上运行读的操作
rs:SECONDARY> rs.secondaryOk()

// 从节点上的数据从主节点上复制过来了
rs:SECONDARY> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
rs:SECONDARY> use test
rs:SECONDARY> db.foo.find()
{ "_id" : ObjectId("607692bcc83116b2c09bb439"), "key" : "value" }

相关文章