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