1、介绍
之前讲到,主从复制集群利用从节点对主节点的数据进行备份,在主节点发生故障后可以手动恢复数据
但是这里有一个问题,那就是每次当主节点发生故障后,都需要暂停服务并且手动转移数据
不仅十分麻烦,而且暂停服务所带来的损失更是无法估量的,副本集就是为了解决这个问题而出现了
在一个副本集(Replica Set)中,包含多个数据承载节点(Data Bearing)和仲裁节点(Arbiter)
数据承载节点又可以细分为主节点(Primary)和从节点(Secondary),它们各自的描述如下:
• 主节点:有且仅有一个,默认情况下对客户端提供所有的增删改查服务
• 从节点:备份主节点数据,可提供查询服务,在主节点挂掉后投票选择一个从节点作为主节点继续提供服务
• 仲裁节点:不储存数据,不提供服务,仅参与投票,在主节点挂掉后投票选择一个从节点作为主节点
副本集的特征如下:
• 写操作仅在主节点上,并同步到从节点以保证数据的一致性
• 读操作既可以在主节点上,也可以在从节点上,在一定程度上可以减轻主节点的负担
• 当主节点挂掉后,会选举一个从节点作为主节点继续提供服务,因此无需进行停机维护
同步机制
副本集中的同步机制有两种,一种是全量同步,一种是增量同步
全量同步:从节点同步主节点的所有数据,当从节点加入或从节点未同步的数据量超过一定阈值时触发
增量同步:从节点定期同步主节点新修改的数据
自动故障转移机制
副本集的各个节点通过心跳信息检测各自的健康状况
当主节点发生故障时,投票选择一个从节点作为新的主节点,继续提供服务
选举机制
副本集中的选举机制使用的是 Bully 算法,Bully 算法的核心思想如下:
当从节点 A 发现主节点挂掉后,它就会发起选举,发送选举消息给其它节点,然后等待这些节点的回复
假设从节点 B 收到这个消息,并且发现自己的优先级比从节点 A 要大,那么从节点 A 就会退出选举
从节点 B 继续给其它节点发送选举消息,等待这些节点的回复,直到有一个节点能够获得大多数节点的赞同
此时,这个节点向所有节点宣布自己成为新的主节点,然后其它节点更新信息,结束选举过程
注意要进行选举有一个前提条件,那就是参与选举的节点数量必须大于副本集总节点数量的一半
2、搭建
下面是一个简单的模拟,一个主节点,一个从节点,一个仲裁节点
(1)下载 MongoDB
su
cd /root
mkdir mongodb-replica-set-cluster
cd mongodb-replica-set-cluster
mkdir mongodb
cd mongodb
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.7.tgz
tar -zxvf mongodb-linux-x86_64-3.2.7.tgz
cd …
(2)创建三个文件夹,分别模拟主节点、从节点和仲裁节点
mkdir primary # 主节点
touch ./primary/mongodb.conf
mkdir ./primary/log
mkdir -p ./primary/data/db
mkdir secondary # 从节点
touch ./secondary/mongodb.conf
mkdir ./secondary/log
mkdir -p ./secondary/data/db
mkdir arbiter # 仲裁节点
touch ./arbiter/mongodb.conf
mkdir ./arbiter/log
mkdir -p ./arbiter/data/db
创建完成之后,目录结构如下
- mongodb-replica-set-cluster
- mongodb
- mongodb-linux-x86_64-3.2.7.tgz
- mongodb-linux-x86_64-3.2.7
- primary
- log
- data
- db
- mongodb.conf
- secondary
- log
- data
- db
- mongodb.conf
- arbiter
- log
- data
- db
- mongodb.conf
- mongodb
(3)写配置文件
primary/mongodb.conf 文件内容
#数据文件的存放位置
dbpath = /root/mongodb-replica-set-cluster/primary/data/db
#日志文件的存放位置
logpath = /root/mongodb-replica-set-cluster/primary/log/mongodb.log
#监听的端口,默认为 27017
port = 27001
#以守护进程的方式运行 MongoDB
fork = true
#设置 replSet 名称
replSet = repl
secondary/mongodb.conf 文件内容
#数据文件的存放位置
dbpath = /root/mongodb-replica-set-cluster/secondary/data/db
#日志文件的存放位置
logpath = /root/mongodb-replica-set-cluster/secondary/log/mongodb.log
#监听的端口,默认为 27017
port = 27002
#以守护进程的方式运行 MongoDB
fork = true
#设置 replSet 名称
replSet = repl
arbiter/mongodb.conf 文件内容
#数据文件的存放位置
dbpath = /root/mongodb-replica-set-cluster/arbiter/data/db
#日志文件的存放位置
logpath = /root/mongodb-replica-set-cluster/arbiter/log/mongodb.log
#监听的端口,默认为 27017
port = 27003
#以守护进程的方式运行 MongoDB
fork = true
#设置 replSet 名称
replSet = repl
(4)分别开启三个数据库
cd /root/mongodb-replica-set-cluster/mongodb/mongodb-linux-x86_64-3.2.7/bin/
./mongod -f /root/mongodb-replica-set-cluster/primary/mongodb.conf
./mongod -f /root/mongodb-replica-set-cluster/secondary/mongodb.conf
./mongod -f /root/mongodb-replica-set-cluster/arbiter/mongodb.conf
之后,应该可以看到有三个 MongoDB 进程正在运行
ps aux | grep mongo
(5)连接到任意一个节点,初始化节点之间的关系
cd /root/mongodb-replica-set-cluster/mongodb/mongodb-linux-x86_64-3.2.7/bin/
./mongo mongodb://127.0.0.1:27001/admin
rs.initiate({
“_id”: “repl”,
“members”: [ // 添加数据承载节点
{
“_id”: 1,
“host”: “127.0.0.1:27001”
},
{
“_id”: 2,
“host”: “127.0.0.1:27002”
}
]
})
// { “ok” : 1 }
repl:OTHER> rs.addArb(“127.0.0.1:27003”) // 添加仲裁节点
// { “ok” : 1 }
repl:PRIMARY> rs.status() // 在设置成功后,应该可以看到具体的配置信息
3、测试副本集
打开新的终端,连接到主数据库,插入一条数据(可写),之后对其进行查询(可读)
cd /root/mongodb-replica-set-cluster/mongodb/mongodb-linux-x86_64-3.2.7/bin/
./mongo mongodb://127.0.0.1:27001/admin
PRIMARY> db.user.insert({‘username’: ‘Alice’, ‘password’: ‘123456’}) // 插入数据(可写)
// WriteResult({ “nInserted” : 1 })
PRIMARY> db.user.find() // 查询数据(可读)
// { “username” : “Alice”, “password” : “123456” }
打开另外一个终端,连接到从节点,查询在主数据库插入的数据(可读),之后尝试插入一条新的数据(不可写)
cd /root/mongodb-replica-set-cluster/mongodb/mongodb-linux-x86_64-3.2.7/bin/
./mongo mongodb://127.0.0.1:27002/admin
SECONDARY> rs.slaveOk() // 在查询之前需要先设置从节点状态
SECONDARY> db.user.find() // 查询数据(可读)
// { “username” : “Alice”, “password” : “123456” }
SECONDARY> db.user.insert({‘username’: ‘Bob’, ‘password’: ‘abc’}) // 插入数据(不可写)
// WriteResult({ “writeError” : { “code” : 10107, “errmsg” : “not master” } })
,接下来新开一个终端,手动关闭主节点,模拟主节点挂掉的情况
cd /root/mongodb-replica-set-cluster/mongodb/mongodb-linux-x86_64-3.2.7/bin/
./mongod --shutdown --dbpath /root/mongodb-replica-set-cluster/primary/data/db
然后在连接着 127.0.0.1:27002 的终端里查看当前副本集的状态
SECONDARY> rs.status()
并且可以看到该节点已经成为主节点(注意此时命令行头已经发生改变),可以在里面插入和查询数据
PRIMARY> db.user.insert({‘username’: ‘Bob’, ‘password’: ‘abc’}) // 插入数据(可写)
// WriteResult({ “nInserted” : 1 })
PRIMARY> db.user.find() // 查询数据(可读)
// { “username” : “Alice”, “password” : “123456” }
// {‘username’: ‘Bob’, ‘password’: ‘abc’}