MongoDB的主从复制存在以下问题:
主节点挂了能否自动切换连接?目前需要手工切换。
主节点的读写压力过大如何解决?
从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
数据压力大到机器支撑不了的时候能否做到自动扩展?
因此,MongoDB设计了副本集和分片的功能
由图可以看到客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。我们看一下主服务器挂掉后的架构:
副本集中的副本节点在主节点挂掉后通过心跳机制检测到后,就会在集群内发起主节点的选举机制,自动选举一位新的主服务器。看起来很牛X的样子,我们赶紧操作部署一下!官方推荐的副本集机器数量为至少3个,那我们也按照这个数量配置测试。
例:我在同一台机器上的操作
cd mongod
方式一:
mkdir data/rs0-0 data/rs0-1 data/rs0-2
开启三个mongod进程,跟启普通的mongod进程基本相同,不同的跟了--replSet选项,rs0是该副本集的名称。
#./bin/mongod --dbpath=data/rs0-1/ --logpath=log/rs0-1.log --port=37018 --bind_ip 10.8.8.162 --fork --replSet rs0 #./bin/mongod --dbpath=data/rs0-2/ --logpath=log/rs0-2.log --port=37019 --bind_ip 10.8.8.162 --fork --replSet rs0 #./bin/mongod --dbpath=data/rs0-0/ --logpath=log/rs0-0.log --port=37017 --bind_ip 10.8.8.162 --fork --replSet rs0
然后我们用mongo shell连上端口为37017的mongod:
#./bin/mongo --port 37017 --host 10.8.8.162
设置副本集rs0
接着我们需要初始化一个Replica Set:首先创建一个副本集配置对象:
> rsconf={"_id":"rs0","members":[{"_id":0,"host":"10.8.8.162:37017"}]} { "_id" : "rs0", "members" : [ { "_id" : 0, "host" : "10.8.8.162:37017" } ] } > rs.initiate(rsconf) ###然后用rs.initiate()进程初始化### { "info" : "Config now saved locally. Should come online in about a minute.", "ok" : 1 } > rs.status()##会发现37017这个端口的mongod进程默认就是PRIMARY## { "set" : "rs0", "date" : ISODate("2014-04-18T06:46:14Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "10.8.8.162:37017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 399, "optime" : Timestamp(1397803396, 1), "optimeDate" : ISODate("2014-04-18T06:43:16Z"), "self" : true } ], "ok" : 1 } rs0:PRIMARY> rs.add("10.8.8.162:37018")##通过rs.add()将另外两个mongod添加到副本集当中## { "ok" : 1 } rs0:PRIMARY> rs.add("10.8.8.162:37019") { "ok" : 1 } rs0:PRIMARY> rs.status()##这个可能需要几分钟才能看见## { "set" : "rs0", "date" : ISODate("2014-04-18T06:51:50Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "10.8.8.162:37017", "health" : 1,##1表明状态正常,0表示不正常## "state" : 1,##1表示PRIMARY,2表示SECONDARY## "stateStr" : "PRIMARY", "uptime" : 735, "optime" : Timestamp(1397803597, 1), "optimeDate" : ISODate("2014-04-18T06:46:37Z"), "self" : true }, { "_id" : 1, "name" : "10.8.8.162:37018", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 318, "optime" : Timestamp(1397803597, 1), "optimeDate" : ISODate("2014-04-18T06:46:37Z"), "lastHeartbeat" : ISODate("2014-04-18T06:51:49Z"), "lastHeartbeatRecv" : ISODate("2014-04-18T06:51:50Z"), "pingMs" : 0, "syncingTo" : "10.8.8.162:37017" }, { "_id" : 2, "name" : "10.8.8.162:37019", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 313, "optime" : Timestamp(1397803597, 1), "optimeDate" : ISODate("2014-04-18T06:46:37Z"), "lastHeartbeat" : ISODate("2014-04-18T06:51:49Z"), "lastHeartbeatRecv" : ISODate("2014-04-18T06:51:49Z"), "pingMs" : 0, "syncingTo" : "10.8.8.162:37017" } ], "ok" : 1 }
在两个SECONDARY节点上
#./bin/mongo --port 37018 --host 10.8.8.162 MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:37018/test rs0:SECONDARY> rs.status(); #./bin/mongo --port 37019 --host 10.8.8.162 MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:37019/test rs0:SECONDARY> rs.status();
方式二
#./bin/mongod --dbpath=data/rs0-0/ --logpath=log/rs0-0.log --bind_ip 10.8.8.162 --fork --rest --logappend --replSet rs0 --port=30000 ##注:--rest是为了打开web监控。http://10.8.8.162:31000/_replSet可以查看各个节点的状态 #./bin/mongod --dbpath=data/rs0-1/ --logpath=log/rs0-1.log --bind_ip 10.8.8.162 --fork --rest --logappend --replSet rs0 --port=30001 #./bin/mongod --dbpath=data/rs0-2/ --logpath=log/rs0-2.log --bind_ip 10.8.8.162 --fork --rest --logappend --replSet rs0 --port=30002 #./bin/mongod --dbpath=data/rs0-arb/ --logpath=log/rs0-arb.log --bind_ip 10.8.8.162 --fork --rest --logappend --replSet rs0 --port=40000 #./bin/mongo --port 30000 --host 10.8.8.162 MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:30000/test > rs.conf() null > use admin switched to db admin > db.runCommand({"replSetInitiate" : { ... "_id":"rs0",##这个键指明了副本集的名称,必须与气动mongod进程时指定的名称一致!## ... "members":[ ##这个键指明服务器列表,我们以后还可以往副本集中加入服务器## ... {"_id":0,"host":"10.8.8.162:30000","priority":2},##“id”内嵌文档的键,用于唯一标示副本集中的某一台服务器."priority :N,优先级,指明一个服务器的优先级,默认为1,可以是[0,1000],通过这个我们可以指明副本集某台服务器节点初始为活跃节点"## ... {"_id":1,"host":"10.8.8.162:30001","priority":3}, ... {"_id":2,"host":"10.8.8.162:30002","priority":4}, ... {"_id":3,"host":"10.8.8.162:40000","arbiterOnly":"true"}##arbiterOnly :true,仲裁节点,特定指明某个服务器节点为仲裁节点,仲裁节点不会复制数据,不会成为活跃节点,其存在的目的只有一个:当前活跃节点失效后,副本集内重新投票选活跃节点时,防止出现僵局!## ... ] ... } ... } ... ); { "info" : "Config now saved locally. Should come online in about a minute.", "ok" : 1 } rs0:SECONDARY> rs.status();##这个可能需要数分钟才能正常显示!## { "set" : "rs0", "date" : ISODate("2014-04-18T09:06:41Z"), "myState" : 2, "syncingTo" : "10.8.8.162:30002", "members" : [ { "_id" : 0, "name" : "10.8.8.162:30000", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 1411, "optime" : Timestamp(1397811410, 1), "optimeDate" : ISODate("2014-04-18T08:56:50Z"), "self" : true }, { "_id" : 1, "name" : "10.8.8.162:30001", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 591, "optime" : Timestamp(1397811410, 1), "optimeDate" : ISODate("2014-04-18T08:56:50Z"), "lastHeartbeat" : ISODate("2014-04-18T09:06:39Z"), "lastHeartbeatRecv" : ISODate("2014-04-18T09:06:39Z"), "pingMs" : 0, "syncingTo" : "10.8.8.162:30002" }, { "_id" : 2, "name" : "10.8.8.162:30002", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 591, "optime" : Timestamp(1397811410, 1), "optimeDate" : ISODate("2014-04-18T08:56:50Z"), "lastHeartbeat" : ISODate("2014-04-18T09:06:40Z"), "lastHeartbeatRecv" : ISODate("2014-04-18T09:06:39Z"), "pingMs" : 0, "syncingTo" : "10.8.8.162:30000"##开始我觉得这里有些奇怪!当我用30002这个PRIMARY节点登陆之后再查看,显示就正常了!## }, { "_id" : 3, "name" : "10.8.8.162:40000", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 562, "lastHeartbeat" : ISODate("2014-04-18T09:06:40Z"), "lastHeartbeatRecv" : ISODate("2014-04-18T09:06:40Z"), "pingMs" : 0 } ], "ok" : 1 }
至此,Mongod的副本集rs0就做完了!
节点切换
kill掉37017端口的mongod进程
#./bin/mongo --port 37018 --host 10.8.8.162 MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:37018/test rs0:SECONDARY> rs.status(); #./bin/mongo --port 37018 --host 10.8.8.162###会发现37019端口的mongod进程变成了PRIMARY# MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:37018/test rs0:PRIMARY> rs.status();
此时,在作为PRIMARY节点的37109进程上就可以查看数据!
再把37017端口的mongod进程启动
#rm -fr data/rs0-0/* #rm -fr log/rs0-0.log #./bin/mongod --dbpath=data/rs0-0/ --logpath=log/rs0-0.log --port=37017 --bind_ip 10.8.8.162 --fork --replSet rs0
会发现37019依然还是PRIMARY,而重启起来的37017就是SECONDARY
但,此时在PRIMARY节点上插入数据,在SECONDARY节点是不可以进行数据查看的!因为mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。
#./bin/mongo --port 37017 --host 10.8.8.162 MongoDB shell version: 2.4.5 connecting to: 10.8.8.162:37017/test rs0:SECONDARY> show dbs##能够查看有哪些数据库## local 8.07421875GB test 0.203125GB rs0:SECONDARY> use test switched to db test rs0:SECONDARY> show collections;##但是不能查看数据## Fri Apr 18 15:35:55.525 JavaScript execution failed: error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:L128 rs0:SECONDARY> rs.slaveOk()##开启从库查询功能,或者用db.getMongo().setSlaveOk()## rs0:SECONDARY> show collections; system.indexes table1 rs0:SECONDARY> db.table1.find() { "_id" : ObjectId("5350cbe620f384efd9ac5583"), "x" : 1 } { "_id" : ObjectId("5350d5b974b8eddf14724359"), "id" : 1, "name" : "ycy" } { "_id" : ObjectId("5350d60174b8eddf1472435a"), "id" : 2, "name" : "yu" } rs0:SECONDARY> db.table1.insert({"id":3,"age":26})##在SECONDARY节点上插入数据是不行的,看来是PRIMARY是读写、SECONDARY是只读## not master
增加、删除节点
rs0:PRIMARY> rs.remove("10.8.8.162:37017")##从副本集rs0中删除一个节点## Fri Apr 18 16:22:49.591 DBClientCursor::init call() failed Fri Apr 18 16:22:49.593 JavaScript execution failed: Error: error doing query: failed at src/mongo/shell/query.js:L78 Fri Apr 18 16:22:49.594 trying reconnect to 10.8.8.162:37019 Fri Apr 18 16:22:49.595 reconnect 10.8.8.162:37019 ok rs0:PRIMARY> rs.add("10.8.8.162:37017")##再把删除的节点添加进来## { "down" : [ "10.8.8.162:37017" ], "ok" : 1 }
如果我想让300这个端口的Mongod进程升级为PRIMORY节点
rs0:PRIMARY> cfg=rs.conf() rs0:PRIMARY> cfg.members[0].priority = 5##设置“id”为“0”的节点,priority为5## 5 rs0:PRIMARY> rs.reconfig(cfg) rs0:SECONDARY> rs.conf()##查看配置文件,就会发现30001的priority变成5## rs0:SECONDARY> rs.status();##过几分钟再看,就会发现30001变成了PRIMARY节点##
注:此操作必须在PROMARY节点上进行!
副本集(replica set)
MongoDB的replica set是一个mongod进程实例簇,数据在这个簇中相互复制,并自动进行故障切换。
MongoDB的数据库复制增加了冗余,确保了高可用性,简化了管理任务如备份,并且增加了读能力。大多数产品部署都使用了复制。MongoDB中primary处理写操作,其它进行复制的成员则是secondaries。
一个副本集可以最多支持12个成员,但是只有7个成员可以参与投票。
注:MongoDB同时提供了传统的master/slave复制,这种复制的操作方法与副本集相同,但是master/slave复制不支持自动故障切换。很容易理解,主备模式下,cli端是指定了地址和端口进行mongodb的访问的,而副本集模式则是通过访问mongos来隐藏动态切换的。