MongoDB之复制集原理和搭建

一、MongoDB复制集

Mongodb复制集由一组Mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点,Mongodb Driver(客户端)的所有数据都写入PrimarySecondaryPrimary同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。

二、复制集的作用

MongoDB的复制集主要是为了实现服务的高可用。

他的实现依赖于两个方面的功能:

  • 数据写入是将数据迅速复制到另一个独立节点上
  • 在接受写入节点发生故障时自动选举出一个新的替代节点

在实现高可用的同时,复制集实现了几个附加功能:

  • 数据分发:将数据从一个区域复制到另一个区域,减少另一个区域的读延迟
  • 读写分离:不同类型的压力分别在不同的节点上执行
  • 异地容灾:在数据中心故障时候快速切换到异地

三、常见复制集结构

一个典型的复制集由三个以上具有投票权的节点组成:

  • 一个主节点:接受写入操作和选举时投票(读写都可以)
  • 两个(或多个)从节点:复制主节点上的新数据和选举时投票(只读)
  • 不推荐使用Arbiter(投票节点:空节点不接受读写只作用于投票)

四、复制集搭建

4.1. 先创建文件夹和日志文件
mkdir data/node{1,2,3}      # 创建 node1 node2 node3 文件夹 存放数据
mkdir config/mongodb{1,2,3}.conf # 创建三个配置文件
touch logs/node{1,2,3}.log  # 创建node1.log node2.log node3.log 日志文件
4.2. 配置文件修改

这里的dbpathlogpathport修改为指定的文件夹、文件和端口。

# 数据库的数据存放目录  注意需要写绝对路径
dbpath=/usr/share/mongodb/mongodb_replica_set/data/node1
# 日志目录 注意需要写绝对路径
logpath=/usr/share/mongodb/mongodb_replica_set/logs/node1.log
# 日志追加方式
logappend=true
# 端口
port=27017
# 是否认证
# auth=true
# 已守护进程在后台运行
fork=true
# 远程链接
bind_ip=0.0.0.0
# 设置复制集名字 同一个复制集名字相同
replSet=long
4.3. 启动三个实例
# !/bin/bash
echo "启动节点一:"
./bin/mongod -f ./config/mongodb1.conf

echo "启动节点二:"
./bin/mongod -f ./config/mongodb2.conf

echo "启动节点三:"
./bin/mongod -f ./config/mongodb3.conf
4.4. 配置复制集
4.4.1. 方法一:

选择一个节点进入MongoDB shell

./bin/mongo localhost:27017

进入之后进行初始化

> rs.initiate()
{
	"info2" : "no configuration specified. Using a default configuration for the set",
	"me" : "ubuntu:27017",
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1616932776, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1616932776, 1)
}
long:SECONDARY> 

这样就是进入了一个复制集配置状态:long就是我们上面的复制集的名字。接着加入其他两个从节点节点:

long:PRIMARY> rs.add("ubuntu:27018")
{
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1616933662, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1616933662, 1)
}
long:PRIMARY> rs.add("ubuntu:27019")
{
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1616933671, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1616933671, 1)
}
long:PRIMARY> 

接着查看复制集状态:

long:PRIMARY> rs.status()
{
	"set" : "long",
	...,
	"members" : [
		{
			"_id" : 0,
			"name" : "ubuntu:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			...
		},
		{
			"_id" : 1,
			"name" : "ubuntu:27018",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			...
		},
		{
			"_id" : 2,
			"name" : "ubuntu:27019",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			...
		}
	],
	...
}
long:PRIMARY> 

注意:复制集是可以只有一个节点的。

验证一下:主节点写,从节点读。

这里我们需要注意,直接在从节点上读是会报错的,原因在于mongo副本集实现了读写分离,默认的副本无法读写。我们可以使用rs.slaveOk()允许在从节点上读取数据。(rs.slaveOk是过期的后续版本可能会删除,我们可以使用rs.secondaryOk()

long:SECONDARY> db.test.find()
Error: error: {
	"topologyVersion" : {
		"processId" : ObjectId("60606829f25506cbae73ca52"),
		"counter" : NumberLong(4)
	},
	"operationTime" : Timestamp(1616934386, 1),
	"ok" : 0,
	"errmsg" : "not master and slaveOk=false",
	"code" : 13435,
	"codeName" : "NotPrimaryNoSecondaryOk",
	"$clusterTime" : {
		"clusterTime" : Timestamp(1616934386, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
long:SECONDARY> rs.slaveOk()
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
long:SECONDARY> db.test.find()
long:SECONDARY> 

接着我们进行写读操作:

long:PRIMARY> db.test.insert({"name": "zhangsan"})
WriteResult({ "nInserted" : 1 })

long:SECONDARY> db.test.find()
{ "_id" : ObjectId("606076f3b0347ba762bc1e81"), "name" : "zhangsan" }
4.4.2. 方法二

区别与方法一我们可以直接一次就配置好复制集的主从节点

> rs.initiate({
...     _id: "long",
...     members: [
...     {
...             _id: 0,
...             host: "localhost:27017"
...         },
...         {
...             _id: 1,
...             host: "localhost:27018"
...         },
...         {
...             _id: 2,
...             host: "localhost:27019"
...     }
...     ]
... })
{
	"ok" : 1,
	"$clusterTime" : {
		"clusterTime" : Timestamp(1616935792, 1),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	},
	"operationTime" : Timestamp(1616935792, 1)
}
long:SECONDARY> 

接着我们查看复制集状态:

long:PRIMARY> rs.status()
{
	"set" : "long",
	...,
	"members" : [
		{
			"_id" : 0,
			"name" : "localhost:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			...
		},
		{
			"_id" : 1,
			"name" : "localhost:27018",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			...
		},
		{
			"_id" : 2,
			"name" : "localhost:27019",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			...
		}
	],
	...
}
long:PRIMARY> 

验证啥的和第一种方法一样!

五、复制集原理与故障恢复

5.1. 节点之间数据复制

当一个修改操作,无论是插入、更新或者删除,到达主节点时,它对数据的操作将被记录下来(经过一些转换),这些记录称为oplog

从节点通过在主节点上打开一个tailable游标不断获取新进入主节点的oplog,并在自己的数据上回放,以此保持跟主节点的数据一致。

5.2. 故障恢复过程

具有投票权的节点之间两两互相发送心跳,当五次心跳未收到时判断节点失联;如果失联的是主节点,从节点就会发起选举,选出新的主节点;如果失联的是从节点则不会产生新的选举。

选举基于RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活。

复制集中最多可以有50个节点,但是具有投票权的节点最多有7个。

影响选举的因素:

  • 整个集群必须有大多数节点存活
  • 被选举为主节点的节点必须能够被多数节点建立链接,具有较新的oplog,具有较高的优先级(手动设置)
5.3. 复制集节点常用配置

在配置复制集的时候可以添加这些参数来控制节点的状态:

priority: 副本集中通过设置priority的值来决定优先权的大小,这个值的范围是0--100,值越大,优先权越高。默认的值是1。如果值是0,那么不能成为primay

hidden: 将一个副节点配置成隐藏节点,首先要将members[n].priority 值设置成0和members[n].hidden值设置为true,隐藏节点既不能变成Primary也不能被客户端访问,但是拥有投票选举的权利。

slaveDelay: 延迟,复制n秒前的数据,保持与主节点的时间差。这样当操作失误的时候可以用来恢复数据。

v: 是否具有投票权

六、复制集注意事项

硬件:

  • 因为正常的复制集节点都有可能成为主节点,他们的地位是一样的,因此硬件配置要一致
  • 为了保证节点不会宕机,各节点使用的硬件必须具有独立性

软件:

  • 复制集各节点软件版本必须一致,以避免出现不可预知的问题

注意:增加节点不能增加系统的写性能,增加读性能(扩展写性能可以通过分片集群)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值