MongoDB 复制(副本集)
MongoDB复制是将数据同步在多个服务器的过程。
复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。
复制还允许您从硬件故障和服务中断中恢复数据。
为什么要复制?
- 为了让数据安全
- 高(24* 7)数据可用性
- 灾难恢复
- 无停机维护(如备份,索引重建,压实)
- 读缩放(额外的副本读取)
- 副本集对应用程序是透明
MongoDB中复制的工作原理
MongoDB的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。
MongoDB各个节点常见的搭配方式为:一主一从、一主多从。
主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。
MongoDB复制结构图如下所示:
以上结构图中,客户端在主节点读取数据,在客户端写入数据到主节点,主节点与从节点进行数据交互保障数据的一致性
副本集特点
- N 个节点的集群
- 任何节点可作为主节点
- 所有写入操作都在主节点上
- 自动故障转移
- 自动恢复
设置一个副本集
将mongod实例转换成独立的副本集。要转换到副本设置遵循以下步骤:
关闭停止已经运行的MongoDB服务器
现在启动MongoDB服务器通过指定 --replSet 选项。
--replSet 基本语法如下:
sudo mongod --port "PORT" --dbpath "YOUR_DB_DATA_PATH" --replSet "REPLICA_SET_INSTANCE_NAME"
示例
1. 用适当的选项启动副本集的每个成员
启动副本集名称 rs0
2. mongo shell连接副本集
mongo -port 27020 --host 192.168.17.129
3. 初始化initiate
副本集
利用rs.initiate()
在副本集的一个成员上:
rs.initiate()
MongoDB会初始化一个默认的复制集配置。
4. 验证初始副本集配置
使用 rs.conf()
显示副本集配置对象:
rs.conf()
副本集配置对象如下:
rs0:OTHER> rs.conf()
{
"_id" : "rs0",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "192.168.17.129:27020",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("579b3500299da8059cc5fb99")
}
}
rs0:PRIMARY>
复制(副本集)当前的状态 rs.status()
此输出反映了副本集的当前状态,使用来自副本集的其他成员发送的心跳数据包的数据。
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2016-07-29T11:09:58.433Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "192.168.17.129:27020",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1200,
"optime" : {
"ts" : Timestamp(1469789441, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-07-29T10:50:41Z"),
"electionTime" : Timestamp(1469789440, 2),
"electionDate" : ISODate("2016-07-29T10:50:40Z"),
"configVersion" : 1,
"self" : true
}
],
"ok" : 1
}
rs0:PRIMARY>
5. 将剩下的成员添加到副本集
必须连接到副本集primary主节点, 才能使用rs.add()
添加剩余的成员。
`rs.add()`在某些情况下,触发一个选举。如果你连接到主节点primary成为从节点secondary,你需要连接Mongo shell到主节点primary继续增加新的副本集成员
利用`rs.status()`识别副本集主节点primary
下面的示例添加了两个成员:
添加节点 192.168.17.129:27021
rs0:PRIMARY> rs.add('192.168.17.129:27021')
{ "ok" : 1 }
rs0:PRIMARY>
添加节点 192.168.17.129:27022
rs0:PRIMARY> rs.add('192.168.17.129:27022')
{ "ok" : 1 }
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2016-07-29T11:17:28.721Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "192.168.17.129:27020",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1650,
"optime" : {
"ts" : Timestamp(1469791047, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-07-29T11:17:27Z"),
"electionTime" : Timestamp(1469789440, 2),
"electionDate" : ISODate("2016-07-29T10:50:40Z"),
"configVersion" : 3,
"self" : true
},
{
"_id" : 1,
"name" : "192.168.17.129:27021",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 115,
"optime" : {
"ts" : Timestamp(1469790932, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2016-07-29T11:15:32Z"),
"lastHeartbeat" : ISODate("2016-07-29T11:17:27.171Z"),
"lastHeartbeatRecv" : ISODate("2016-07-29T11:17:27.159Z"),
"pingMs" : NumberLong(0),
"configVersion" : 3
},
{
"_id" : 2,
"name" : "192.168.17.129:27022",
"health" : 1,
"state" : 0,
"stateStr" : "STARTUP",
"uptime" : 1,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2016-07-29T11:17:27.168Z"),
"lastHeartbeatRecv" : ISODate("2016-07-29T11:17:28.182Z"),
"pingMs" : NumberLong(1),
"configVersion" : -2
}
],
"ok" : 1
}
rs0:PRIMARY>
当完成时,您有一个完整功能的副本集。新的副本集将选出一个primary主节点。
6. 检查副本集的状态
使用rs.status()
操作:
rs.status()
7. 删除副本
rs.remove("192.168.17.129:27021")
副本集成员
副本集的每个成员都有一个反映它在集合中的配置的状态。
Number | Name | State Description |
---|---|---|
0 | STARTUP | 还没有任何一个的活跃成员。所有成员在这个状态下都开始。副本集的符号解析配置文件在启动。 |
1 | PRIMARY | 主节点,唯一可以接受写操作的成员 |
2 | SECONDARY | 备份节点,复制数据存储的成员 |
3 | RECOVERING | 成员要么执行启动自我检查,或过渡完成回滚或重新同步。 |
5 | STARTUP2 | 该成员已加入该组,并运行一个初始同步。 |
6 | UNKNOWN | 成员的状态,是从其他集合的成员看,目前还不知道。 |
7 | ARBITER | 仲裁者不复制数据,只是为了参加选举。 |
8 | DOWN | 节点不可到达 |
9 | ROLLBACK | 该成员正在积极执行回滚。数据不可用于读取。 |
10 | REMOVED | 该成员曾经在一个副本集,但随后被删除。 |
测试性能
批量入库
db.eval(function, arguments)
Parameter | Type | Description |
---|---|---|
function | function | 要执行的一个JavaScript函数 |
arguments | list | 参数列表传递给JavaScript函数。省略函数如果不带参数。 |
use example
db.eval(
function(num) {
for (var i=0;i<num;i++)
{
db.mycol.save({'_id':i})
}
},10000
)
- 查看一下状态
rs0:PRIMARY> rs.status()
- 查看从节点数据情况
mongo --port 27021 --host 192.168.17.129
rs0:SECONDARY> show dbs
2016-09-14T23:02:41.228+0800 E QUERY [thread1] Error: listDatabases failed:{ "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435 } :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1
shellHelper.show@src/mongo/shell/utils.js:760:19
shellHelper@src/mongo/shell/utils.js:650:15
@(shellhelp2):1:1
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
example 0.000GB
local 0.000GB
rs0:SECONDARY> db.mycol.count()
10000
注意: 主从启动之后,连接slave可以成功连上,但是在slave
中执行 show dbs
的时候就报错了:
QUERY Error: listDatabases failed:{ "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" }
解决方法:
在报错的slave机器上执行 rs.slaveOk()
方法即可。
解释一下具体slaveOk方法是什么意思?
Provides a shorthand for the following operation:
db.getMongo().setSlaveOk()
This allows the current connection to allow read operations to run on secondary members. See the readPref() method for more fine-grained control over read preference in the mongo shell.
测试
测试一下集群是否可用:
关掉主节点,模拟主节点宕机的情景
python@ubuntu:~$ ps -ef | grep mongod
python 6731 2119 0 9月13 ? 00:01:02 /opt/sublime_text/sublime_text /var/log/mongodb/mongod.log
root 37130 6046 0 22:31 pts/22 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27020 --dbpath /MongoDB/node1 --replSet rs0
root 37131 37130 0 22:31 pts/22 00:00:21 mongod --bind_ip 192.168.17.129 --port 27020 --dbpath /MongoDB/node1 --replSet rs0
root 37180 37153 0 22:31 pts/12 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27021 --dbpath /MongoDB/node2 --replSet rs0
root 37181 37180 0 22:31 pts/12 00:00:15 mongod --bind_ip 192.168.17.129 --port 27021 --dbpath /MongoDB/node2 --replSet rs0
root 37229 37202 0 22:31 pts/21 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27022 --dbpath /MongoDB/node3 --replSet rs0
root 37230 37229 0 22:31 pts/21 00:00:15 mongod --bind_ip 192.168.17.129 --port 27022 --dbpath /MongoDB/node3 --replSet rs0
python 41115 28977 0 23:08 pts/6 00:00:00 grep --color=auto mongod
python@ubuntu:~$ sudo kill -9 37131
再来查看集群状态
mongo --port 27021 --host 192.168.17.129
rs0:SECONDARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2016-09-14T15:17:34.915Z"),
"myState" : 2,
"term" : NumberLong(2),
"syncingTo" : "192.168.17.129:27022",
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "192.168.17.129:27020",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2016-09-14T15:17:33.613Z"),
"lastHeartbeatRecv" : ISODate("2016-09-14T15:16:53.392Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Connection refused",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "192.168.17.129:27021",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 2778,
"optime" : {
"ts" : Timestamp(1473866226, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2016-09-14T15:17:06Z"),
"syncingTo" : "192.168.17.129:27022",
"configVersion" : 3,
"self" : true
},
{
"_id" : 2,
"name" : "192.168.17.129:27022",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2613,
"optime" : {
"ts" : Timestamp(1473866226, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2016-09-14T15:17:06Z"),
"lastHeartbeat" : ISODate("2016-09-14T15:17:34.603Z"),
"lastHeartbeatRecv" : ISODate("2016-09-14T15:17:33.643Z"),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1473866225, 1),
"electionDate" : ISODate("2016-09-14T15:17:05Z"),
"configVersion" : 3
}
],
"ok" : 1
}