细说MongoDB复制集
一. MongoDB复制集特点:
1. 由一组Mongod实例组成, 可以在一台机器上, 也可以在多台机器上,甚至可以在多个机房。
2. PRIMAY节点承担写请求,而SECONDARY通过同步数据来让集群内数据一致, 并且SECONDARY节点 可以担读请求(需在连接会话里设置db.getMongo().setSlaveOk();)
如果要为MONGODB 建立异地容灾, 可考虑在异地机房建立SECONDARY 节点,保证数据冗余。
注: 在设置异地节点时,我们需要考虑一个问题,应用是否会发现这个节点,如果会,我们又该如何规避这个问题?
二. MongoDB复制集的角色:
Primary: 承担所有请求,一个复制集里只能有一个主库。
Secondary: 同步数据,在主库出现问时,可切为主库, 以如下三种Secondary类型:
Priority:0 只同步数据, 不会被切为主库。
Hidden:true 同步数据,并且不会被应用程序节点所识别到。
slaveDelay:3600 保持延迟一个小时, 在设置此类型的SECONDARY节点时,必须保证不会被切为主库,并且不被应用程序所识别。
Arbiter: 不同步数据,只参与投票,且不能作为Primary.
思考:
在MONGODB的复制集中是没有VIP的概念的, 那么我们如何设置能够保证在主节点出问题时应用能够继续正常服务?
三. 正确使用MONGODB 复制集的姿势:
如:这里是使用一主两SECONDARY节点, 而其中有一个节点为异地机房, 且设置成为了隐藏属性。
>>> import pymongo >>> from pymongo import MongoClient >>> MongoClient("mongodb://192.168.1.51:27027,192.168.1.52:27027", replicaSet='pktest') MongoClient(host=['192.168.1.51:27027', '192.168.1.52:27027'], document_class=dict, tz_aware=False, connect=True, replicaset='pktest') >>> db=MongoClient("mongodb://192.168.1.51:27027,192.168.1.52:27027", replicaSet='pktest').test >>> db.test.find_one() {u'a': 11L, u'_id': 1L}
模拟主库DOWN掉:
pktest:PRIMARY> use admin; switched to db admin pktest:PRIMARY> db.shutdownServer() 2016-12-21T17:24:22.103+0800 I NETWORK DBClientCursor::init call() failed server should be down... 2016-12-21T17:24:22.105+0800 I NETWORK trying reconnect to 127.0.0.1:27027 (127.0.0.1) failed 2016-12-21T17:24:22.105+0800 I NETWORK reconnect 127.0.0.1:27027 (127.0.0.1) ok 2016-12-21T17:24:22.722+0800 I NETWORK Socket recv() errno:104 Connection reset by peer 127.0.0.1:27027 2016-12-21T17:24:22.722+0800 I NETWORK SocketException: remote: 127.0.0.1:27027 error: 9001 socket exception [RECV_ERROR] server [127.0.0.1:27027] 2016-12-21T17:24:22.722+0800 I NETWORK DBClientCursor::init call() failed 2016-12-21T17:24:22.725+0800 I NETWORK trying reconnect to 127.0.0.1:27027 (127.0.0.1) failed 2016-12-21T17:24:22.725+0800 W NETWORK Failed to connect to 127.0.0.1:27027, reason: errno:111 Connection refused 2016-12-21T17:24:22.725+0800 I NETWORK reconnect 127.0.0.1:27027 (127.0.0.1) failed failed couldn't connect to server 127.0.0.1:27027 (127.0.0.1), connection attempt failed >
再次去查数据库:
>>> db.test.find_one() {u'a': 11L, u'_id': 1L} >>> db.test.find_one() {u'a': 11L, u'_id': 1L} >>> db.test.find_one() {u'a': 11L, u'_id': 1L} >>> db=MongoClient("mongodb://192.168.1.51:27027,192.168.1.52:27027", replicaSet='pktest').test >>> db.test.find_one() {u'a': 11L, u'_id': 1L}
当挂掉一个节点后,MONGODB 复制集仍然可以正常提供服务。
思考: 在主库挂掉之后都发生了什么?
四. 复制集的选举:
选出支持票数最多的节点来做为主库,为业务提供服务。
选举触发条件:
1. 初始化复制集。
2. 执行了RECONFIG命令以后。
3. 主节点DOWN掉以后(注意理解DOWN的意思, 并不一定是真的DOWN掉)。
4. 手动执行stepDown()进行主库降级操作。
在选举时会考虑如下因素:
1. 复制集中节点间的心跳检测(每2秒一发送一次心跳,达到10秒没有某个节点的心跳,则集群中其它节点会认为已DOWN机) 。
2. 复制集中节点设置的优先级(优先级为0的不会被选为主库)。
3. 复制集节点的OPTIME是否最新。
4. 网络是否连通。
5. 复制集内存活成员数量是否满足大多数的定义。比如一个三节点的复制集一下DOWN掉两个,那整个复制集是无法选择出主库的,复制集会处于只读状态,拒绝写服务。 因此我们在做规划时要确定好复制集的节点数,遵守奇数原则。
下面列举一些集群,当达到多少节点DOWN掉之后,集群就变得不可用:
节点数 | 允许失效数 |
3 | 1 |
5 | 2 |
7 | 3 |
9 | 4 |
11 | 5 |
三节点复制集,其中有一个从库的优先级为0:
A: primary priority:1
B:secondary priority:1
C: secondary priority:0
当主库DOWN掉时:
B: 我都发了5次心跳包了,没有收到A的心跳了。
C: 同上。
B: 我的优先级为1.
C: 我的优先级为0,我不能被选为主,我将票投给B。
B: 我有两票了, 我可以成为主库了。
五. 搭建复制集:
经过前面的了解, 我们对复制集有一个大概的理解了,下面我们动手搭建一个复制集起来吧!
服务器IP | 复制集角色 |
192.168.1.77 | Primay |
192.168.1.78 | Secondary |
192.168.1.51 | Secondary(异地机房) |
1. 在各个节点上安装好MONGODB:
准备参数文件:
dbpath = /data/dbdata/mongodb_27017/data directoryperdb = true #master = true port = 27017 fork = true logappend = true logpath = /data/dbdata/mongodb_27017/mongodb_27017.log journal = true nohttpinterface = true cpu = true #Replica Set oplogSize = 2000 replSet = rstest
创建所需要的目录。
在所有节点上启动数据库:
/apps/svr/mongo2.6/bin/mongod -f /apps/conf/mongo_27017.cnf
所有节点启动好后,进行设置。
2. 设置复制集:
[root@VM-1-77 conf]# /apps/svr/mongo2.6/bin/mongo 127.0.0.1:27017 MongoDB shell version: 2.6.12 connecting to: 127.0.0.1:27017/test > config={_id:"rstest",members:[{_id:0, host:"192.168.1.77"},{_id:1, host:"192.168.1.78"},{_id:2, host:"192.168.1.51",hidden:true, priority:0}]} #设置复制集节点 { "_id" : "rstest", "members" : [ { "_id" : 0, "host" : "192.168.1.77" }, { "_id" : 1, "host" : "192.168.1.78" }, { "_id" : 2, "host" : "192.168.1.51", "hidden" : true, "priority" : 0 } ] } > rs.initiate(config) #初始化复制集的配置 { "info" : "Config now saved locally. Should come online in about a minute.", "ok" : 1 } > rs.conf() #查看复制集的成员以及成员属性 { "_id" : "rstest", "version" : 1, "members" : [ { "_id" : 0, "host" : "192.168.1.77:27017" }, { "_id" : 1, "host" : "192.168.1.78:27017" }, { "_id" : 2, "host" : "192.168.1.51:27017", "priority" : 0, "hidden" : true } ] } rstest:PRIMARY> rs.status() #查看复制集状态 { "set" : "rstest", "date" : ISODate("2016-12-21T10:42:25Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "192.168.1.77:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 391, "optime" : Timestamp(1482316913, 1), "optimeDate" : ISODate("2016-12-21T10:41:53Z"), "electionTime" : Timestamp(1482316924, 1), "electionDate" : ISODate("2016-12-21T10:42:04Z"), "self" : true }, { "_id" : 1, "name" : "192.168.1.78:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 29, "optime" : Timestamp(1482316913, 1), "optimeDate" : ISODate("2016-12-21T10:41:53Z"), "lastHeartbeat" : ISODate("2016-12-21T10:42:24Z"), "lastHeartbeatRecv" : ISODate("2016-12-21T10:42:24Z"), "pingMs" : 2, "syncingTo" : "192.168.1.77:27017" }, { "_id" : 2, "name" : "192.168.1.51:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 29, "optime" : Timestamp(1482316913, 1), "optimeDate" : ISODate("2016-12-21T10:41:53Z"), "lastHeartbeat" : ISODate("2016-12-21T10:42:24Z"), "lastHeartbeatRecv" : ISODate("2016-12-21T10:42:24Z"), "pingMs" : 1, "syncingTo" : "192.168.1.77:27017" } ], "ok" : 1 } rstest:PRIMARY>
3. 验证数据同步效果:
主库操作:
[root@VM-1-77]# /apps/svr/mongo2.6/bin/mongo 127.0.0.1:27017 MongoDB shell version: 2.6.12 connecting to: 127.0.0.1:27017/test rstest:PRIMARY> for(i=0;i<20;i++){db.scores.save({student:i,score:20})}; WriteResult({ "nInserted" : 1 }) rstest:PRIMARY> db.scores.count(); 20
从库查询:
[root@VM-1-78 ]# /apps/svr/mongo2.6/bin/mongo 127.0.0.1:27017 MongoDB shell version: 2.6.12 connecting to: 127.0.0.1:27017/test rstest:SECONDARY> db.getMongo().setSlaveOk(); rstest:SECONDARY> db.scores.count(); 20 rstest:SECONDARY>