MongoDB是一个跨平台的,面向文档的数据库,是当前NoSQL数据库产品中最热门的一种,是一种介于关系数据库和非关系数据库之间的产品。它支持的数据结构非常松散,类似json格式,可以存储比较复杂的数据类型。
Mongodb的集群模式有三种:Master-Slaver模式、Replica Set模式、sharding模式,其中Replica set应用最为广泛。
Replica Set是一组MongoDB实例组成的集群,由一个主(Primary)节点和多个备份(Secondary)节点构成。通过Replication,由Primary将更新的数据同步到其他实例上,这样每个MongoDB实例都有相同的数据集副本。当主节点崩溃,备份节点会自动将其中权重最高的成员升级为新的主节点;当集群为偶数台时,特别是一主一从架构时,还需要加入一个仲裁(Arbiter)节点,参与升级投票。Replica Set操作中一般读写数据都是在主(Primary)节点上,需要手动指定读库的备份(Secondary)节点,从而实现负载均衡。
Replica Set通过维护冗余的数据库副本,能够实现数据的异地备份,读写分离和自动故障转移。
下面我们搭建MongoDB一主一从架构的Replica Set集群。
1.配置多实例
Replica Set需要3个实例,我们来配置MongoDB的多实例。
1.1 创建3个实例目录
jishu@Jishu:~$ mkdir -p mongodb/mongo1/db
jishu@Jishu:~$ mkdir -p mongodb/mongo2/db
jishu@Jishu:~$ mkdir -p mongodb/mongo3/db
1.2 创建3个实例的配置文件
在mongodb/mongo1目录下创建配置文件mongod.conf。
systemLog:
destination: file
path: "/home/jishu/mongodb/mongo1/mongod.log"
logAppend: true
storage:
dbPath: /home/jishu/mongodb/mongo1/db
journal:
enabled: true
processManagement:
fork: true
security:
authorization: disabled
net:
bindIp: 127.0.0.1
port: 27017
setParameter:
enableLocalhostAuthBypass: false
replication:
replSetName: test
在mongodb/mongo2目录下创建配置文件mongod.conf。
systemLog:
destination: file
path: "/home/jishu/mongodb/mongo2/mongod.log"
logAppend: true
storage:
dbPath: /home/jishu/mongodb/mongo2/db
journal:
enabled: true
processManagement:
fork: true
security:
authorization: disabled
net:
bindIp: 127.0.0.1
port: 27018
setParameter:
enableLocalhostAuthBypass: false
replication:
replSetName: test
在mongodb/mongo3目录下创建配置文件mongod.conf。
systemLog:
destination: file
path: "/home/jishu/mongodb/mongo3/mongod.log"
logAppend: true
storage:
dbPath: /home/jishu/mongodb/mongo3/db
journal:
enabled: true
processManagement:
fork: true
security:
authorization: disabled
net:
bindIp: 127.0.0.1
port: 27019
setParameter:
enableLocalhostAuthBypass: false
replication:
replSetName: test
1.2 启动3个实例
jishu@Jishu:~/mongodb$ sudo mongod --config mongo1/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1415
child process started successfully, parent exiting
jishu@Jishu:~/mongodb$ sudo mongod --config mongo2/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1497
child process started successfully, parent exiting
jishu@Jishu:~/mongodb$ sudo mongod --config mongo3/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1542
child process started successfully, parent exiting
jishu@Jishu:~/mongodb$ sudo ps -A | grep "mongod"
1415 ? 00:00:01 mongod
1497 ? 00:00:00 mongod
1542 ? 00:00:00 mongod
3个实例启动完成。
2.配置ReplicaSet集群
参照ReplicaSet集群的角色,把mongo1作为主(Primary)节点,mongo2作为备份(Secondary)节点,mongo3作为仲裁(Arbiter)节点。
集群需要在主(Primary)节点上进行。
连接主(Primary)节点的MongoDB:
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017
MongoDB shell version v4.2.6
connecting to: mongodb://127.0.0.1:27017/test? compressors=disabled&gssapiServiceName=mongodb
......
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
>
配置集群参数:
> use admin
switched to db admin
> cfg={ _id:"test", members:[ {_id:0,host:'127.0.0.1:27017',priority:2}, {_id:1,host:'127.0.0.1:27018',priority:1}, {_id:2,host:'127.0.0.1:27019',arbiterOnly:true}] };
{
"_id" : "test",
"members" : [
{
"_id" : 0,
"host" : "127.0.0.1:27017",
"priority" : 2
},
{
"_id" : 1,
"host" : "127.0.0.1:27018",
"priority" : 1
},
{
"_id" : 2,
"host" : "127.0.0.1:27019",
"arbiterOnly" : true
}
]
}
> rs.initiate(cfg)
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1590067297, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1590067297, 1)
}
备注:
集群配置的cfg名字可自定义,只要跟mongodb参数不冲突,_id参数为Replica Set名字,members里面的优先级priority值高的为主(Primary)节点,对于仲裁(Arbiter)点一定要加上arbiterOnly:true,否则主备模式不生效。
集群cfg配置生效:rs.initiate(cfg)
集群配置生效后,我们查看一下集群状态:rs.status()
test:PRIMARY> rs.status()
{
"set" : "test",
"date" : ISODate("2020-05-21T13:24:42.872Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
......
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1458,
......
},
{
"_id" : 1,
"name" : "127.0.0.1:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 185,
......
},
{
"_id" : 2,
"name" : "127.0.0.1:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 185,
.....
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1590067478, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1590067478, 1)
}
可以看到,集群状态正常,其中"stateStr" : "PRIMARY"表示主(Primary)节点, "stateStr" : "SECONDARY"表示备份(Secondary)节点, "stateStr" : "ARBITER,表示仲裁(Arbiter)节点。
2.5 集群验证
集群搭建好了,接下来我们用停掉/重启主(Primary)节点的方法来看一下集群是否能正常切换主备节点。
直接kill掉主(Primary)节点mongo1的进程,然后连接mongo2的备份(Secondary)节点,查询集群状态。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27018
MongoDB shell version v4.2.6
......
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
test:PRIMARY> rs.status()
{
"set" : "test",
"date" : ISODate("2020-05-21T13:34:35.681Z"),
"myState" : 1,
"term" : NumberLong(2),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
......
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
......
},
{
"_id" : 1,
"name" : "127.0.0.1:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2030,
......
},
{
"_id" : 2,
"name" : "127.0.0.1:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 778,
......
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1590068075, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1590068075, 1)
}
可以看到,集群中原先的主(Primary)节点状态变为"stateStr" : "(not reachable/healthy)", 原先的备份(Secondary)节点状态变为"stateStr" : "PRIMARY",变为新的主(Primary)节点。
重启主(Primary)节点后,再查询集群状态,又恢复成原先的状态。
从上述验证中,可以看到集群的主备节点切换正常。
2.6 配置keyfile
为了保证集群之间的安全性,需要增加KeyFile安全认证机制。
使用keyfile认证,副本集中的每个mongod实例使用keyfile内容认证其他成员。只有有正确的keyfile的mongod实例可以加入副本集。keyfile的内容必须是6到1024个字符的长度,且副本集所有成员的keyfile内容必须相同。
开启keyfile认证会默认开启auth认证,需要用户的auth认证才能连接MongoDB数据库,所以还需要创建用户。
在集群中先创建用户。
test:PRIMARY> use admin
switched to db admin
#创建数据库管理员
test:PRIMARY> db.createUser({user:"admin",pwd:"admin",roles:[{role:"readWriteAnyDatabase",db:"admin"},{role:"dbAdminAnyDatabase",db:"admin"},{role:"userAdminAnyDatabase",db:"admin"}]})
#创建集群管理员
test:PRIMARY> db.createUser({user:"cluster",pwd:"cluster",roles:[{role:"clusterAdmin",db:"admin"},{role:"clusterManager",db:"admin"},{role:"clusterMonitor",db:"admin"}]})
用户创建好之后,关闭主备节点和arbiter节点的服务,可以直接kill掉进程。
创建keyfile文件,并修改权限。
jishu@Jishu:~/mongodb$ sudo openssl rand -base64 128 > ./mongodb.key
jishu@Jishu:~/mongodb$ sudo chmod 600 ./mongodb.key
修改3个实例的配置文件,添加认证机制。
security:
authorization: enabled
keyFile: /home/jishu/mongodb/mongodb.key
重新启动3个实例。
重新连接主(Primary)节点。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin
MongoDB shell version v4.2.6
connecting to: mongodb://127.0.0.1:27017/admin? compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("188cd807-0a9d-46f1-bab9-c485c4e7e919") }
MongoDB server version: 4.2.6
test:PRIMARY> rs.status()
{
"operationTime" : Timestamp(1590072034, 1),
"ok" : 0,
"errmsg" : "command replSetGetStatus requires authentication",
"code" : 13,
"codeName" : "Unauthorized",
"$clusterTime" : {
"clusterTime" : Timestamp(1590072034, 1),
"signature" : {
"hash" : BinData(0,"sUrYE7ATx1FC8rBeWBKkOEY61wk="),
"keyId" : NumberLong("6829287086298759171")
}
}
}
如果不进行用户认证连接MongoDB,将无法查看集群状态。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u admin -p admin
MongoDB shell version v4.2.6
connecting to: mongodb://127.0.0.1:27017/admin? compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("188cd807-0a9d-46f1-bab9-c485c4e7e919") }
MongoDB server version: 4.2.6
test:PRIMARY> rs.status()
{
"operationTime" : Timestamp(1590074047, 1),
"ok" : 0,
"errmsg" : "command replSetGetStatus requires authentication",
"code" : 13,
"codeName" : "Unauthorized",
"$clusterTime" : {
"clusterTime" : Timestamp(1590074047, 1),
"signature" : {
"hash" : BinData(0,"sUrYE7ATx1FC8rBeWBKkOEY61wk="),
"keyId" : NumberLong("6829287086298759171")
}
}
}
使用admin账号,也无法查看集群状态。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u cluster -p cluster
MongoDB shell version v4.2.6
connecting to: mongodb://127.0.0.1:27017/admin?compressors=disabled&gssapiServiceName=mongodb
......
test:PRIMARY> rs.status()
{
"set" : "test",
"date" : ISODate("2020-05-21T15:12:19.580Z"),
"myState" : 1,
"term" : NumberLong(9),
"syncingTo" : "",
......
}
使用cluster账号,可以正常参看集群状态。查看集群状态,需要clusterMonitor权限。如果需要用其它用户账号来查看,需要给账号授权。
db.grantRolesToUser(id, ["clusterAdmin"])
至此,MongoDB的副本集搭建完成。
3.副本集验证
我们在主(Primary)节点插入数据来验证功能。
根据账号的权限,cluster账号只能用于管理集群,无法修改数据。插入数据时需要用admin账号登录。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27017/admin -u admin -p admin
MongoDB shell version v4.2.6
......
test:PRIMARY> use test
switched to db test
#创建集合
test:PRIMARY> db.createCollection("ttcollection")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1590132227, 1),
"signature" : {
"hash" : BinData(0,"U/atfhk+a6gmBUKuwktlR7f1kv0="),
"keyId" : NumberLong("6829287086298759171")
}
},
"operationTime" : Timestamp(1590132227, 1)
}
#插入测试数据
test:PRIMARY> db.ttcollection.insert({title: 'MongoDB 教程',description: 'MongoDB 是一个 Nosql 数据库',tags: ['mongodb', 'database', 'NoSQL']})
WriteResult({ "nInserted" : 1 })
到备份(Secondary)节点查看数据是否同步成功。
jishu@Jishu:~/mongodb$ sudo mongo 127.0.0.1:27018/admin -u admin -p admin
MongoDB shell version v4.2.6
......
test:SECONDARY> use test
switched to db test
test:SECONDARY> show tables;
2020-05-22T15:20:55.534+0800 E QUERY [js] uncaught exception: Error: listCollections failed: {
"operationTime" : Timestamp(1590132053, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
......
}
}
出现上面的错误是因为备份(Secondary)节点不允许读写,需要用命令开启。
test:SECONDARY> rs.slaveOk()
test:SECONDARY> show tables
ttcollection
test:SECONDARY> db.ttcollection.find()
{ "_id" : ObjectId("5ec77f0535251ff83c7cc399"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "tags" : [ "mongodb", "database", "NoSQL" ] }
数据同步成功。