一、准备
准备三台服务器,或者在一台服务器上启动三个不同端口的mongodb服务
防火墙,开放对应的端口,否则会报错
selinux设置关闭
二、mongodb搭建
#!/bin/bash
#This is a script to install mongidb, the version of mongodb is 3.4.0
#Download the installation package
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.4.0.tgz
tar zxvf mongodb-linux-x86_64-3.4.0.tgz -C /opt
cd /opt
mv mongodb-linux-x86_64-3.4.0 mongodb
Cat>>/etc/profile<<EOF
export MONGODB_HOME=/usr/local/mongodb
export PATH=$PATH:$MONGODB_HOME/bin
EOF
source /etc/profile
mkdir -p /opt/mongodb/data
mkdir -p /opt/mongodb/data/db
mkdir -p /opt/mongodb/data/logs
cd /opt/mongodb/data/logs
touch mongodb.log
cat>>/opt/mongodb/data1/mongodb.conf<<EOF
#端口号
port=27017
#数据目录
dbpath=/opt/mongodb/data/db
#日志目录
logpath=/opt/mongodb/data/logs/mongodb.log
#以后台方式运行
fork=true
#日志输出方式
logappend=true
#开启认证
#auth=true
#副本集名称
replSet=test
#本机IP
bind_ip=127.0.0.1
#操作文件最大值,单位 mb,默认硬盘百分之 5
oplogSize=10000
#不预先分配内存
noprealloc=true
EOF
cd /opt/mongodb/bin
./mongod --config /opt/mongodb/data/mongodb.conf
ln -s /opt/mongodb/bin/* /usr/bin/
按照这个安装也可以,路径改成自己想要安装的路径。
安装完成
查看进程
ps -aux |grep mongod
我这个上面启动了两个mongo
是指定路径,端口,配置文件启动
启动命令是
sudo mongod -f /opt/mongodb/data/mongodb.conf -dbpath /opt/mongodb/data/db -replSet LZAPP --bind_ip=10.10.100.186 --port=27017 &
设置开机启动
vim /etc/rc.d/rc.local
添加
sudo mongod -f /opt/mongodb/data/mongodb.conf -dbpath /opt/mongodb/data/db -replSet test --bind_ip=10.10.100.186 --port=27017
修改 rc.local文件的的权限
chmod +x rc.local
重启机器,测试看看
三、修改配置文件
port=27017 #启动端口
#数据目录
dbpath=/opt/mongodb/data/db #数据目录
logpath=/opt/mongodb/data/logs/mongodb.log #日志文件
#设置后台运行
fork=true
#日志输出方式
logappend=true
#开启认证
#auth=true
bind_ip=10.10.100.186 #本机IP,这个一定要写
replSet=test #副本集名称
四、初始化副本集
在三个节点中的任意一个节点机上操作(比如在10.10.100.186节点机)
登录mongodb
mongo 10.10.100.177:27017
登录admin数据库
>use admin
switched to db admin
#定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。
> config={_id:"test",members:[{_id:0,host:"10.10.100.186:27018"},{_id:1,host:"10.10.100.187:27018"},{_id:2,host:"10.10.100.188:27018"}]}
{
"_id" : "test",
"members" : [
{
"_id" : 0,
"host" : "10.10.100.186:27018"
},
{
"_id" : 1,
"host" : "10.10.100.187:27018"
},
{
"_id" : 2,
"host" : "10.10.100.188:27018"
}
]
}
初始化副本集配置
> rs.initiate(config)
{ "ok" : 1 }
返回1,是正常,返回0,就是失败
查看副本集配置
test:OTHER> rs.conf()
{
"_id" : "test",
"version" : 1,
"protocolVersion" : NumberLong(1),
"members" : [
{
"_id" : 0,
"host" : "10.10.100.186:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "10.10.100.187:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "10.10.100.188:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatIntervalMillis" : 2000,
"heartbeatTimeoutSecs" : 10,
"electionTimeoutMillis" : 10000,
"catchUpTimeoutMillis" : 2000,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
},
"replicaSetId" : ObjectId("5d6db54279053540497f68c8")
}
}
#查看集群节点的状态
test:PRIMARY> rs.status();
{
"set" : "test",
"date" : ISODate("2019-09-03T00:36:45.238Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "10.10.100.186:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 213,
"optime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2019-09-03T00:36:35Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1567470924, 1),
"electionDate" : ISODate("2019-09-03T00:35:24Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 1,
"name" : "10.10.100.187:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 90,
"optime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2019-09-03T00:36:35Z"),
"optimeDurableDate" : ISODate("2019-09-03T00:36:35Z"),
"lastHeartbeat" : ISODate("2019-09-03T00:36:45.077Z"),
"lastHeartbeatRecv" : ISODate("2019-09-03T00:36:43.544Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "10.10.100.186:27018",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "10.10.100.188:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 90,
"optime" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1567470995, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2019-09-03T00:36:35Z"),
"optimeDurableDate" : ISODate("2019-09-03T00:36:35Z"),
"lastHeartbeat" : ISODate("2019-09-03T00:36:45.077Z"),
"lastHeartbeatRecv" : ISODate("2019-09-03T00:36:43.543Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "10.10.100.186:27018",
"configVersion" : 1
}
],
"ok" : 1
}
五、创建帐户密码
副本集搭建成功后,需要给整个副本集创建帐户、密码
1、在主节点上,用客户端连接,创建用户权限(主节点,可以用 rs.status() 查看)
#创建分配用户权限的帐户:root
[root@localhost ~]#mongo ip:端口 #登录
replSet:PRIMARY> use admin
#创建分配用户权限的帐户
replSet:PRIMARY> db.createUser({user:"账号", pwd:"密码", roles:[{role: "root", db:"admin" }]})
Successfully added user: {
"user" : "root",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
#创建普通数据库、用户
replSet:PRIMARY>use test #创建test数据库
switched to db test
replSet:PRIMARY>db.createUser({user:"test", pwd:"test", roles:[{role: "readWrite", db:"lzkj"}]})
Successfully added user: {
"user" : "test",
"roles" : [
{
"role" : "readWrite",
"db" : "test"
}
]
}
#在test库上创建一个test用户,密码为test,具有读写权限。
#注意字符,标点符号,是英文下的的字符,否则会报错。
链接方式
[root@localhost ~]#mongo ip:端口/库名 -u 用户 -p 密码
创建副本集认证key文件
副本集设置身份验证与单机不同,需要增加一个 keyFile
以便副本集成员相互认证。
这个文件需要满足下面几点要求:
- 文本长度需要在 6 和 1024 之间
- 认证时候不考虑文件中空白字符
- 连接到副本集的成员和 mongos 进程的 keyfile 文件内容必须一样
- 必须是base64编码,但是不能有等号
- 文件权限必须是x00,也就是说,不能分配任何权限给group成员和other成员
我们可以在 Linux 上直接使用 openssl 创建一个这样的文件,然后上传至其他副本集成员服务器
创建key文件: 注意,三个节点必须要用同一份keyfile,在一台机器生成,拷贝到另外两台,并且修改成 600 的文件属性
[root@localhost ~]# openssl rand -base64 90 -out ./keyfile #生成key文件
[root@localhost ~]# cp keyfile /opt/mongodb/data/ #将key复制到指定路径
[root@localhost ~]#chomd 600 /opt/mongodb/data/keyfile #修改属性,不修改会报错
备注需要将keyfile文件同步到每一个节点
修改每个节点的配置文件mongo.conf下面的内容
...
...
#是否以安装认证方式运行
auth=true
#KeyFile鉴权文件:改为实际路径
keyFile=/opt/mongodb/data/keyfile
重新启动副本集
[root@localhost ~] mongod -f 配置文件的路径
附录
1、创建数据库的用户角色:
role角色
- 数据库用户角色:read、readWrite;
- 数据库管理角色:dbAdmin、dbOwner、userAdmin;
- 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
- 备份恢复角色:backup、restore;
- 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
- 超级用户角色:root
- 内部角色:__system
角色说明
- read:允许用户读取指定数据库
- readWrite:允许用户读写指定数据库
- dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
- userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
- clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
- readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
- readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
- userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
- dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
- root:只在admin数据库中可用。超级账号,超级权限
- dbOwner: readWrite + dbAdmin + dbAdmin
Java客户端连接配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- credentials="用户名:密码@用户归属数据库" -->
<mongo:mongo-client replica-set="172.17.0.3:27018,172.17.0.4:27018, 172.17.0.5:27018" credentials="mytest:mytest@mytest" id="mongo">
<mongo:client-options
connections-per-host="20"
threads-allowed-to-block-for-connection-multiplier="10"
connect-timeout="120000"
max-wait-time="120000"
socket-keep-alive="true"
socket-timeout="150000"
/>
</mongo:mongo-client>
<mongo:db-factory dbname="数据库名" mongo-ref="mongo" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
</beans>
Spring Boot配置
spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database
副本集常用管理命令
命令 解释
rs.initiate() 使用默认配置初始化副本集
rs.initiate(cfg) 使用配置文件cfg初始化副本集
rs.reconfig(cfg) 修改副本集配置信息
rs.status() 查看副本集状态
rs.conf() 查看副本集配置
rs.add(hostportstr)
rs.add(membercfgobj) 添加新的节点
rs.addArb(hostportstr) 添加投票节点
rs.remove(hostportstr) 删除节点
rs.slaveOk() 允许从库只读,默认从库不允许读写
rs.isMaster() 查看哪个节点为主节点
rs.printReplicationInfo() 查看oplog大小以及oplog可用时间,可以判断系统繁忙程度
rs.printSlaveReplicationInfo() 查看复制集成员以及延迟
rs.stepDown([stepdownSecs, catchUpSecs]) 手动主从切换
rs.freeze(secs) 冻结当前节点在指定的时间内(秒)不能选举为主
rs.syncFrom(hostportstr) 管理员临时覆盖当前成员的默认同步目标。以[hostname]:[port]的形式指定要复制的成员的名称。
db.system.users.find() 查看已存在的用户
db.system.users.remove({user:”simpleUser”}) 删除用户
六、测试Mongodb副本集数据复制功能 <mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读>
1)在主节点建立数据库,插入测试数据
test:PRIMARY> use test;
switched to db test
test:PRIMARY> db.testdb.insert({"test1":"testval1"})
WriteResult({ "nInserted" : 1 })
2)在从节点查看数据是否复制过来
登录数据库
mongo 10.10.100.187:27017
test:SECONDARY> use test;
switched to db test
test:SECONDARY> show tables;
2019-09-03T08:40:09.371+0800 E QUERY [main] Error: listCollections failed: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype._getCollectionInfosCommand@src/mongo/shell/db.js:805:1
DB.prototype.getCollectionInfos@src/mongo/shell/db.js:817:19
DB.prototype.getCollectionNames@src/mongo/shell/db.js:828:16
shellHelper.show@src/mongo/shell/utils.js:748:9
shellHelper@src/mongo/shell/utils.js:645:15
@(shellhelp2):1:1
上面出现了报错!
这是因为mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读
test:SECONDARY> db.getMongo().setSlaveOk(); 设置一下
test:SECONDARY> show tables; 就可以读出来了,两台都需要设置一下
testdb
现在,把主节点宕掉,登录另外的节点,查看副本集的状态
test:PRIMARY> rs.status();
{
"set" : "test",
"date" : ISODate("2019-09-03T00:46:21.057Z"),
"myState" : 1,
"term" : NumberLong(2),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"appliedOpTime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"durableOpTime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
}
},
"members" : [
{
"_id" : 0,
"name" : "10.10.100.186:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 34,
"optime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2019-09-03T00:46:17Z"),
"optimeDurableDate" : ISODate("2019-09-03T00:46:17Z"),
"lastHeartbeat" : ISODate("2019-09-03T00:46:20.494Z"),
"lastHeartbeatRecv" : ISODate("2019-09-03T00:46:19.899Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "10.10.100.188:27018",
"configVersion" : 1
},
{
"_id" : 1,
"name" : "10.10.100.187:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 664,
"optime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2019-09-03T00:46:17Z"),
"optimeDurableDate" : ISODate("2019-09-03T00:46:17Z"),
"lastHeartbeat" : ISODate("2019-09-03T00:46:20.462Z"),
"lastHeartbeatRecv" : ISODate("2019-09-03T00:46:19.161Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "10.10.100.188:27018",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "10.10.100.188:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 724,
"optime" : {
"ts" : Timestamp(1567471577, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2019-09-03T00:46:17Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1567471486, 1),
"electionDate" : ISODate("2019-09-03T00:44:46Z"),
"configVersion" : 1,
"self" : true
}
],
"ok" : 1
}
发现当原来的主节点10.10.100.186宕掉后,经过选举,原来的从节点10.10.100.188被推举为新的主节点。
然后在新的节点插入数据
test:PRIMARY> for(var i=0;i<10000;i++){db.test.insert({"name":"test"+i,"age":123})}
WriteResult({ "nInserted" : 1 })
test:PRIMARY>
登录从节点查看数据是否同步
test:SECONDARY> db.test.find()
{ "_id" : ObjectId("5d6db9ce460b07bcce150716"), "name" : "test0", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150718"), "name" : "test2", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150717"), "name" : "test1", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071b"), "name" : "test5", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071e"), "name" : "test8", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150719"), "name" : "test3", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071c"), "name" : "test6", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071d"), "name" : "test7", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071a"), "name" : "test4", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150720"), "name" : "test10", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15071f"), "name" : "test9", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150721"), "name" : "test11", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150722"), "name" : "test12", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15072b"), "name" : "test21", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150725"), "name" : "test15", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150728"), "name" : "test18", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce15072a"), "name" : "test20", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150726"), "name" : "test16", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150729"), "name" : "test19", "age" : 123 }
{ "_id" : ObjectId("5d6db9ce460b07bcce150727"), "name" : "test17", "age" : 123 }
发现数据已经同步
六、Mongodb读写分离
目前来看。Mongodb副本集可以完美支持故障转移。至于主节点的读写压力过大如何解决?常见的解决方案是读写分离。
一般情况下,常规写操作来说并没有读操作多,所以在Mongodb副本集中,一台主节点负责写操作,两台副本节点负责读操作。
1)设置读写分离需要先在副本节点SECONDARY 设置 setSlaveOk。
2)在程序中设置副本节点负责读操作,如下代码:<br>
public class TestMongoDBReplSetReadSplit {
public static void main(String[] args) {
try {
List<ServerAddress> addresses = new ArrayList<ServerAddress>();
ServerAddress address1 = new ServerAddress("10.10.100.186" , 27017);
ServerAddress address2 = new ServerAddress("10.10.100.187" , 27017);
ServerAddress address3 = new ServerAddress("10.10.100.188" , 27017);
addresses.add(address1);
addresses.add(address2);
addresses.add(address3);
MongoClient client = new MongoClient(addresses);
DB db = client.getDB( "test" );
DBCollection coll = db.getCollection( "testdb" );
BasicDBObject object = new BasicDBObject();
object.append( "test2" , "testval2" );
//读操作从副本节点读取
ReadPreference preference = ReadPreference. secondary();
DBObject dbObject = coll.findOne(object, null , preference);
System. out .println(dbObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}
读参数除了secondary一共还有五个参数:primary、primaryPreferred、secondary、secondaryPreferred、nearest。
primary:默认参数,只从主节点上进行读取操作;
primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。
secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。
secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;
nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。
读写分离做好后,就可以进行数据分流,减轻压力,解决了"主节点的读写压力过大如何解决?"这个问题。不过当副本节点增多时,主节点的复制压力会加大有什么办法解决吗?基于这个问题,Mongodb已有了相应的解决方案 - 引用仲裁节点:
在Mongodb副本集中,仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。看起来想的很周到啊,其实不只是主节点、副本节点、仲裁节点,还有Secondary-Only、Hidden、Delayed、Non-Voting,其中:
Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。
Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。
Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点,恢复又恢复不了。
Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。