Primary节点写入数据,Secondary经过读取Primary的oplog获得复制信息,开始复制数据而且将复制信息写入到本身的oplog。若是某个操做失败,则备份节点中止从当前数据源复制数据。若是某个备份节点因为某些缘由挂掉了,当从新启动后,就会自动从oplog的最后一个操做开始同步,同步完成后,将信息写入本身的oplog,因为复制操做是先复制数据,复制完成后再写入oplog,有可能相同的操做会同步两份,不过MongoDB在设计之初就考虑到这个问题,将oplog的同一个操做执行屡次,与执行一次的效果是同样的。简单的说就是:python
当Primary节点完成数据操做后,Secondary会作出一系列的动做保证数据的同步:
1:检查本身local库的oplog.rs集合找出最近的时间戳。
2:检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录。
3:将找到的记录插入到本身的oplog.rs集合中,并执行这些操做 linux
副本集的同步和主从同步同样,都是异步同步的过程,不一样的是副本集有个自动故障转移的功能。其原理是:slave端从primary端获取日志,而后在本身身上彻底顺序的执行日志所记录的各类操做(该日志是不记录查询操做的),这个日志就是local数据 库中的oplog.rs表,默认在64位机器上这个表是比较大的,占磁盘大小的5%,oplog.rs的大小能够在启动参数中设 定:--oplogSize 1000,单位是M。mongodb
注意:在副本集的环境中,要是全部的Secondary都宕机了,只剩下Primary。最后Primary会变成Secondary,不能提供服务数据库
环境说明vim
IP | 角色 |
172.16.1.216 | primary(主节点) |
172.16.1.223 | secondary(副本节点) |
172.16.1.215 | arbiterOnly(仲裁节点) |
三台机器解压mongodb安装包服务器
# curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.4.6.tgz
# tar -zxvf mongodb-linux-x86_64-amazon-3.4.6.tgz
# mv mongodb-linux-x86_64-amazon-3.4.6/ /usr/local/mongodb
分别在三台机器上建立mongoDB的data,log以及配置目录app
# cd /usr/local/mongodb
# mkdir -p data/db
# mkdir log
# touch log/mongod.log
# mkdir etc
# touch etc/mongod.conf
分别在三台机器上配置config文件curl
vim /usr/local/mongodb/etc/mongod.conf
dbpath = /usr/local/mongodb/data/db # 指定数据库路径
logpath = /usr/local/mongodb/log/mongod.log # 指定mongodb日志文件
logappend = true # 使用追加的方式写日志
port = 27017 #设置端口号为27017
fork = true #设置以守护进程的方式启动mongod
replSet = timecash #设置副本集的名字为myrs,同一副本集群的replSet名称必需相同
分别在三台机器上启动副本集异步
./usr/local/mongodb/bin/mongod -f /usr/local/mongodb/etc/mongod.conf #指定以mongod.conf配置启动mongod
初始化副本集测试
登陆任意一台机器的mongodb执行
./usr/local/mongodb/bin/mongo
>>>use admin
>>>config = {
"_id":"myrs",
"members":[
{"_id":0,"host":"172.16.1.216:27017"},
{"_id":1,"host":"172.16.1.223:27017"},
{"_id":2,"host":"172.16.1.215:27017",arbiterOnly:true} #这个节点就是仲裁节点
]
}
>>>rs.initiate(config); #初始化配置
{ "ok" : 1 }
查看集群节点
>>>rs.status();
{
"set" : "timecash",
"date" : ISODate("2018-03-08T08:29:32.268Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "172.16.1.216:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 788,
"optime" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-03-08T08:29:24Z"),
"electionTime" : Timestamp(1520497194, 1),
"electionDate" : ISODate("2018-03-08T08:19:54Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 1,
"name" : "172.16.1.223:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 591,
"optime" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1520497764, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2018-03-08T08:29:24Z"),
"optimeDurableDate" : ISODate("2018-03-08T08:29:24Z"),
"lastHeartbeat" : ISODate("2018-03-08T08:29:30.443Z"),
"lastHeartbeatRecv" : ISODate("2018-03-08T08:29:30.680Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "172.16.1.216:27017",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "172.16.1.215:27017",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 591,
"lastHeartbeat" : ISODate("2018-03-08T08:29:30.431Z"),
"lastHeartbeatRecv" : ISODate("2018-03-08T08:29:27.927Z"),
"pingMs" : NumberLong(0),
"configVersion" : 1
}
],
"ok" : 1
}
#能够看出172.16.1.216是主节点,172.16.1.223是副本节点,172.16.1.215是仲裁节点
测试
1.测试副本集的数据复制功能
登陆172.16.1.216主节点,用mongo登陆到交互模式
./usr/local/mongodb/bin/mongo
timecash:PRIMARY> use test #建立test数据库
timecash:PRIMARY> db.testdb.insert({"name":"alex"}); #插入数据
登陆172.16.1.223副本节点,用mongo登陆到交互模式
./usr/local/mongodb/bin/mongo
timecash:SECONDARY>use test
timecash:SECONDARY>show collections
[thread1] Error: listCollections failed: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
}
#能够看到报错了 由于mongodb默认是从主节点读写数据的,副本节点上不容许读,须要设置副本节点能够读
timecash:SECONDARY>db.getMongo().setSlaveOk();
#此时副本节点就能够读取数据了
timecash:SECONDARY>db.testdb.find();
{ "_id" : ObjectId("59676d711881041abab44477"), "name" : "alex" }
2.测试副本集的故障转移功能
timecash:PRIMARY> use admin
timecash:PRIMARY> db.shutdownServer() #停掉 主节点
此时能够登陆到副本节点查看,副本节点已经升级为主节点
验证(Pyhton)
1.主节点断开,看是否影响写入
(如今主是216,从是223)
脚本
#!/bin/env python
import time
from pymongo import MongoClient
#两地址
CONN_ADDR1 = '172.16.1.216:27017'
CONN_ADDR2 = '172.16.1.223:27017'
REPLICAT_SET = 'timecash'
#username = 'demouser'
#password = '123456'
#获取mongoclient
client = MongoClient([CONN_ADDR1, CONN_ADDR2], replicaSet=REPLICAT_SET)
for i in range(1000):
try:
client.test.tt.insert({"name":"test" + str(i)})
time.sleep(1)
print(client.primary,'主')
print(client.secondaries,'从')
except Exception as e:
print(e)
脚本执行打印的内容
# python3.5 test.py
('172.16.1.216', 27017) 主 #刚开始 216是主,223是从
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
('172.16.1.216', 27017) 主
{('172.16.1.223', 27017)} 从
connection closed #到这的时候,我把主(216)停了,而后发现写不进数据了
('172.16.1.223', 27017) 主 #到这的时候,mongo副本集的高可用自动把主切换到223了,216起来的时候 就会变成从
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主
set() 从
('172.16.1.223', 27017) 主 #到这的时候,216起来了 就称为从了
{('172.16.1.216', 27017)} 从
('172.16.1.223', 27017) 主
{('172.16.1.216', 27017)} 从
('172.16.1.223', 27017) 主
{('172.16.1.216', 27017)} 从
('172.16.1.223', 27017) 主
{('172.16.1.216', 27017)} 从
('172.16.1.223', 27017) 主
{('172.16.1.216', 27017)} 从
上面是 在执行脚本的时候,模拟主(216)宕机,看到主 迁移到了223上,在把216起来以后 变成 从了. 其中在主(216)挂了以后 切换的过程当中 有一段数据写入丢了
{ "name" : "GOODODOO15" }
{ "name" : "GOODODOO592" }
{ "name" : "GOODODOO593" }
#其实这部分数据是因为在选举过程期间丢失的,要是不容许数据丢失,则把在选举期间的数据放到队列中,等到找到新的Primary,再写入